diff options
448 files changed, 19522 insertions, 9223 deletions
diff --git a/Android.mk b/Android.mk index ace7b7c163a4..5acfb86f5411 100644 --- a/Android.mk +++ b/Android.mk @@ -94,7 +94,7 @@ LOCAL_SRC_FILES += \ core/java/android/bluetooth/IBluetoothHealthCallback.aidl \ core/java/android/bluetooth/IBluetoothPbap.aidl \ core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \ - core/java/android/content/ICancelationSignal.aidl \ + core/java/android/content/ICancellationSignal.aidl \ core/java/android/content/IClipboard.aidl \ core/java/android/content/IContentService.aidl \ core/java/android/content/IIntentReceiver.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index fb334fce090e..d74d7b171f1f 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -117,6 +117,8 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/fonts/DroidSans*) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/fonts/DroidSans*) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/android/content) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ diff --git a/api/current.txt b/api/current.txt index 0b2ecea713ad..574af6b9f55d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2288,7 +2288,8 @@ package android.animation { method public long getStagger(int); method public long getStartDelay(int); method public java.util.List<android.animation.LayoutTransition.TransitionListener> getTransitionListeners(); - method public void hideChild(android.view.ViewGroup, android.view.View); + method public deprecated void hideChild(android.view.ViewGroup, android.view.View); + method public void hideChild(android.view.ViewGroup, android.view.View, int); method public boolean isChangingLayout(); method public boolean isRunning(); method public void removeChild(android.view.ViewGroup, android.view.View); @@ -2300,7 +2301,8 @@ package android.animation { method public void setInterpolator(int, android.animation.TimeInterpolator); method public void setStagger(int, long); method public void setStartDelay(int, long); - method public void showChild(android.view.ViewGroup, android.view.View); + method public deprecated void showChild(android.view.ViewGroup, android.view.View); + method public void showChild(android.view.ViewGroup, android.view.View, int); field public static final int APPEARING = 2; // 0x2 field public static final int CHANGE_APPEARING = 0; // 0x0 field public static final int CHANGE_DISAPPEARING = 1; // 0x1 @@ -2347,6 +2349,15 @@ package android.animation { method public void setPropertyName(java.lang.String); } + public class TimeAnimator extends android.animation.ValueAnimator { + ctor public TimeAnimator(); + method public void setTimeListener(android.animation.TimeAnimator.TimeListener); + } + + public static abstract interface TimeAnimator.TimeListener { + method public abstract void onTimeUpdate(android.animation.TimeAnimator, long, long); + } + public abstract interface TimeInterpolator { method public abstract float getInterpolation(float); } @@ -2400,6 +2411,16 @@ package android.animation { } +package android.annotation { + + public abstract class SuppressLint implements java.lang.annotation.Annotation { + } + + public abstract class TargetApi implements java.lang.annotation.Annotation { + } + +} + package android.app { public abstract class ActionBar { @@ -4658,10 +4679,9 @@ package android.content { public abstract class AsyncTaskLoader extends android.content.Loader { ctor public AsyncTaskLoader(android.content.Context); - method public boolean cancelLoad(); - method protected boolean isLoadInBackgroundCanceled(); + method public void cancelLoadInBackground(); + method public boolean isLoadInBackgroundCanceled(); method public abstract D loadInBackground(); - method protected void onCancelLoadInBackground(); method public void onCanceled(D); method protected D onLoadInBackground(); method public void setUpdateThrottle(long); @@ -4703,15 +4723,15 @@ package android.content { method public final void setResultExtras(android.os.Bundle); } - public final class CancelationSignal { - ctor public CancelationSignal(); + public final class CancellationSignal { + ctor public CancellationSignal(); method public void cancel(); method public boolean isCanceled(); - method public void setOnCancelListener(android.content.CancelationSignal.OnCancelListener); + method public void setOnCancelListener(android.content.CancellationSignal.OnCancelListener); method public void throwIfCanceled(); } - public static abstract interface CancelationSignal.OnCancelListener { + public static abstract interface CancellationSignal.OnCancelListener { method public abstract void onCancel(); } @@ -4834,7 +4854,7 @@ package android.content { method public android.os.ParcelFileDescriptor openPipeHelper(android.net.Uri, java.lang.String, android.os.Bundle, T, android.content.ContentProvider.PipeDataWriter<T>) throws java.io.FileNotFoundException; method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); - method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancelationSignal); + method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancellationSignal); method protected final void setPathPermissions(android.content.pm.PathPermission[]); method protected final void setReadPermission(java.lang.String); method protected final void setWritePermission(java.lang.String); @@ -4858,7 +4878,7 @@ package android.content { method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException, android.os.RemoteException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException; - method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancelationSignal) throws android.os.RemoteException; + method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancellationSignal) throws android.os.RemoteException; method public boolean release(); method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException; } @@ -4945,7 +4965,7 @@ package android.content { method public final java.io.OutputStream openOutputStream(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); - method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancelationSignal); + method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancellationSignal); method public final void registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver); method public static void removePeriodicSync(android.accounts.Account, java.lang.String, android.os.Bundle); method public static void removeStatusChangeListener(java.lang.Object); @@ -5628,6 +5648,7 @@ package android.content { field public static final int FLAG_GRANT_READ_URI_PERMISSION = 1; // 0x1 field public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 2; // 0x2 field public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32; // 0x20 + field public static final int FLAG_RECEIVER_FOREGROUND = 268435456; // 0x10000000 field public static final int FLAG_RECEIVER_REGISTERED_ONLY = 1073741824; // 0x40000000 field public static final int FLAG_RECEIVER_REPLACE_PENDING = 536870912; // 0x20000000 field public static final java.lang.String METADATA_DOCK_HOME = "android.dock_home"; @@ -5752,7 +5773,9 @@ package android.content { public class Loader { ctor public Loader(android.content.Context); method public void abandon(); + method public boolean cancelLoad(); method public java.lang.String dataToString(D); + method public void deliverCancellation(); method public void deliverResult(D); method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); method public void forceLoad(); @@ -5762,23 +5785,30 @@ package android.content { method public boolean isReset(); method public boolean isStarted(); method protected void onAbandon(); + method protected boolean onCancelLoad(); method public void onContentChanged(); method protected void onForceLoad(); method protected void onReset(); method protected void onStartLoading(); method protected void onStopLoading(); method public void registerListener(int, android.content.Loader.OnLoadCompleteListener<D>); + method public void registerOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>); method public void reset(); method public final void startLoading(); method public void stopLoading(); method public boolean takeContentChanged(); method public void unregisterListener(android.content.Loader.OnLoadCompleteListener<D>); + method public void unregisterOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>); } public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver { ctor public Loader.ForceLoadContentObserver(); } + public static abstract interface Loader.OnLoadCanceledListener { + method public abstract void onLoadCanceled(android.content.Loader<D>); + } + public static abstract interface Loader.OnLoadCompleteListener { method public abstract void onLoadComplete(android.content.Loader<D>, D); } @@ -7247,15 +7277,15 @@ package android.database.sqlite { method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory); method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler); method public android.database.Cursor query(boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String); - method public android.database.Cursor query(boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancelationSignal); + method public android.database.Cursor query(boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancellationSignal); method public android.database.Cursor query(java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String); method public android.database.Cursor query(java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String); method public android.database.Cursor queryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String); - method public android.database.Cursor queryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancelationSignal); + method public android.database.Cursor queryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancellationSignal); method public android.database.Cursor rawQuery(java.lang.String, java.lang.String[]); - method public android.database.Cursor rawQuery(java.lang.String, java.lang.String[], android.content.CancelationSignal); + method public android.database.Cursor rawQuery(java.lang.String, java.lang.String[], android.content.CancellationSignal); method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, java.lang.String, java.lang.String[], java.lang.String); - method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, java.lang.String, java.lang.String[], java.lang.String, android.content.CancelationSignal); + method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, java.lang.String, java.lang.String[], java.lang.String, android.content.CancellationSignal); method public static int releaseMemory(); method public long replace(java.lang.String, java.lang.String, android.content.ContentValues); method public long replaceOrThrow(java.lang.String, java.lang.String, android.content.ContentValues) throws android.database.SQLException; @@ -7377,7 +7407,7 @@ package android.database.sqlite { method public java.lang.String getTables(); method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String); method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String); - method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancelationSignal); + method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancellationSignal); method public void setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory); method public void setDistinct(boolean); method public void setProjectionMap(java.util.Map<java.lang.String, java.lang.String>); @@ -9081,14 +9111,17 @@ package android.graphics.drawable { ctor public GradientDrawable(android.graphics.drawable.GradientDrawable.Orientation, int[]); method public void draw(android.graphics.Canvas); method public int getOpacity(); + method public android.graphics.drawable.GradientDrawable.Orientation getOrientation(); method public void setAlpha(int); method public void setColor(int); method public void setColorFilter(android.graphics.ColorFilter); + method public void setColors(int[]); method public void setCornerRadii(float[]); method public void setCornerRadius(float); method public void setGradientCenter(float, float); method public void setGradientRadius(float); method public void setGradientType(int); + method public void setOrientation(android.graphics.drawable.GradientDrawable.Orientation); method public void setShape(int); method public void setSize(int, int); method public void setStroke(int, int); @@ -10902,11 +10935,12 @@ package android.media { } public final class MediaRecorder.OutputFormat { + field public static final int AAC_ADTS = 6; // 0x6 field public static final int AMR_NB = 3; // 0x3 field public static final int AMR_WB = 4; // 0x4 field public static final int DEFAULT = 0; // 0x0 field public static final int MPEG_4 = 2; // 0x2 - field public static final int RAW_AMR = 3; // 0x3 + field public static final deprecated int RAW_AMR = 3; // 0x3 field public static final int THREE_GPP = 1; // 0x1 } @@ -11615,7 +11649,7 @@ package android.net { method public void setNetworkPreference(int); method public int startUsingNetworkFeature(int, java.lang.String); method public int stopUsingNetworkFeature(int, java.lang.String); - field public static final java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; field public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 field public static final java.lang.String EXTRA_EXTRA_INFO = "extraInfo"; @@ -17481,6 +17515,7 @@ package android.provider { field public static final android.net.Uri CONTENT_URI; field public static final java.lang.String DATA_ROAMING = "data_roaming"; field public static final java.lang.String DEFAULT_INPUT_METHOD = "default_input_method"; + field public static final java.lang.String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled"; field public static final java.lang.String DEVICE_PROVISIONED = "device_provisioned"; field public static final java.lang.String ENABLED_ACCESSIBILITY_SERVICES = "enabled_accessibility_services"; field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods"; @@ -17559,6 +17594,7 @@ package android.provider { field public static final java.lang.String ALARM_ALERT = "alarm_alert"; field public static final java.lang.String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities"; field public static final deprecated java.lang.String ANDROID_ID = "android_id"; + field public static final java.lang.String ANIMATOR_DURATION_SCALE = "animator_duration_scale"; field public static final java.lang.String APPEND_FOR_LAST_AUDIBLE = "_last_audible"; field public static final java.lang.String AUTO_TIME = "auto_time"; field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone"; @@ -23826,6 +23862,9 @@ package android.view { method public android.view.ViewPropertyAnimator translationXBy(float); method public android.view.ViewPropertyAnimator translationY(float); method public android.view.ViewPropertyAnimator translationYBy(float); + method public android.view.ViewPropertyAnimator withEndAction(java.lang.Runnable); + method public android.view.ViewPropertyAnimator withLayer(); + method public android.view.ViewPropertyAnimator withStartAction(java.lang.Runnable); method public android.view.ViewPropertyAnimator x(float); method public android.view.ViewPropertyAnimator xBy(float); method public android.view.ViewPropertyAnimator y(float); diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index fddb429d5ae5..3d36ebf09f92 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -135,6 +136,8 @@ public class Am { runToUri(false); } else if (op.equals("to-intent-uri")) { runToUri(true); + } else if (op.equals("switch-profile")) { + runSwitchUser(); } else { throw new IllegalArgumentException("Unknown command: " + op); } @@ -531,7 +534,8 @@ public class Am { Intent intent = makeIntent(); IntentReceiver receiver = new IntentReceiver(); System.out.println("Broadcasting: " + intent); - mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null, true, false); + mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null, true, false, + Binder.getOrigCallingUser()); receiver.waitForFinish(); } @@ -722,6 +726,14 @@ public class Am { mAm.setDebugApp(null, false, true); } + private void runSwitchUser() throws Exception { + if (android.os.Process.myUid() != 0) { + throw new RuntimeException("switchuser can only be run as root"); + } + String user = nextArgRequired(); + mAm.switchUser(Integer.parseInt(user)); + } + class MyActivityController extends IActivityController.Stub { final String mGdbPort; diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c index 83c881a74902..16dc5178504a 100644 --- a/cmds/dumpstate/dumpstate.c +++ b/cmds/dumpstate/dumpstate.c @@ -122,7 +122,7 @@ static void dumpstate() { dump_file("NETWORK DEV INFO", "/proc/net/dev"); dump_file("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all"); dump_file("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl"); - run_command("QTAGUID STATS INFO", 10, "su", "root", "cat", "/proc/net/xt_qtaguid/stats", NULL); + dump_file("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats"); dump_file("NETWORK ROUTES", "/proc/net/route"); dump_file("NETWORK ROUTES IPV6", "/proc/net/ipv6_route"); @@ -334,7 +334,7 @@ int main(int argc, char *argv[]) { } /* switch to non-root user and group */ - gid_t groups[] = { AID_LOG, AID_SDCARD_RW, AID_MOUNT, AID_INET }; + gid_t groups[] = { AID_LOG, AID_SDCARD_RW, AID_MOUNT, AID_INET, AID_NET_BW_STATS }; if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) { ALOGE("Unable to setgroups, aborting: %s\n", strerror(errno)); return -1; diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index dd92bbe499bc..203d180a6439 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -148,6 +148,48 @@ int delete_persona(uid_t persona) return delete_dir_contents(pkgdir, 1, NULL); } +int clone_persona_data(uid_t src_persona, uid_t target_persona, int copy) +{ + char src_data_dir[PKG_PATH_MAX]; + char pkg_path[PKG_PATH_MAX]; + DIR *d; + struct dirent *de; + struct stat s; + uid_t uid; + + if (create_persona_path(src_data_dir, src_persona)) { + return -1; + } + + d = opendir(src_data_dir); + if (d != NULL) { + while ((de = readdir(d))) { + const char *name = de->d_name; + + if (de->d_type == DT_DIR) { + int subfd; + /* always skip "." and ".." */ + if (name[0] == '.') { + if (name[1] == 0) continue; + if ((name[1] == '.') && (name[2] == 0)) continue; + } + /* Create the full path to the package's data dir */ + create_pkg_path(pkg_path, name, PKG_DIR_POSTFIX, src_persona); + /* Get the file stat */ + if (stat(pkg_path, &s) < 0) continue; + /* Get the uid of the package */ + ALOGI("Adding datadir for uid = %d\n", s.st_uid); + uid = (uid_t) s.st_uid % PER_USER_RANGE; + /* Create the directory for the target */ + make_user_data(name, uid + target_persona * PER_USER_RANGE, + target_persona); + } + } + closedir(d); + } + return 0; +} + int delete_cache(const char *pkgname) { char cachedir[PKG_PATH_MAX]; diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c index 569b491be012..7f94a9659998 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -107,6 +107,11 @@ static int do_rm_user(char **arg, char reply[REPLY_MAX]) return delete_persona(atoi(arg[0])); /* userid */ } +static int do_clone_user_data(char **arg, char reply[REPLY_MAX]) +{ + return clone_persona_data(atoi(arg[0]), atoi(arg[1]), atoi(arg[2])); +} + static int do_movefiles(char **arg, char reply[REPLY_MAX]) { return movefiles(); @@ -146,6 +151,7 @@ struct cmdinfo cmds[] = { { "unlinklib", 1, do_unlinklib }, { "mkuserdata", 3, do_mk_user_data }, { "rmuser", 1, do_rm_user }, + { "cloneuserdata", 3, do_clone_user_data }, }; static int readx(int s, void *_buf, int count) diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index 173cabfb77a7..78342bb6ac09 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -72,6 +72,9 @@ #define PKG_NAME_MAX 128 /* largest allowed package name */ #define PKG_PATH_MAX 256 /* max size of any path we use */ +#define PER_USER_RANGE ((uid_t)100000) /* range of uids per user + uid = persona * PER_USER_RANGE + appid */ + /* data structures */ typedef struct { @@ -143,6 +146,7 @@ int renamepkg(const char *oldpkgname, const char *newpkgname); 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 clone_persona_data(uid_t src_persona, uid_t target_persona, int copy); 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/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index c0ba543eee36..f4578429bc61 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -16,8 +16,6 @@ package com.android.commands.pm; -import com.android.internal.content.PackageHelper; - import android.app.ActivityManagerNative; import android.content.ComponentName; import android.content.pm.ApplicationInfo; @@ -33,14 +31,17 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; +import android.content.pm.UserInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; -import android.os.Parcel; +import android.os.Binder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import com.android.internal.content.PackageHelper; + import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -135,13 +136,18 @@ public final class Pm { return; } - if ("createUser".equals(op)) { - runCreateUser(); + if ("create-profile".equals(op)) { + runCreateProfile(); + return; + } + + if ("remove-profile".equals(op)) { + runRemoveProfile(); return; } - if ("removeUser".equals(op)) { - runRemoveUser(); + if ("list-profiles".equals(op)) { + runListProfiles(); return; } @@ -829,10 +835,10 @@ public final class Pm { } } - public void runCreateUser() { + public void runCreateProfile() { // Need to be run as root if (Process.myUid() != ROOT_UID) { - System.err.println("Error: createUser must be run as root"); + System.err.println("Error: create-profile must be run as root"); return; } String name; @@ -845,7 +851,7 @@ public final class Pm { name = arg; try { if (mPm.createUser(name, 0) == null) { - System.err.println("Error: couldn't create user."); + System.err.println("Error: couldn't create profile."); showUsage(); } } catch (RemoteException e) { @@ -855,10 +861,10 @@ public final class Pm { } - public void runRemoveUser() { + public void runRemoveProfile() { // Need to be run as root if (Process.myUid() != ROOT_UID) { - System.err.println("Error: removeUser must be run as root"); + System.err.println("Error: remove-profile must be run as root"); return; } int userId; @@ -877,7 +883,7 @@ public final class Pm { } try { if (!mPm.removeUser(userId)) { - System.err.println("Error: couldn't remove user."); + System.err.println("Error: couldn't remove profile."); showUsage(); } } catch (RemoteException e) { @@ -886,6 +892,27 @@ public final class Pm { } } + public void runListProfiles() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: list-profiles must be run as root"); + return; + } + try { + List<UserInfo> users = mPm.getUsers(); + if (users == null) { + System.err.println("Error: couldn't get users"); + } else { + System.out.println("Users:"); + for (int i = 0; i < users.size(); i++) { + System.out.println("\t" + users.get(i).toString()); + } + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } class PackageDeleteObserver extends IPackageDeleteObserver.Stub { boolean finished; boolean result; @@ -966,7 +993,8 @@ public final class Pm { ClearDataObserver obs = new ClearDataObserver(); try { - if (!ActivityManagerNative.getDefault().clearApplicationUserData(pkg, obs)) { + if (!ActivityManagerNative.getDefault().clearApplicationUserData(pkg, obs, + Binder.getOrigCallingUser())) { System.err.println("Failed"); } @@ -1132,8 +1160,8 @@ public final class Pm { System.err.println(" pm disable-user PACKAGE_OR_COMPONENT"); System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]"); System.err.println(" pm get-install-location"); - System.err.println(" pm createUser USER_NAME"); - System.err.println(" pm removeUser USER_ID"); + System.err.println(" pm create-profile USER_NAME"); + System.err.println(" pm remove-profile USER_ID"); System.err.println(""); System.err.println("pm list packages: prints all packages, optionally only"); System.err.println(" those whose package name contains the text in FILTER. Options:"); diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index 7a599e93e73b..bee5880ac4cc 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -28,6 +28,7 @@ #include <SkImageEncoder.h> #include <SkBitmap.h> +#include <SkData.h> #include <SkStream.h> using namespace android; @@ -168,7 +169,9 @@ int main(int argc, char** argv) SkDynamicMemoryWStream stream; SkImageEncoder::EncodeStream(&stream, b, SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality); - write(fd, stream.getStream(), stream.getOffset()); + SkData* streamData = stream.copyToData(); + write(fd, streamData->data(), streamData->size()); + streamData->unref(); } else { write(fd, &w, 4); write(fd, &h, 4); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 211be52e0622..a463a62c5045 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -17,8 +17,10 @@ package android.accessibilityservice; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; @@ -218,10 +220,17 @@ public abstract class AccessibilityService extends Service { private static final String LOG_TAG = "AccessibilityService"; - private AccessibilityServiceInfo mInfo; + interface Callbacks { + public void onAccessibilityEvent(AccessibilityEvent event); + public void onInterrupt(); + public void onServiceConnected(); + public void onSetConnectionId(int connectionId); + } private int mConnectionId; + private AccessibilityServiceInfo mInfo; + /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. * @@ -282,27 +291,49 @@ public abstract class AccessibilityService extends Service { */ @Override public final IBinder onBind(Intent intent) { - return new IEventListenerWrapper(this); + return new IEventListenerWrapper(this, getMainLooper(), new Callbacks() { + @Override + public void onServiceConnected() { + AccessibilityService.this.onServiceConnected(); + } + + @Override + public void onInterrupt() { + AccessibilityService.this.onInterrupt(); + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + AccessibilityService.this.onAccessibilityEvent(event); + } + + @Override + public void onSetConnectionId( int connectionId) { + mConnectionId = connectionId; + } + }); } /** * Implements the internal {@link IEventListener} interface to convert * incoming calls to it back to calls on an {@link AccessibilityService}. */ - class IEventListenerWrapper extends IEventListener.Stub + static class IEventListenerWrapper extends IEventListener.Stub implements HandlerCaller.Callback { + static final int NO_ID = -1; + private static final int DO_SET_SET_CONNECTION = 10; private static final int DO_ON_INTERRUPT = 20; private static final int DO_ON_ACCESSIBILITY_EVENT = 30; private final HandlerCaller mCaller; - private final AccessibilityService mTarget; + private final Callbacks mCallback; - public IEventListenerWrapper(AccessibilityService context) { - mTarget = context; - mCaller = new HandlerCaller(context, this); + public IEventListenerWrapper(Context context, Looper looper, Callbacks callback) { + mCallback = callback; + mCaller = new HandlerCaller(context, looper, this); } public void setConnection(IAccessibilityServiceConnection connection, int connectionId) { @@ -326,12 +357,13 @@ public abstract class AccessibilityService extends Service { case DO_ON_ACCESSIBILITY_EVENT : AccessibilityEvent event = (AccessibilityEvent) message.obj; if (event != null) { - mTarget.onAccessibilityEvent(event); + AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); + mCallback.onAccessibilityEvent(event); event.recycle(); } return; case DO_ON_INTERRUPT : - mTarget.onInterrupt(); + mCallback.onInterrupt(); return; case DO_SET_SET_CONNECTION : final int connectionId = message.arg1; @@ -340,12 +372,11 @@ public abstract class AccessibilityService extends Service { if (connection != null) { AccessibilityInteractionClient.getInstance().addConnection(connectionId, connection); - mConnectionId = connectionId; - mTarget.onServiceConnected(); + mCallback.onSetConnectionId(connectionId); + mCallback.onServiceConnected(); } else { AccessibilityInteractionClient.getInstance().removeConnection(connectionId); - mConnectionId = AccessibilityInteractionClient.NO_ID; - // TODO: Do we need a onServiceDisconnected callback? + mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID); } return; default : diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index e53b31395bb1..c9468eb11f23 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -32,8 +32,13 @@ interface IAccessibilityServiceConnection { /** * Finds an {@link AccessibilityNodeInfo} by accessibility id. * - * @param accessibilityWindowId A unique window id. - * @param accessibilityNodeId A unique view id or virtual descendant id. + * @param accessibilityWindowId A unique window id. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id or virtual descendant id from + * where to start the search. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID} + * to start from the root. * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. * @param threadId The id of the calling thread. @@ -46,57 +51,58 @@ interface IAccessibilityServiceConnection { /** * Finds {@link AccessibilityNodeInfo}s by View text. The match is case * insensitive containment. The search is performed in the window whose - * id is specified and starts from the View whose accessibility id is + * id is specified and starts from the node whose accessibility id is * specified. * - * @param text The searched text. - * @param accessibilityWindowId A unique window id. + * @param accessibilityWindowId A unique window id. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID} + * to query the currently active window. * @param accessibilityNodeId A unique view id or virtual descendant id from - * where to start the search. Use {@link android.view.View#NO_ID} to start from the root. - * @param interactionId The id of the interaction for matching with the callback result. - * @param callback Callback which to receive the result. - * @param threadId The id of the calling thread. - * @return The current window scale, where zero means a failure. - */ - float findAccessibilityNodeInfosByText(String text, int accessibilityWindowId, - long accessibilityNodeId, int interractionId, - IAccessibilityInteractionConnectionCallback callback, long threadId); - - /** - * Finds {@link AccessibilityNodeInfo}s by View text. The match is case - * insensitive containment. The search is performed in the currently - * active window and start from the root View in the window. - * + * where to start the search. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID} + * to start from the root. * @param text The searched text. - * @param accessibilityId The id of the view from which to start searching. - * Use {@link android.view.View#NO_ID} to start from the root. * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. * @param threadId The id of the calling thread. * @return The current window scale, where zero means a failure. */ - float findAccessibilityNodeInfosByTextInActiveWindow(String text, - int interactionId, IAccessibilityInteractionConnectionCallback callback, + float findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, + String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); /** - * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed - * in the currently active window and starts from the root View in the window. + * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in + * the window whose id is specified and starts from the node whose accessibility + * id is specified. * + * @param accessibilityWindowId A unique window id. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id or virtual descendant id from + * where to start the search. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID} + * to start from the root. * @param id The id of the node. * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. * @param threadId The id of the calling thread. * @return The current window scale, where zero means a failure. */ - float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long threadId); + float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId, + int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, + long threadId); /** * Performs an accessibility action on an {@link AccessibilityNodeInfo}. * - * @param accessibilityWindowId The id of the window. - * @param accessibilityNodeId A unique view id or virtual descendant id. + * @param accessibilityWindowId A unique window id. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id or virtual descendant id from + * where to start the search. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID} + * to start from the root. * @param action The action to perform. * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java new file mode 100644 index 000000000000..616b7966eab6 --- /dev/null +++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2012 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.accessibilityservice; + +import android.accessibilityservice.AccessibilityService.Callbacks; +import android.accessibilityservice.AccessibilityService.IEventListenerWrapper; +import android.content.Context; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.Log; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityInteractionClient; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.IAccessibilityManager; + +import com.android.internal.util.Predicate; + +import java.util.List; +import java.util.concurrent.TimeoutException; + +/** + * This class represents a bridge that can be used for UI test + * automation. It is responsible for connecting to the system, + * keeping track of the last accessibility event, and exposing + * window content querying APIs. This class is designed to be + * used from both an Android application and a Java program + * run from the shell. + * + * @hide + */ +public class UiTestAutomationBridge { + + private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName(); + + public static final int ACTIVE_WINDOW_ID = -1; + + public static final long ROOT_NODE_ID = -1; + + private static final int TIMEOUT_REGISTER_SERVICE = 5000; + + private final Object mLock = new Object(); + + private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID; + + private IEventListenerWrapper mListener; + + private AccessibilityEvent mLastEvent; + + private AccessibilityEvent mLastWindowStateChangeEvent; + + private volatile boolean mWaitingForEventDelivery; + + private volatile boolean mUnprocessedEventAvailable; + + /** + * Gets the last received {@link AccessibilityEvent}. + * + * @return The event. + */ + public AccessibilityEvent getLastAccessibilityEvent() { + return mLastEvent; + } + + /** + * Callback for receiving an {@link AccessibilityEvent}. + * + * <strong>Note:</strong> This method is <strong>NOT</strong> + * executed on the application main thread. The client are + * responsible for proper synchronization. + * + * @param event The received event. + */ + public void onAccessibilityEvent(AccessibilityEvent event) { + /* hook - do nothing */ + } + + /** + * Callback for requests to stop feedback. + * + * <strong>Note:</strong> This method is <strong>NOT</strong> + * executed on the application main thread. The client are + * responsible for proper synchronization. + */ + public void onInterrupt() { + /* hook - do nothing */ + } + + /** + * Connects this service. + * + * @throws IllegalStateException If already connected. + */ + public void connect() { + if (isConnected()) { + throw new IllegalStateException("Already connected."); + } + + // Serialize binder calls to a handler on a dedicated thread + // different from the main since we expose APIs that block + // the main thread waiting for a result the deliver of which + // on the main thread will prevent that thread from waking up. + // The serialization is needed also to ensure that events are + // examined in delivery order. Otherwise, a fair locking + // is needed for making sure the binder calls are interleaved + // with check for the expected event and also to make sure the + // binder threads are allowed to proceed in the received order. + HandlerThread handlerThread = new HandlerThread("UiTestAutomationBridge"); + handlerThread.start(); + Looper looper = handlerThread.getLooper(); + + mListener = new IEventListenerWrapper(null, looper, new Callbacks() { + @Override + public void onServiceConnected() { + /* do nothing */ + } + + @Override + public void onInterrupt() { + UiTestAutomationBridge.this.onInterrupt(); + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + synchronized (mLock) { + while (true) { + mLastEvent = AccessibilityEvent.obtain(event); + + final int eventType = event.getEventType(); + if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + || eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) { + if (mLastWindowStateChangeEvent != null) { + mLastWindowStateChangeEvent.recycle(); + } + mLastWindowStateChangeEvent = mLastEvent; + } + + if (!mWaitingForEventDelivery) { + break; + } + if (!mUnprocessedEventAvailable) { + mUnprocessedEventAvailable = true; + mLock.notifyAll(); + break; + } + try { + mLock.wait(); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + UiTestAutomationBridge.this.onAccessibilityEvent(event); + } + + @Override + public void onSetConnectionId(int connectionId) { + synchronized (mLock) { + mConnectionId = connectionId; + mLock.notifyAll(); + } + } + }); + + final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( + ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); + + final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); + info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; + info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; + + try { + manager.registerUiTestAutomationService(mListener, info); + } catch (RemoteException re) { + throw new IllegalStateException("Cound not register UiAutomationService.", re); + } + + synchronized (mLock) { + final long startTimeMillis = SystemClock.uptimeMillis(); + while (true) { + if (isConnected()) { + return; + } + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + final long remainingTimeMillis = TIMEOUT_REGISTER_SERVICE - elapsedTimeMillis; + if (remainingTimeMillis <= 0) { + throw new IllegalStateException("Cound not register UiAutomationService."); + } + try { + mLock.wait(remainingTimeMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + } + + /** + * Disconnects this service. + * + * @throws IllegalStateException If already disconnected. + */ + public void disconnect() { + if (!isConnected()) { + throw new IllegalStateException("Already disconnected."); + } + + IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( + ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); + + try { + manager.unregisterUiTestAutomationService(mListener); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while unregistering UiTestAutomationService", re); + } + } + + /** + * Gets whether this service is connected. + * + * @return True if connected. + */ + public boolean isConnected() { + return (mConnectionId != AccessibilityInteractionClient.NO_ID); + } + + /** + * Executes a command and waits for a specific accessibility event type up + * to a given timeout. + * + * @param command The command to execute before starting to wait for the event. + * @param predicate Predicate for recognizing the awaited event. + * @param timeoutMillis The max wait time in milliseconds. + */ + public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command, + Predicate<AccessibilityEvent> predicate, long timeoutMillis) + throws TimeoutException, Exception { + synchronized (mLock) { + // Prepare to wait for an event. + mWaitingForEventDelivery = true; + mUnprocessedEventAvailable = false; + if (mLastEvent != null) { + mLastEvent.recycle(); + mLastEvent = null; + } + // Execute the command. + command.run(); + // Wait for the event. + final long startTimeMillis = SystemClock.uptimeMillis(); + while (true) { + // If the expected event is received, that's it. + if ((mUnprocessedEventAvailable && predicate.apply(mLastEvent))) { + mWaitingForEventDelivery = false; + mUnprocessedEventAvailable = false; + mLock.notifyAll(); + return mLastEvent; + } + // Ask for another event. + mWaitingForEventDelivery = true; + mUnprocessedEventAvailable = false; + mLock.notifyAll(); + // Check if timed out and if not wait. + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; + if (remainingTimeMillis <= 0) { + mWaitingForEventDelivery = false; + mUnprocessedEventAvailable = false; + mLock.notifyAll(); + throw new TimeoutException("Expacted event not received within: " + + timeoutMillis + " ms."); + } + try { + mLock.wait(remainingTimeMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + } + + /** + * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active + * window. The search is performed from the root node. + * + * @param accessibilityNodeId A unique view id or virtual descendant id for + * which to search. + * @return The current window scale, where zero means a failure. + */ + public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveWindow( + long accessibilityNodeId) { + return findAccessibilityNodeInfoByAccessibilityId(ACTIVE_WINDOW_ID, accessibilityNodeId); + } + + /** + * Finds an {@link AccessibilityNodeInfo} by accessibility id. + * + * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id or virtual descendant id for + * which to search. + * @return The current window scale, where zero means a failure. + */ + public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId( + int accessibilityWindowId, long accessibilityNodeId) { + // Cache the id to avoid locking + final int connectionId = mConnectionId; + ensureValidConnection(connectionId); + return AccessibilityInteractionClient.getInstance() + .findAccessibilityNodeInfoByAccessibilityId(mConnectionId, + accessibilityWindowId, accessibilityNodeId); + } + + /** + * Finds an {@link AccessibilityNodeInfo} by View id in the active + * window. The search is performed from the root node. + * + * @return The current window scale, where zero means a failure. + */ + public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) { + return findAccessibilityNodeInfoByViewId(ACTIVE_WINDOW_ID, ROOT_NODE_ID, viewId); + } + + /** + * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in + * the window whose id is specified and starts from the node whose accessibility + * id is specified. + * + * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id or virtual descendant id from + * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root. + * @return The current window scale, where zero means a failure. + */ + public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId, + long accessibilityNodeId, int viewId) { + // Cache the id to avoid locking + final int connectionId = mConnectionId; + ensureValidConnection(connectionId); + return AccessibilityInteractionClient.getInstance() + .findAccessibilityNodeInfoByViewId(connectionId, accessibilityWindowId, + accessibilityNodeId, viewId); + } + + /** + * Finds {@link AccessibilityNodeInfo}s by View text in the active + * window. The search is performed from the root node. + * + * @param text The searched text. + * @return The current window scale, where zero means a failure. + */ + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(String text) { + return findAccessibilityNodeInfosByText(ACTIVE_WINDOW_ID, ROOT_NODE_ID, text); + } + + /** + * Finds {@link AccessibilityNodeInfo}s by View text. The match is case + * insensitive containment. The search is performed in the window whose + * id is specified and starts from the node whose accessibility id is + * specified. + * + * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id or virtual descendant id from + * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root. + * @param text The searched text. + * @return The current window scale, where zero means a failure. + */ + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int accessibilityWindowId, + long accessibilityNodeId, String text) { + // Cache the id to avoid locking + final int connectionId = mConnectionId; + ensureValidConnection(connectionId); + return AccessibilityInteractionClient.getInstance() + .findAccessibilityNodeInfosByText(connectionId, accessibilityWindowId, + accessibilityNodeId, text); + } + + /** + * Performs an accessibility action on an {@link AccessibilityNodeInfo} + * in the active window. + * + * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id). + * @param action The action to perform. + * @return Whether the action was performed. + */ + public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action) { + return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action); + } + + /** + * Performs an accessibility action on an {@link AccessibilityNodeInfo}. + * + * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id). + * @param action The action to perform. + * @return Whether the action was performed. + */ + public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, + int action) { + // Cache the id to avoid locking + final int connectionId = mConnectionId; + ensureValidConnection(connectionId); + return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId, + accessibilityWindowId, accessibilityNodeId, action); + } + + /** + * Gets the root {@link AccessibilityNodeInfo} in the active window. + * + * @return The root info. + */ + public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() { + synchronized (mLock) { + if (mLastWindowStateChangeEvent != null) { + return mLastWindowStateChangeEvent.getSource(); + } + } + return null; + } + + private void ensureValidConnection(int connectionId) { + if (connectionId == AccessibilityInteractionClient.NO_ID) { + throw new IllegalStateException("UiAutomationService not connected." + + " Did you call #register()?"); + } + } +} diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index 894f4287f709..634e4d824279 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -960,17 +960,17 @@ public class LayoutTransition { if (anim instanceof ObjectAnimator) { ((ObjectAnimator) anim).setCurrentPlayTime(0); } - if (mListeners != null) { - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator anim) { - currentAppearingAnimations.remove(child); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator anim) { + currentAppearingAnimations.remove(child); + if (mListeners != null) { for (TransitionListener listener : mListeners) { listener.endTransition(LayoutTransition.this, parent, child, APPEARING); } } - }); - } + } + }); currentAppearingAnimations.put(child, anim); anim.start(); } @@ -998,17 +998,19 @@ public class LayoutTransition { anim.setStartDelay(mDisappearingDelay); anim.setDuration(mDisappearingDuration); anim.setTarget(child); - if (mListeners != null) { - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator anim) { - currentDisappearingAnimations.remove(child); + final float preAnimAlpha = child.getAlpha(); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator anim) { + currentDisappearingAnimations.remove(child); + child.setAlpha(preAnimAlpha); + if (mListeners != null) { for (TransitionListener listener : mListeners) { listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); } } - }); - } + } + }); if (anim instanceof ObjectAnimator) { ((ObjectAnimator) anim).setCurrentPlayTime(0); } @@ -1024,18 +1026,25 @@ public class LayoutTransition { * * @param parent The ViewGroup to which the View is being added. * @param child The View being added to the ViewGroup. + * @param changesLayout Whether the removal will cause changes in the layout of other views + * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not + * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. */ - public void addChild(ViewGroup parent, View child) { + private void addChild(ViewGroup parent, View child, boolean changesLayout) { // Want disappearing animations to finish up before proceeding cancel(DISAPPEARING); - // Also, cancel changing animations so that we start fresh ones from current locations - cancel(CHANGE_APPEARING); + if (changesLayout) { + // Also, cancel changing animations so that we start fresh ones from current locations + cancel(CHANGE_APPEARING); + } if (mListeners != null) { for (TransitionListener listener : mListeners) { listener.startTransition(this, parent, child, APPEARING); } } - runChangeTransition(parent, child, APPEARING); + if (changesLayout) { + runChangeTransition(parent, child, APPEARING); + } runAppearingTransition(parent, child); } @@ -1048,8 +1057,31 @@ public class LayoutTransition { * @param parent The ViewGroup to which the View is being added. * @param child The View being added to the ViewGroup. */ + public void addChild(ViewGroup parent, View child) { + addChild(parent, child, true); + } + + /** + * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}. + */ + @Deprecated public void showChild(ViewGroup parent, View child) { - addChild(parent, child); + addChild(parent, child, true); + } + + /** + * This method is called by ViewGroup when a child view is about to be made visible in the + * container. This callback starts the process of a transition; we grab the starting + * values, listen for changes to all of the children of the container, and start appropriate + * animations. + * + * @param parent The ViewGroup in which the View is being made visible. + * @param child The View being made visible. + * @param oldVisibility The previous visibility value of the child View, either + * {@link View#GONE} or {@link View#INVISIBLE}. + */ + public void showChild(ViewGroup parent, View child, int oldVisibility) { + addChild(parent, child, oldVisibility == View.GONE); } /** @@ -1060,18 +1092,25 @@ public class LayoutTransition { * * @param parent The ViewGroup from which the View is being removed. * @param child The View being removed from the ViewGroup. + * @param changesLayout Whether the removal will cause changes in the layout of other views + * in the container. Views becoming INVISIBLE will not cause changes and thus will not + * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. */ - public void removeChild(ViewGroup parent, View child) { + private void removeChild(ViewGroup parent, View child, boolean changesLayout) { // Want appearing animations to finish up before proceeding cancel(APPEARING); - // Also, cancel changing animations so that we start fresh ones from current locations - cancel(CHANGE_DISAPPEARING); + if (changesLayout) { + // Also, cancel changing animations so that we start fresh ones from current locations + cancel(CHANGE_DISAPPEARING); + } if (mListeners != null) { for (TransitionListener listener : mListeners) { listener.startTransition(this, parent, child, DISAPPEARING); } } - runChangeTransition(parent, child, DISAPPEARING); + if (changesLayout) { + runChangeTransition(parent, child, DISAPPEARING); + } runDisappearingTransition(parent, child); } @@ -1084,8 +1123,31 @@ public class LayoutTransition { * @param parent The ViewGroup from which the View is being removed. * @param child The View being removed from the ViewGroup. */ + public void removeChild(ViewGroup parent, View child) { + removeChild(parent, child, true); + } + + /** + * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}. + */ + @Deprecated public void hideChild(ViewGroup parent, View child) { - removeChild(parent, child); + removeChild(parent, child, true); + } + + /** + * This method is called by ViewGroup when a child view is about to be hidden in + * container. This callback starts the process of a transition; we grab the starting + * values, listen for changes to all of the children of the container, and start appropriate + * animations. + * + * @param parent The parent ViewGroup of the View being hidden. + * @param child The View being hidden. + * @param newVisibility The new visibility value of the child View, either + * {@link View#GONE} or {@link View#INVISIBLE}. + */ + public void hideChild(ViewGroup parent, View child, int newVisibility) { + removeChild(parent, child, newVisibility == View.GONE); } /** diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java index 0a96d592b3e6..a79f2a309326 100644 --- a/core/java/android/animation/TimeAnimator.java +++ b/core/java/android/animation/TimeAnimator.java @@ -1,13 +1,11 @@ package android.animation; /** - * This class provides a simple callback mechanism to listeners that is synchronized with other - * animators in the system. There is no duration, interpolation, or object value-setting - * with this Animator. Instead, it is simply started and proceeds to send out events on every - * animation frame to its TimeListener (if set), with information about this animator, - * the total elapsed time, and the time since the last animation frame. - * - * @hide + * This class provides a simple callback mechanism to listeners that is synchronized with all + * other animators in the system. There is no duration, interpolation, or object value-setting + * with this Animator. Instead, it is simply started, after which it proceeds to send out events + * on every animation frame to its TimeListener (if set), with information about this animator, + * the total elapsed time, and the elapsed time since the previous animation frame. */ public class TimeAnimator extends ValueAnimator { @@ -59,10 +57,10 @@ public class TimeAnimator extends ValueAnimator { * Implementors of this interface can set themselves as update listeners * to a <code>TimeAnimator</code> instance to receive callbacks on every animation * frame to receive the total time since the animator started and the delta time - * since the last frame. The first time the listener is called, totalTime and - * deltaTime should both be zero. - * - * @hide + * since the last frame. The first time the listener is called, + * deltaTime will be zero. The same is true for totalTime, unless the animator was + * set to a specific {@link ValueAnimator#setCurrentPlayTime(long) currentPlayTime} + * prior to starting. */ public static interface TimeListener { /** @@ -70,7 +68,8 @@ public class TimeAnimator extends ValueAnimator { * along with information about the elapsed time.</p> * * @param animation The animator sending out the notification. - * @param totalTime The + * @param totalTime The total time elapsed since the animator started, in milliseconds. + * @param deltaTime The time elapsed since the previous frame, in milliseconds. */ void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime); diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index c7a129e3e518..cc1efb9acd98 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -19,6 +19,7 @@ package android.animation; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemProperties; import android.util.AndroidRuntimeException; import android.view.Choreographer; import android.view.animation.AccelerateDecelerateInterpolator; @@ -52,6 +53,7 @@ public class ValueAnimator extends Animator { /** * Internal constants */ + private static float sDurationScale = 1.0f; /** * Messages sent to timing handler: START is sent when an animation first begins. @@ -158,10 +160,12 @@ public class ValueAnimator extends Animator { // // How long the animation should last in ms - private long mDuration = 300; + private long mDuration = (long)(300 * sDurationScale); + private long mUnscaledDuration = 300; // The amount of time in ms to delay starting the animation after start() is called private long mStartDelay = 0; + private long mUnscaledStartDelay = 0; // The number of times the animation will repeat. The default is 0, which means the animation // will play only once @@ -217,6 +221,14 @@ public class ValueAnimator extends Animator { */ public static final int INFINITE = -1; + + /** + * @hide + */ + public static void setDurationScale(float durationScale) { + sDurationScale = durationScale; + } + /** * Creates a new ValueAnimator object. This default constructor is primarily for * use internally; the factory methods which take parameters are more generally @@ -453,7 +465,8 @@ public class ValueAnimator extends Animator { throw new IllegalArgumentException("Animators cannot have negative duration: " + duration); } - mDuration = duration; + mUnscaledDuration = duration; + mDuration = (long)(duration * sDurationScale); return this; } @@ -463,7 +476,7 @@ public class ValueAnimator extends Animator { * @return The length of the animation, in milliseconds. */ public long getDuration() { - return mDuration; + return mUnscaledDuration; } /** @@ -658,7 +671,7 @@ public class ValueAnimator extends Animator { * @return the number of milliseconds to delay running the animation */ public long getStartDelay() { - return mStartDelay; + return mUnscaledStartDelay; } /** @@ -668,7 +681,8 @@ public class ValueAnimator extends Animator { * @param startDelay The amount of the delay, in milliseconds */ public void setStartDelay(long startDelay) { - this.mStartDelay = startDelay; + this.mStartDelay = (long)(startDelay * sDurationScale); + mUnscaledStartDelay = startDelay; } /** diff --git a/core/java/android/annotation/SuppressLint.java b/core/java/android/annotation/SuppressLint.java new file mode 100644 index 000000000000..2d3456b0ea46 --- /dev/null +++ b/core/java/android/annotation/SuppressLint.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 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.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Indicates that Lint should ignore the specified warnings for the annotated element. */ +@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) +@Retention(RetentionPolicy.CLASS) +public @interface SuppressLint { + /** + * The set of warnings (identified by the lint issue id) that should be + * ignored by lint. It is not an error to specify an unrecognized name. + */ + String[] value(); +} diff --git a/core/java/android/annotation/TargetApi.java b/core/java/android/annotation/TargetApi.java new file mode 100644 index 000000000000..ea178903eb49 --- /dev/null +++ b/core/java/android/annotation/TargetApi.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012 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.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Indicates that Lint should treat this type as targeting a given API level, no matter what the + project target is. */ +@Target({TYPE, METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface TargetApi { + /** + * This sets the target api level for the type.. + */ + int value(); +} diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9661b9e0d32b..d98d87b3129a 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -30,6 +30,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; +import android.os.Binder; import android.os.Debug; import android.os.Handler; import android.os.Parcel; @@ -975,7 +976,7 @@ public class ActivityManager { public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) { try { return ActivityManagerNative.getDefault().clearApplicationUserData(packageName, - observer); + observer, Binder.getOrigCallingUser()); } catch (RemoteException e) { return false; } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 7994d7cd3bfb..d80902d734fb 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -91,7 +91,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM try { getDefault().broadcastIntent( null, intent, null, null, Activity.RESULT_OK, null, null, - null /*permission*/, false, true); + null /*permission*/, false, true, Binder.getOrigCallingUser()); } catch (RemoteException ex) { } } @@ -306,9 +306,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM String perm = data.readString(); boolean serialized = data.readInt() != 0; boolean sticky = data.readInt() != 0; + int userId = data.readInt(); int res = broadcastIntent(app, intent, resolvedType, resultTo, resultCode, resultData, resultExtras, perm, - serialized, sticky); + serialized, sticky, userId); reply.writeNoException(); reply.writeInt(res); return true; @@ -320,7 +321,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IBinder b = data.readStrongBinder(); IApplicationThread app = b != null ? ApplicationThreadNative.asInterface(b) : null; Intent intent = Intent.CREATOR.createFromParcel(data); - unbroadcastIntent(app, intent); + int userId = data.readInt(); + unbroadcastIntent(app, intent, userId); reply.writeNoException(); return true; } @@ -900,7 +902,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM String packageName = data.readString(); IPackageDataObserver observer = IPackageDataObserver.Stub.asInterface( data.readStrongBinder()); - boolean res = clearApplicationUserData(packageName, observer); + int userId = data.readInt(); + boolean res = clearApplicationUserData(packageName, observer, userId); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; @@ -1819,7 +1822,7 @@ class ActivityManagerProxy implements IActivityManager Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, boolean serialized, - boolean sticky) throws RemoteException + boolean sticky, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -1834,6 +1837,7 @@ class ActivityManagerProxy implements IActivityManager data.writeString(requiredPermission); data.writeInt(serialized ? 1 : 0); data.writeInt(sticky ? 1 : 0); + data.writeInt(userId); mRemote.transact(BROADCAST_INTENT_TRANSACTION, data, reply, 0); reply.readException(); int res = reply.readInt(); @@ -1841,13 +1845,15 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); return res; } - public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException + public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); intent.writeToParcel(data, 0); + data.writeInt(userId); mRemote.transact(UNBROADCAST_INTENT_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -2651,12 +2657,13 @@ class ActivityManagerProxy implements IActivityManager return res; } public boolean clearApplicationUserData(final String packageName, - final IPackageDataObserver observer) throws RemoteException { + final IPackageDataObserver observer, final int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(packageName); data.writeStrongBinder(observer.asBinder()); + data.writeInt(userId); mRemote.transact(CLEAR_APP_DATA_TRANSACTION, data, reply, 0); reply.readException(); boolean res = reply.readInt() != 0; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 9807b89a2b10..e4cfc9948704 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -47,6 +47,7 @@ import android.net.Proxy; import android.net.ProxyProperties; import android.opengl.GLUtils; import android.os.AsyncTask; +import android.os.Binder; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -60,6 +61,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; +import android.os.UserId; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.EventLog; @@ -132,6 +134,7 @@ public final class ActivityThread { private static final boolean DEBUG_RESULTS = false; private static final boolean DEBUG_BACKUP = true; private static final boolean DEBUG_CONFIGURATION = false; + private static final boolean DEBUG_SERVICE = true; private static final long MIN_TIME_BETWEEN_GCS = 5*1000; private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";"); private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; @@ -635,6 +638,9 @@ public final class ActivityThread { s.intent = intent; s.rebind = rebind; + if (DEBUG_SERVICE) + Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid=" + + Binder.getCallingUid() + " pid=" + Binder.getCallingPid()); queueOrSendMessage(H.BIND_SERVICE, s); } @@ -1592,7 +1598,8 @@ public final class ActivityThread { boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; boolean securityViolation = includeCode && ai.uid != 0 && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null - ? ai.uid != mBoundApplication.appInfo.uid : true); + ? !UserId.isSameApp(ai.uid, mBoundApplication.appInfo.uid) + : true); if ((flags&(Context.CONTEXT_INCLUDE_CODE |Context.CONTEXT_IGNORE_SECURITY)) == Context.CONTEXT_INCLUDE_CODE) { @@ -2294,6 +2301,8 @@ public final class ActivityThread { private void handleBindService(BindServiceData data) { Service s = mServices.get(data.token); + if (DEBUG_SERVICE) + Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind); if (s != null) { try { data.intent.setExtrasClassLoader(s.getClassLoader()); @@ -4413,7 +4422,7 @@ public final class ActivityThread { }); } - public static final ActivityThread systemMain() { + public static ActivityThread systemMain() { HardwareRenderer.disable(true); ActivityThread thread = new ActivityThread(); thread.attach(true); @@ -4454,6 +4463,8 @@ public final class ActivityThread { ActivityThread thread = new ActivityThread(); thread.attach(false); + AsyncTask.init(); + if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 180a442f52c1..fee2beb779ac 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1177,13 +1177,14 @@ final class ApplicationPackageManager extends PackageManager { */ @Override public List<UserInfo> getUsers() { - // TODO: - // Dummy code, always returns just the primary user - ArrayList<UserInfo> users = new ArrayList<UserInfo>(); - UserInfo primary = new UserInfo(0, "Root!", - UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); - users.add(primary); - return users; + try { + return mPM.getUsers(); + } catch (RemoteException re) { + ArrayList<UserInfo> users = new ArrayList<UserInfo>(); + UserInfo primary = new UserInfo(0, "Root!", UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); + users.add(primary); + return users; + } } /** diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 2bf1fb712304..db5113ec905b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -75,6 +75,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserId; import android.os.Vibrator; import android.os.storage.StorageManager; import android.telephony.TelephonyManager; @@ -896,7 +897,8 @@ class ContextImpl extends Context { intent.setAllowFds(false); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, null, false, false); + Activity.RESULT_OK, null, null, null, false, false, + Binder.getOrigCallingUser()); } catch (RemoteException e) { } } @@ -908,7 +910,8 @@ class ContextImpl extends Context { intent.setAllowFds(false); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, receiverPermission, false, false); + Activity.RESULT_OK, null, null, receiverPermission, false, false, + Binder.getOrigCallingUser()); } catch (RemoteException e) { } } @@ -921,7 +924,8 @@ class ContextImpl extends Context { intent.setAllowFds(false); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, receiverPermission, true, false); + Activity.RESULT_OK, null, null, receiverPermission, true, false, + Binder.getOrigCallingUser()); } catch (RemoteException e) { } } @@ -954,7 +958,7 @@ class ContextImpl extends Context { ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermission, - true, false); + true, false, Binder.getOrigCallingUser()); } catch (RemoteException e) { } } @@ -966,7 +970,8 @@ class ContextImpl extends Context { intent.setAllowFds(false); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, null, false, true); + Activity.RESULT_OK, null, null, null, false, true, + Binder.getOrigCallingUser()); } catch (RemoteException e) { } } @@ -999,7 +1004,7 @@ class ContextImpl extends Context { ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, - true, true); + true, true, Binder.getOrigCallingUser()); } catch (RemoteException e) { } } @@ -1014,7 +1019,7 @@ class ContextImpl extends Context { try { intent.setAllowFds(false); ActivityManagerNative.getDefault().unbroadcastIntent( - mMainThread.getApplicationThread(), intent); + mMainThread.getApplicationThread(), intent, Binder.getOrigCallingUser()); } catch (RemoteException e) { } } @@ -1215,8 +1220,7 @@ class ContextImpl extends Context { int pid = Binder.getCallingPid(); if (pid != Process.myPid()) { - return checkPermission(permission, pid, - Binder.getCallingUid()); + return checkPermission(permission, pid, Binder.getCallingUid()); } return PackageManager.PERMISSION_DENIED; } @@ -1384,7 +1388,8 @@ class ContextImpl extends Context { Uri uri, int modeFlags, String message) { enforceForUri( modeFlags, checkCallingUriPermission(uri, modeFlags), - false, Binder.getCallingUid(), uri, message); + false, + Binder.getCallingUid(), uri, message); } public void enforceCallingOrSelfUriPermission( diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 5222d375a439..39817ac2cde8 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -114,8 +114,8 @@ public interface IActivityManager extends IInterface { public int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, - boolean serialized, boolean sticky) throws RemoteException; - public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException; + boolean serialized, boolean sticky, int userId) throws RemoteException; + public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) throws RemoteException; /* oneway */ public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException; public void attachApplication(IApplicationThread app) throws RemoteException; @@ -209,7 +209,7 @@ public interface IActivityManager extends IInterface { int flags) throws RemoteException; public void cancelIntentSender(IIntentSender sender) throws RemoteException; public boolean clearApplicationUserData(final String packageName, - final IPackageDataObserver observer) throws RemoteException; + final IPackageDataObserver observer, int userId) throws RemoteException; public String getPackageForIntentSender(IIntentSender sender) throws RemoteException; public void setProcessLimit(int max) throws RemoteException; diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 0c6baebf26b9..fcbcd8172c47 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -36,6 +36,7 @@ import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.StrictMode; +import android.os.UserId; import android.util.AndroidRuntimeException; import android.util.Slog; import android.view.CompatibilityInfoHolder; @@ -67,6 +68,8 @@ final class ServiceConnectionLeaked extends AndroidRuntimeException { */ public final class LoadedApk { + private static final String TAG = "LoadedApk"; + private final ActivityThread mActivityThread; private final ApplicationInfo mApplicationInfo; final String mPackageName; @@ -113,8 +116,13 @@ public final class LoadedApk { mApplicationInfo = aInfo; mPackageName = aInfo.packageName; mAppDir = aInfo.sourceDir; - mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir + final int myUid = Process.myUid(); + mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir; + if (!UserId.isSameUser(aInfo.uid, myUid)) { + aInfo.dataDir = PackageManager.getDataDirForUser(UserId.getUserId(myUid), + mPackageName); + } mSharedLibraries = aInfo.sharedLibraryFiles; mDataDir = aInfo.dataDir; mDataDirFile = mDataDir != null ? new File(mDataDir) : null; diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index d83d2e6a80d4..ff71ee77c131 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -17,6 +17,7 @@ package android.app; import android.content.Loader; +import android.content.Loader.OnLoadCanceledListener; import android.os.Bundle; import android.util.DebugUtils; import android.util.Log; @@ -219,7 +220,8 @@ class LoaderManagerImpl extends LoaderManager { boolean mCreatingLoader; - final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> { + final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>, + Loader.OnLoadCanceledListener<Object> { final int mId; final Bundle mArgs; LoaderManager.LoaderCallbacks<Object> mCallbacks; @@ -271,6 +273,7 @@ class LoaderManagerImpl extends LoaderManager { } if (!mListenerRegistered) { mLoader.registerListener(mId, this); + mLoader.registerOnLoadCanceledListener(this); mListenerRegistered = true; } mLoader.startLoading(); @@ -329,11 +332,21 @@ class LoaderManagerImpl extends LoaderManager { // Let the loader know we're done with it mListenerRegistered = false; mLoader.unregisterListener(this); + mLoader.unregisterOnLoadCanceledListener(this); mLoader.stopLoading(); } } } - + + void cancel() { + if (DEBUG) Log.v(TAG, " Canceling: " + this); + if (mStarted && mLoader != null && mListenerRegistered) { + if (!mLoader.cancelLoad()) { + onLoadCanceled(mLoader); + } + } + } + void destroy() { if (DEBUG) Log.v(TAG, " Destroying: " + this); mDestroyed = true; @@ -361,6 +374,7 @@ class LoaderManagerImpl extends LoaderManager { if (mListenerRegistered) { mListenerRegistered = false; mLoader.unregisterListener(this); + mLoader.unregisterOnLoadCanceledListener(this); } mLoader.reset(); } @@ -368,8 +382,38 @@ class LoaderManagerImpl extends LoaderManager { mPendingLoader.destroy(); } } - - @Override public void onLoadComplete(Loader<Object> loader, Object data) { + + @Override + public void onLoadCanceled(Loader<Object> loader) { + if (DEBUG) Log.v(TAG, "onLoadCanceled: " + this); + + if (mDestroyed) { + if (DEBUG) Log.v(TAG, " Ignoring load canceled -- destroyed"); + return; + } + + if (mLoaders.get(mId) != this) { + // This cancellation message is not coming from the current active loader. + // We don't care about it. + if (DEBUG) Log.v(TAG, " Ignoring load canceled -- not active"); + return; + } + + LoaderInfo pending = mPendingLoader; + if (pending != null) { + // There is a new request pending and we were just + // waiting for the old one to cancel or complete before starting + // it. So now it is time, switch over to the new loader. + if (DEBUG) Log.v(TAG, " Switching to pending loader: " + pending); + mPendingLoader = null; + mLoaders.put(mId, null); + destroy(); + installLoader(pending); + } + } + + @Override + public void onLoadComplete(Loader<Object> loader, Object data) { if (DEBUG) Log.v(TAG, "onLoadComplete: " + this); if (mDestroyed) { @@ -632,7 +676,9 @@ class LoaderManagerImpl extends LoaderManager { } else { // Now we have three active loaders... we'll queue // up this request to be processed once one of the other loaders - // finishes. + // finishes or is canceled. + if (DEBUG) Log.v(TAG, " Current loader is running; attempting to cancel"); + info.cancel(); if (info.mPendingLoader != null) { if (DEBUG) Log.v(TAG, " Removing pending loader: " + info.mPendingLoader); info.mPendingLoader.destroy(); diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java index 944ca6b9f33c..da5195292f78 100644 --- a/core/java/android/content/AsyncTaskLoader.java +++ b/core/java/android/content/AsyncTaskLoader.java @@ -53,19 +53,33 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { static final boolean DEBUG = false; final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable { + private final CountDownLatch mDone = new CountDownLatch(1); - D result; + // Set to true to indicate that the task has been posted to a handler for + // execution at a later time. Used to throttle updates. boolean waiting; - private CountDownLatch done = new CountDownLatch(1); - /* Runs on a worker thread */ @Override protected D doInBackground(Void... params) { if (DEBUG) Slog.v(TAG, this + " >>> doInBackground"); - result = AsyncTaskLoader.this.onLoadInBackground(); - if (DEBUG) Slog.v(TAG, this + " <<< doInBackground"); - return result; + try { + D data = AsyncTaskLoader.this.onLoadInBackground(); + if (DEBUG) Slog.v(TAG, this + " <<< doInBackground"); + return data; + } catch (OperationCanceledException ex) { + if (!isCancelled()) { + // onLoadInBackground threw a canceled exception spuriously. + // This is problematic because it means that the LoaderManager did not + // cancel the Loader itself and still expects to receive a result. + // Additionally, the Loader's own state will not have been updated to + // reflect the fact that the task was being canceled. + // So we treat this case as an unhandled exception. + throw ex; + } + if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)"); + return null; + } } /* Runs on the UI thread */ @@ -75,25 +89,37 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { try { AsyncTaskLoader.this.dispatchOnLoadComplete(this, data); } finally { - done.countDown(); + mDone.countDown(); } } + /* Runs on the UI thread */ @Override - protected void onCancelled() { + protected void onCancelled(D data) { if (DEBUG) Slog.v(TAG, this + " onCancelled"); try { - AsyncTaskLoader.this.dispatchOnCancelled(this, result); + AsyncTaskLoader.this.dispatchOnCancelled(this, data); } finally { - done.countDown(); + mDone.countDown(); } } + /* Runs on the UI thread, when the waiting task is posted to a handler. + * This method is only executed when task execution was deferred (waiting was true). */ @Override public void run() { waiting = false; AsyncTaskLoader.this.executePendingTask(); } + + /* Used for testing purposes to wait for the task to complete. */ + public void waitForLoader() { + try { + mDone.await(); + } catch (InterruptedException e) { + // Ignore + } + } } volatile LoadTask mTask; @@ -109,7 +135,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { /** * Set amount to throttle updates by. This is the minimum time from - * when the last {@link #onLoadInBackground()} call has completed until + * when the last {@link #loadInBackground()} call has completed until * a new load is scheduled. * * @param delayMS Amount of delay, in milliseconds. @@ -130,24 +156,9 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { executePendingTask(); } - /** - * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)} - * for more info. Must be called on the main thread of the process. - * - * <p>Cancelling is not an immediate operation, since the load is performed - * in a background thread. If there is currently a load in progress, this - * method requests that the load be cancelled, and notes this is the case; - * once the background thread has completed its work its remaining state - * will be cleared. If another load request comes in during this time, - * it will be held until the cancelled load is complete. - * - * @return Returns <tt>false</tt> if the task could not be cancelled, - * typically because it has already completed normally, or - * because {@link #startLoading()} hasn't been called; returns - * <tt>true</tt> otherwise. - */ - public boolean cancelLoad() { - if (DEBUG) Slog.v(TAG, "cancelLoad: mTask=" + mTask); + @Override + protected boolean onCancelLoad() { + if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask); if (mTask != null) { if (mCancellingTask != null) { // There was a pending task already waiting for a previous @@ -173,7 +184,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled); if (cancelled) { mCancellingTask = mTask; - onCancelLoadInBackground(); + cancelLoadInBackground(); } mTask = null; return cancelled; @@ -184,7 +195,10 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { /** * Called if the task was canceled before it was completed. Gives the class a chance - * to properly dispose of the result. + * to clean up post-cancellation and to properly dispose of the result. + * + * @param data The value that was returned by {@link #loadInBackground}, or null + * if the task threw {@link OperationCanceledException}. */ public void onCanceled(D data) { } @@ -218,6 +232,8 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!"); mLastLoadCompleteTime = SystemClock.uptimeMillis(); mCancellingTask = null; + if (DEBUG) Slog.v(TAG, "Delivering cancellation"); + deliverCancellation(); executePendingTask(); } } @@ -240,38 +256,72 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { } /** + * Called on a worker thread to perform the actual load and to return + * the result of the load operation. + * + * Implementations should not deliver the result directly, but should return them + * from this method, which will eventually end up calling {@link #deliverResult} on + * the UI thread. If implementations need to process the results on the UI thread + * they may override {@link #deliverResult} and do so there. + * + * To support cancellation, this method should periodically check the value of + * {@link #isLoadInBackgroundCanceled} and terminate when it returns true. + * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load + * directly instead of polling {@link #isLoadInBackgroundCanceled}. + * + * When the load is canceled, this method may either return normally or throw + * {@link OperationCanceledException}. In either case, the {@link Loader} will + * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the + * result object, if any. + * + * @return The result of the load operation. + * + * @throws OperationCanceledException if the load is canceled during execution. + * + * @see #isLoadInBackgroundCanceled + * @see #cancelLoadInBackground + * @see #onCanceled */ public abstract D loadInBackground(); /** - * Called on a worker thread to perform the actual load. Implementations should not deliver the - * result directly, but should return them from this method, which will eventually end up - * calling {@link #deliverResult} on the UI thread. If implementations need to process - * the results on the UI thread they may override {@link #deliverResult} and do so - * there. + * Calls {@link #loadInBackground()}. + * + * This method is reserved for use by the loader framework. + * Subclasses should override {@link #loadInBackground} instead of this method. + * + * @return The result of the load operation. * - * @return Implementations must return the result of their load operation. + * @throws OperationCanceledException if the load is canceled during execution. + * + * @see #loadInBackground */ protected D onLoadInBackground() { return loadInBackground(); } /** - * Override this method to try to abort the computation currently taking - * place on a background thread. + * Called on the main thread to abort a load in progress. + * + * Override this method to abort the current invocation of {@link #loadInBackground} + * that is running in the background on a worker thread. * - * Note that when this method is called, it is possible that {@link #loadInBackground} - * has not started yet or has already completed. + * This method should do nothing if {@link #loadInBackground} has not started + * running or if it has already finished. + * + * @see #loadInBackground */ - protected void onCancelLoadInBackground() { + public void cancelLoadInBackground() { } /** - * Returns true if the current execution of {@link #loadInBackground()} is being canceled. + * Returns true if the current invocation of {@link #loadInBackground} is being canceled. + * + * @return True if the current invocation of {@link #loadInBackground} is being canceled. * - * @return True if the current execution of {@link #loadInBackground()} is being canceled. + * @see #loadInBackground */ - protected boolean isLoadInBackgroundCanceled() { + public boolean isLoadInBackgroundCanceled() { return mCancellingTask != null; } @@ -288,11 +338,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> { public void waitForLoader() { LoadTask task = mTask; if (task != null) { - try { - task.done.await(); - } catch (InterruptedException e) { - // Ignore - } + task.waitForLoader(); } } diff --git a/core/java/android/content/CancelationSignal.java b/core/java/android/content/CancellationSignal.java index 58cf59d485be..2dbbe54c417b 100644 --- a/core/java/android/content/CancelationSignal.java +++ b/core/java/android/content/CancellationSignal.java @@ -21,15 +21,15 @@ import android.os.RemoteException; /** * Provides the ability to cancel an operation in progress. */ -public final class CancelationSignal { +public final class CancellationSignal { private boolean mIsCanceled; private OnCancelListener mOnCancelListener; - private ICancelationSignal mRemote; + private ICancellationSignal mRemote; /** - * Creates a cancelation signal, initially not canceled. + * Creates a cancellation signal, initially not canceled. */ - public CancelationSignal() { + public CancellationSignal() { } /** @@ -55,7 +55,7 @@ public final class CancelationSignal { } /** - * Cancels the operation and signals the cancelation listener. + * Cancels the operation and signals the cancellation listener. * If the operation has not yet started, then it will be canceled as soon as it does. */ public void cancel() { @@ -76,17 +76,23 @@ public final class CancelationSignal { } /** - * Sets the cancelation listener to be called when canceled. - * If {@link CancelationSignal#cancel} has already been called, then the provided + * Sets the cancellation listener to be called when canceled. + * + * This method is intended to be used by the recipient of a cancellation signal + * such as a database or a content provider to handle cancellation requests + * while performing a long-running operation. This method is not intended to be + * used by applications themselves. + * + * If {@link CancellationSignal#cancel} has already been called, then the provided * listener is invoked immediately. * - * The listener is called while holding the cancelation signal's lock which is + * The listener is called while holding the cancellation signal's lock which is * also held while registering or unregistering the listener. Because of the lock, * it is not possible for the listener to run after it has been unregistered. - * This design choice makes it easier for clients of {@link CancelationSignal} to + * This design choice makes it easier for clients of {@link CancellationSignal} to * prevent race conditions related to listener registration and unregistration. * - * @param listener The cancelation listener, or null to remove the current listener. + * @param listener The cancellation listener, or null to remove the current listener. */ public void setOnCancelListener(OnCancelListener listener) { synchronized (this) { @@ -104,7 +110,7 @@ public final class CancelationSignal { * * @hide */ - public void setRemote(ICancelationSignal remote) { + public void setRemote(ICancellationSignal remote) { synchronized (this) { mRemote = remote; if (mIsCanceled && remote != null) { @@ -118,47 +124,47 @@ public final class CancelationSignal { /** * Creates a transport that can be returned back to the caller of - * a Binder function and subsequently used to dispatch a cancelation signal. + * a Binder function and subsequently used to dispatch a cancellation signal. * - * @return The new cancelation signal transport. + * @return The new cancellation signal transport. * * @hide */ - public static ICancelationSignal createTransport() { + public static ICancellationSignal createTransport() { return new Transport(); } /** - * Given a locally created transport, returns its associated cancelation signal. + * Given a locally created transport, returns its associated cancellation signal. * * @param transport The locally created transport, or null if none. - * @return The associated cancelation signal, or null if none. + * @return The associated cancellation signal, or null if none. * * @hide */ - public static CancelationSignal fromTransport(ICancelationSignal transport) { + public static CancellationSignal fromTransport(ICancellationSignal transport) { if (transport instanceof Transport) { - return ((Transport)transport).mCancelationSignal; + return ((Transport)transport).mCancellationSignal; } return null; } /** - * Listens for cancelation. + * Listens for cancellation. */ public interface OnCancelListener { /** - * Called when {@link CancelationSignal#cancel} is invoked. + * Called when {@link CancellationSignal#cancel} is invoked. */ void onCancel(); } - private static final class Transport extends ICancelationSignal.Stub { - final CancelationSignal mCancelationSignal = new CancelationSignal(); + private static final class Transport extends ICancellationSignal.Stub { + final CancellationSignal mCancellationSignal = new CancellationSignal(); @Override public void cancel() throws RemoteException { - mCancelationSignal.cancel(); + mCancellationSignal.cancel(); } } } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index adbeb6a82b1e..12e3ccf883b6 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -178,10 +178,10 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, - ICancelationSignal cancelationSignal) { + ICancellationSignal cancellationSignal) { enforceReadPermission(uri); return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder, - CancelationSignal.fromTransport(cancelationSignal)); + CancellationSignal.fromTransport(cancellationSignal)); } @Override @@ -263,8 +263,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } @Override - public ICancelationSignal createCancelationSignal() throws RemoteException { - return CancelationSignal.createTransport(); + public ICancellationSignal createCancellationSignal() throws RemoteException { + return CancellationSignal.createTransport(); } private void enforceReadPermission(Uri uri) { @@ -557,7 +557,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { String selection, String[] selectionArgs, String sortOrder); /** - * Implement this to handle query requests from clients with support for cancelation. + * Implement this to handle query requests from clients with support for cancellation. * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes * and Threads</a>. @@ -597,9 +597,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return c;</pre> * <p> * If you implement this method then you must also implement the version of - * {@link #query(Uri, String[], String, String[], String)} that does not take a cancelation - * provider to ensure correct operation on older versions of the Android Framework in - * which the cancelation signal overload was not available. + * {@link #query(Uri, String[], String, String[], String)} that does not take a cancellation + * signal to ensure correct operation on older versions of the Android Framework in + * which the cancellation signal overload was not available. * * @param uri The URI to query. This will be the full URI sent by the client; * if the client is requesting a specific record, the URI will end in a record number @@ -614,14 +614,14 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * The values will be bound as Strings. * @param sortOrder How the rows in the cursor should be sorted. * If null then the provider is free to define the sort order. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. * @return a Cursor or null. */ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { return query(uri, projection, selection, selectionArgs, sortOrder); } diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 9a1fa652a42c..3ac5e0717a9b 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -52,15 +52,15 @@ public class ContentProviderClient { /** See {@link ContentProvider#query ContentProvider.query} */ public Cursor query(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder, CancelationSignal cancelationSignal) + String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) throws RemoteException { - ICancelationSignal remoteCancelationSignal = null; - if (cancelationSignal != null) { - remoteCancelationSignal = mContentProvider.createCancelationSignal(); - cancelationSignal.setRemote(remoteCancelationSignal); + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + remoteCancellationSignal = mContentProvider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); } return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder, - remoteCancelationSignal); + remoteCancellationSignal); } /** See {@link ContentProvider#getType ContentProvider.getType} */ diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index e0e277aa4a59..eb83dbc6e549 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -105,11 +105,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr String sortOrder = data.readString(); IContentObserver observer = IContentObserver.Stub.asInterface( data.readStrongBinder()); - ICancelationSignal cancelationSignal = ICancelationSignal.Stub.asInterface( + ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder, - cancelationSignal); + cancellationSignal); if (cursor != null) { CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor( cursor, observer, getProviderName()); @@ -300,9 +300,9 @@ abstract public class ContentProviderNative extends Binder implements IContentPr { data.enforceInterface(IContentProvider.descriptor); - ICancelationSignal cancelationSignal = createCancelationSignal(); + ICancellationSignal cancellationSignal = createCancellationSignal(); reply.writeNoException(); - reply.writeStrongBinder(cancelationSignal.asBinder()); + reply.writeStrongBinder(cancellationSignal.asBinder()); return true; } } @@ -334,7 +334,7 @@ final class ContentProviderProxy implements IContentProvider } public Cursor query(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder, ICancelationSignal cancelationSignal) + String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal) throws RemoteException { BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); Parcel data = Parcel.obtain(); @@ -363,7 +363,7 @@ final class ContentProviderProxy implements IContentProvider } data.writeString(sortOrder); data.writeStrongBinder(adaptor.getObserver().asBinder()); - data.writeStrongBinder(cancelationSignal != null ? cancelationSignal.asBinder() : null); + data.writeStrongBinder(cancellationSignal != null ? cancellationSignal.asBinder() : null); mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); @@ -632,7 +632,7 @@ final class ContentProviderProxy implements IContentProvider } } - public ICancelationSignal createCancelationSignal() throws RemoteException { + public ICancellationSignal createCancellationSignal() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { @@ -642,9 +642,9 @@ final class ContentProviderProxy implements IContentProvider data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); - ICancelationSignal cancelationSignal = ICancelationSignal.Stub.asInterface( + ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface( reply.readStrongBinder()); - return cancelationSignal; + return cancellationSignal; } finally { data.recycle(); reply.recycle(); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index e79475a38218..96a65da4765f 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -335,7 +335,7 @@ public abstract class ContentResolver { * @param sortOrder How to order the rows, formatted as an SQL ORDER BY * clause (excluding the ORDER BY itself). Passing null will use the * default sort order, which may be unordered. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. * @return A Cursor object, which is positioned before the first entry, or null @@ -343,7 +343,7 @@ public abstract class ContentResolver { */ public final Cursor query(final Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { IContentProvider provider = acquireProvider(uri); if (provider == null) { return null; @@ -351,14 +351,14 @@ public abstract class ContentResolver { try { long startTime = SystemClock.uptimeMillis(); - ICancelationSignal remoteCancelationSignal = null; - if (cancelationSignal != null) { - cancelationSignal.throwIfCanceled(); - remoteCancelationSignal = provider.createCancelationSignal(); - cancelationSignal.setRemote(remoteCancelationSignal); + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + remoteCancellationSignal = provider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); } Cursor qCursor = provider.query(uri, projection, - selection, selectionArgs, sortOrder, remoteCancelationSignal); + selection, selectionArgs, sortOrder, remoteCancellationSignal); if (qCursor == null) { releaseProvider(provider); return null; diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index 6e4aca852c93..aed3728b2203 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -48,7 +48,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { String mSortOrder; Cursor mCursor; - CancelationSignal mCancelationSignal; + CancellationSignal mCancellationSignal; /* Runs on a worker thread */ @Override @@ -57,11 +57,11 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { if (isLoadInBackgroundCanceled()) { throw new OperationCanceledException(); } - mCancelationSignal = new CancelationSignal(); + mCancellationSignal = new CancellationSignal(); } try { Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection, - mSelectionArgs, mSortOrder, mCancelationSignal); + mSelectionArgs, mSortOrder, mCancellationSignal); if (cursor != null) { // Ensure the cursor window is filled cursor.getCount(); @@ -70,18 +70,18 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { return cursor; } finally { synchronized (this) { - mCancelationSignal = null; + mCancellationSignal = null; } } } @Override - protected void onCancelLoadInBackground() { - super.onCancelLoadInBackground(); + public void cancelLoadInBackground() { + super.cancelLoadInBackground(); synchronized (this) { - if (mCancelationSignal != null) { - mCancelationSignal.cancel(); + if (mCancellationSignal != null) { + mCancellationSignal.cancel(); } } } diff --git a/core/java/android/content/ICancelationSignal.aidl b/core/java/android/content/ICancellationSignal.aidl index 3f5a24d82b54..cf1c5d3a892e 100644 --- a/core/java/android/content/ICancelationSignal.aidl +++ b/core/java/android/content/ICancellationSignal.aidl @@ -19,6 +19,6 @@ package android.content; /** * @hide */ -interface ICancelationSignal { +interface ICancellationSignal { oneway void cancel(); } diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index f52157fb8aa8..16478b749445 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -34,7 +34,7 @@ import java.util.ArrayList; */ public interface IContentProvider extends IInterface { public Cursor query(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder, ICancelationSignal cancelationSignal) + String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal) throws RemoteException; public String getType(Uri url) throws RemoteException; public Uri insert(Uri url, ContentValues initialValues) @@ -51,7 +51,7 @@ public interface IContentProvider extends IInterface { public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException; public Bundle call(String method, String arg, Bundle extras) throws RemoteException; - public ICancelationSignal createCancelationSignal() throws RemoteException; + public ICancellationSignal createCancellationSignal() throws RemoteException; // Data interchange. public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index fbc1b2b7eca1..ab62c446e57a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2957,6 +2957,13 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_RECEIVER_REPLACE_PENDING = 0x20000000; /** + * If set, when sending a broadcast the recipient is allowed to run at + * foreground priority, with a shorter timeout interval. During normal + * broadcasts the receivers are not automatically hoisted out of the + * background priority class. + */ + public static final int FLAG_RECEIVER_FOREGROUND = 0x10000000; + /** * If set, when sending a broadcast <i>before boot has completed</i> only * registered receivers will be called -- no BroadcastReceiver components * will be launched. Sticky intent state will be recorded properly even @@ -2969,14 +2976,14 @@ public class Intent implements Parcelable, Cloneable { * * @hide */ - public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x10000000; + public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x08000000; /** * Set when this broadcast is for a boot upgrade, a special mode that * allows the broadcast to be sent before the system is ready and launches * the app process with no providers running in it. * @hide */ - public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x08000000; + public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x04000000; /** * @hide Flags that can't be changed with PendingIntent. diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java index ac056829567f..30524148d689 100644 --- a/core/java/android/content/Loader.java +++ b/core/java/android/content/Loader.java @@ -52,6 +52,7 @@ import java.io.PrintWriter; public class Loader<D> { int mId; OnLoadCompleteListener<D> mListener; + OnLoadCanceledListener<D> mOnLoadCanceledListener; Context mContext; boolean mStarted = false; boolean mAbandoned = false; @@ -100,6 +101,23 @@ public class Loader<D> { } /** + * Interface that is implemented to discover when a Loader has been canceled + * before it finished loading its data. You do not normally need to implement + * this yourself; it is used in the implementation of {@link android.app.LoaderManager} + * to find out when a Loader it is managing has been canceled so that it + * can schedule the next Loader. This interface should only be used if a + * Loader is not being used in conjunction with LoaderManager. + */ + public interface OnLoadCanceledListener<D> { + /** + * Called on the thread that created the Loader when the load is canceled. + * + * @param loader the loader that canceled the load + */ + public void onLoadCanceled(Loader<D> loader); + } + + /** * Stores away the application context associated with context. * Since Loaders can be used across multiple activities it's dangerous to * store the context directly; always use {@link #getContext()} to retrieve @@ -127,6 +145,18 @@ public class Loader<D> { } /** + * Informs the registered {@link OnLoadCanceledListener} that the load has been canceled. + * Should only be called by subclasses. + * + * Must be called from the process's main thread. + */ + public void deliverCancellation() { + if (mOnLoadCanceledListener != null) { + mOnLoadCanceledListener.onLoadCanceled(this); + } + } + + /** * @return an application context retrieved from the Context passed to the constructor. */ public Context getContext() { @@ -171,6 +201,40 @@ public class Loader<D> { } /** + * Registers a listener that will receive callbacks when a load is canceled. + * The callback will be called on the process's main thread so it's safe to + * pass the results to widgets. + * + * Must be called from the process's main thread. + * + * @param listener The listener to register. + */ + public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) { + if (mOnLoadCanceledListener != null) { + throw new IllegalStateException("There is already a listener registered"); + } + mOnLoadCanceledListener = listener; + } + + /** + * Unregisters a listener that was previously added with + * {@link #registerOnLoadCanceledListener}. + * + * Must be called from the process's main thread. + * + * @param listener The listener to unregister. + */ + public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) { + if (mOnLoadCanceledListener == null) { + throw new IllegalStateException("No listener register"); + } + if (mOnLoadCanceledListener != listener) { + throw new IllegalArgumentException("Attempting to unregister the wrong listener"); + } + mOnLoadCanceledListener = null; + } + + /** * Return whether this load has been started. That is, its {@link #startLoading()} * has been called and no calls to {@link #stopLoading()} or * {@link #reset()} have yet been made. @@ -234,6 +298,43 @@ public class Loader<D> { } /** + * Attempt to cancel the current load task. + * Must be called on the main thread of the process. + * + * <p>Cancellation is not an immediate operation, since the load is performed + * in a background thread. If there is currently a load in progress, this + * method requests that the load be canceled, and notes this is the case; + * once the background thread has completed its work its remaining state + * will be cleared. If another load request comes in during this time, + * it will be held until the canceled load is complete. + * + * @return Returns <tt>false</tt> if the task could not be canceled, + * typically because it has already completed normally, or + * because {@link #startLoading()} hasn't been called; returns + * <tt>true</tt> otherwise. When <tt>true</tt> is returned, the task + * is still running and the {@link OnLoadCanceledListener} will be called + * when the task completes. + */ + public boolean cancelLoad() { + return onCancelLoad(); + } + + /** + * Subclasses must implement this to take care of requests to {@link #cancelLoad()}. + * This will always be called from the process's main thread. + * + * @return Returns <tt>false</tt> if the task could not be canceled, + * typically because it has already completed normally, or + * because {@link #startLoading()} hasn't been called; returns + * <tt>true</tt> otherwise. When <tt>true</tt> is returned, the task + * is still running and the {@link OnLoadCanceledListener} will be called + * when the task completes. + */ + protected boolean onCancelLoad() { + return false; + } + + /** * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously * loaded data set and load a new one. This simply calls through to the * implementation's {@link #onForceLoad()}. You generally should only call this diff --git a/core/java/android/content/OperationCanceledException.java b/core/java/android/content/OperationCanceledException.java index 24afcfa3ff67..d783a076d90b 100644 --- a/core/java/android/content/OperationCanceledException.java +++ b/core/java/android/content/OperationCanceledException.java @@ -19,7 +19,7 @@ package android.content; /** * An exception type that is thrown when an operation in progress is canceled. * - * @see CancelationSignal + * @see CancellationSignal */ public class OperationCanceledException extends RuntimeException { public OperationCanceledException() { diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index decb974bded8..bb35c291ace2 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -364,4 +364,6 @@ interface IPackageManager { VerifierDeviceIdentity getVerifierDeviceIdentity(); boolean isFirstBoot(); + + List<UserInfo> getUsers(); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8541748ddbed..26a9181c2f0d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -28,6 +28,7 @@ import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Environment; import android.util.AndroidException; import android.util.DisplayMetrics; @@ -753,13 +754,6 @@ public abstract class PackageManager { public static final int VERIFICATION_REJECT = -1; /** - * 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. @@ -2615,39 +2609,6 @@ public abstract class PackageManager { 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; - } - - /** * Returns the device identity that verifiers can use to associate their * scheme to a particular device. This should not be used by anything other * than a package verifier. @@ -2656,4 +2617,17 @@ public abstract class PackageManager { * @hide */ public abstract VerifierDeviceIdentity getVerifierDeviceIdentity(); + + /** + * Returns the data directory for a particular user and package, given the uid of the package. + * @param uid uid of the package, including the userId and appId + * @param packageName name of the package + * @return the user-specific data directory for the package + * @hide + */ + public static String getDataDirForUser(int userId, String packageName) { + // TODO: This should be shared with Installer's knowledge of user directory + return Environment.getDataDirectory().toString() + "/user/" + userId + + "/" + packageName; + } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e593d5bac8d1..faee873a5660 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -24,18 +24,17 @@ 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; +import android.os.UserId; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; import android.util.TypedValue; -import com.android.internal.util.XmlUtils; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedInputStream; import java.io.File; @@ -59,6 +58,11 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + /** * Package archive parsing * @@ -209,6 +213,8 @@ public class PackageParser { public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime) { + final int userId = Binder.getOrigCallingUser(); + PackageInfo pi = new PackageInfo(); pi.packageName = p.packageName; pi.versionCode = p.mVersionCode; @@ -250,7 +256,8 @@ public class PackageParser { final Activity activity = p.activities.get(i); if (activity.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags); + pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags, + userId); } } } @@ -271,7 +278,7 @@ public class PackageParser { final Activity activity = p.receivers.get(i); if (activity.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags); + pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags, userId); } } } @@ -292,7 +299,7 @@ public class PackageParser { final Service service = p.services.get(i); if (service.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.services[j++] = generateServiceInfo(p.services.get(i), flags); + pi.services[j++] = generateServiceInfo(p.services.get(i), flags, userId); } } } @@ -313,7 +320,7 @@ public class PackageParser { final Provider provider = p.providers.get(i); if (provider.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags); + pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, userId); } } } @@ -3241,8 +3248,12 @@ public class PackageParser { } public static ApplicationInfo generateApplicationInfo(Package p, int flags) { + return generateApplicationInfo(p, flags, UserId.getUserId(Binder.getCallingUid())); + } + + public static ApplicationInfo generateApplicationInfo(Package p, int flags, int userId) { if (p == null) return null; - if (!copyNeeded(flags, p, null)) { + if (!copyNeeded(flags, p, null) && userId == 0) { // CompatibilityMode is global state. It's safe to modify the instance // of the package. if (!sCompatibilityModeEnabled) { @@ -3258,6 +3269,10 @@ public class PackageParser { // Make shallow copy so we can store the metadata/libraries safely ApplicationInfo ai = new ApplicationInfo(p.applicationInfo); + if (userId != 0) { + ai.uid = UserId.getUid(userId, ai.uid); + ai.dataDir = PackageManager.getDataDirForUser(userId, ai.packageName); + } if ((flags & PackageManager.GET_META_DATA) != 0) { ai.metaData = p.mAppMetaData; } @@ -3325,16 +3340,15 @@ public class PackageParser { } } - public static final ActivityInfo generateActivityInfo(Activity a, - int flags) { + public static final ActivityInfo generateActivityInfo(Activity a, int flags, int userId) { if (a == null) return null; - if (!copyNeeded(flags, a.owner, a.metaData)) { + if (!copyNeeded(flags, a.owner, a.metaData) && userId == 0) { return a.info; } // Make shallow copies so we can store the metadata safely ActivityInfo ai = new ActivityInfo(a.info); ai.metaData = a.metaData; - ai.applicationInfo = generateApplicationInfo(a.owner, flags); + ai.applicationInfo = generateApplicationInfo(a.owner, flags, userId); return ai; } @@ -3359,15 +3373,15 @@ public class PackageParser { } } - public static final ServiceInfo generateServiceInfo(Service s, int flags) { + public static final ServiceInfo generateServiceInfo(Service s, int flags, int userId) { if (s == null) return null; - if (!copyNeeded(flags, s.owner, s.metaData)) { + if (!copyNeeded(flags, s.owner, s.metaData) && userId == 0) { return s.info; } // Make shallow copies so we can store the metadata safely ServiceInfo si = new ServiceInfo(s.info); si.metaData = s.metaData; - si.applicationInfo = generateApplicationInfo(s.owner, flags); + si.applicationInfo = generateApplicationInfo(s.owner, flags, userId); return si; } @@ -3400,12 +3414,12 @@ public class PackageParser { } } - public static final ProviderInfo generateProviderInfo(Provider p, - int flags) { + public static final ProviderInfo generateProviderInfo(Provider p, int flags, int userId) { if (p == null) return null; if (!copyNeeded(flags, p.owner, p.metaData) && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0 - || p.info.uriPermissionPatterns == null)) { + || p.info.uriPermissionPatterns == null) + && userId == 0) { return p.info; } // Make shallow copies so we can store the metadata safely @@ -3414,7 +3428,7 @@ public class PackageParser { if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) { pi.uriPermissionPatterns = null; } - pi.applicationInfo = generateApplicationInfo(p.owner, flags); + pi.applicationInfo = generateApplicationInfo(p.owner, flags, userId); return pi; } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 601566841e65..e04b2f75f27a 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -347,6 +347,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration sb.append(" (no locale)"); } switch (textLayoutDirection) { + case LocaleUtil.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE: /* ltr not interesting */ break; case LocaleUtil.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE: sb.append(" rtl"); break; default: sb.append(" layoutdir="); sb.append(textLayoutDirection); break; } diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index b69d9bfa0706..0022118430f6 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -21,6 +21,7 @@ import org.apache.commons.codec.binary.Hex; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; +import android.content.OperationCanceledException; import android.database.sqlite.SQLiteAbortException; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; @@ -107,6 +108,9 @@ public class DatabaseUtils { code = 9; } else if (e instanceof OperationApplicationException) { code = 10; + } else if (e instanceof OperationCanceledException) { + code = 11; + logException = false; } else { reply.writeException(e); Log.e(TAG, "Writing exception to parcel", e); @@ -178,6 +182,8 @@ public class DatabaseUtils { throw new SQLiteDiskIOException(msg); case 9: throw new SQLiteException(msg); + case 11: + throw new OperationCanceledException(msg); default: reply.readException(code, msg); } diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 710bd5333b45..b5cef81ad83c 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -19,7 +19,7 @@ package android.database.sqlite; import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; -import android.content.CancelationSignal; +import android.content.CancellationSignal; import android.content.OperationCanceledException; import android.database.Cursor; import android.database.CursorWindow; @@ -84,7 +84,7 @@ import java.util.regex.Pattern; * * @hide */ -public final class SQLiteConnection implements CancelationSignal.OnCancelListener { +public final class SQLiteConnection implements CancellationSignal.OnCancelListener { private static final String TAG = "SQLiteConnection"; private static final boolean DEBUG = false; @@ -110,11 +110,11 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene private boolean mOnlyAllowReadOnlyOperations; - // The number of times attachCancelationSignal has been called. + // The number of times attachCancellationSignal has been called. // Because SQLite statement execution can be re-entrant, we keep track of how many - // times we have attempted to attach a cancelation signal to the connection so that + // times we have attempted to attach a cancellation signal to the connection so that // we can ensure that we detach the signal at the right time. - private int mCancelationSignalAttachCount; + private int mCancellationSignalAttachCount; private static native int nativeOpen(String path, int openFlags, String label, boolean enableTrace, boolean enableProfile); @@ -355,14 +355,14 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ public void execute(String sql, Object[] bindArgs, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -374,11 +374,11 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - attachCancelationSignal(cancelationSignal); + attachCancellationSignal(cancellationSignal); try { nativeExecute(mConnectionPtr, statement.mStatementPtr); } finally { - detachCancelationSignal(cancelationSignal); + detachCancellationSignal(cancellationSignal); } } finally { releasePreparedStatement(statement); @@ -396,7 +396,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The value of the first column in the first row of the result set * as a <code>long</code>, or zero if none. * @@ -405,7 +405,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * @throws OperationCanceledException if the operation was canceled. */ public long executeForLong(String sql, Object[] bindArgs, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -417,11 +417,11 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - attachCancelationSignal(cancelationSignal); + attachCancellationSignal(cancellationSignal); try { return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr); } finally { - detachCancelationSignal(cancelationSignal); + detachCancellationSignal(cancellationSignal); } } finally { releasePreparedStatement(statement); @@ -439,7 +439,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The value of the first column in the first row of the result set * as a <code>String</code>, or null if none. * @@ -448,7 +448,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * @throws OperationCanceledException if the operation was canceled. */ public String executeForString(String sql, Object[] bindArgs, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -460,11 +460,11 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - attachCancelationSignal(cancelationSignal); + attachCancellationSignal(cancellationSignal); try { return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr); } finally { - detachCancelationSignal(cancelationSignal); + detachCancellationSignal(cancellationSignal); } } finally { releasePreparedStatement(statement); @@ -483,7 +483,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The file descriptor for a shared memory region that contains * the value of the first column in the first row of the result set as a BLOB, * or null if none. @@ -493,7 +493,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * @throws OperationCanceledException if the operation was canceled. */ public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -506,13 +506,13 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - attachCancelationSignal(cancelationSignal); + attachCancellationSignal(cancellationSignal); try { int fd = nativeExecuteForBlobFileDescriptor( mConnectionPtr, statement.mStatementPtr); return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null; } finally { - detachCancelationSignal(cancelationSignal); + detachCancellationSignal(cancellationSignal); } } finally { releasePreparedStatement(statement); @@ -531,7 +531,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The number of rows that were changed. * * @throws SQLiteException if an error occurs, such as a syntax error @@ -539,7 +539,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * @throws OperationCanceledException if the operation was canceled. */ public int executeForChangedRowCount(String sql, Object[] bindArgs, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -552,12 +552,12 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - attachCancelationSignal(cancelationSignal); + attachCancellationSignal(cancellationSignal); try { return nativeExecuteForChangedRowCount( mConnectionPtr, statement.mStatementPtr); } finally { - detachCancelationSignal(cancelationSignal); + detachCancellationSignal(cancellationSignal); } } finally { releasePreparedStatement(statement); @@ -576,7 +576,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * * @param sql The SQL statement to execute. * @param bindArgs The arguments to bind, or null if none. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The row id of the last row that was inserted, or 0 if none. * * @throws SQLiteException if an error occurs, such as a syntax error @@ -584,7 +584,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * @throws OperationCanceledException if the operation was canceled. */ public long executeForLastInsertedRowId(String sql, Object[] bindArgs, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -597,12 +597,12 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - attachCancelationSignal(cancelationSignal); + attachCancellationSignal(cancellationSignal); try { return nativeExecuteForLastInsertedRowId( mConnectionPtr, statement.mStatementPtr); } finally { - detachCancelationSignal(cancelationSignal); + detachCancellationSignal(cancellationSignal); } } finally { releasePreparedStatement(statement); @@ -629,7 +629,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene * so that it does. Must be greater than or equal to <code>startPos</code>. * @param countAllRows True to count all rows that the query would return * regagless of whether they fit in the window. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The number of rows that were counted during query execution. Might * not be all rows in the result set unless <code>countAllRows</code> is true. * @@ -639,7 +639,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene */ public int executeForCursorWindow(String sql, Object[] bindArgs, CursorWindow window, int startPos, int requiredPos, boolean countAllRows, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -658,7 +658,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); applyBlockGuardPolicy(statement); - attachCancelationSignal(cancelationSignal); + attachCancellationSignal(cancellationSignal); try { final long result = nativeExecuteForCursorWindow( mConnectionPtr, statement.mStatementPtr, window.mWindowPtr, @@ -669,7 +669,7 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene window.setStartPosition(actualPos); return countedRows; } finally { - detachCancelationSignal(cancelationSignal); + detachCancellationSignal(cancellationSignal); } } finally { releasePreparedStatement(statement); @@ -751,40 +751,40 @@ public final class SQLiteConnection implements CancelationSignal.OnCancelListene recyclePreparedStatement(statement); } - private void attachCancelationSignal(CancelationSignal cancelationSignal) { - if (cancelationSignal != null) { - cancelationSignal.throwIfCanceled(); + private void attachCancellationSignal(CancellationSignal cancellationSignal) { + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); - mCancelationSignalAttachCount += 1; - if (mCancelationSignalAttachCount == 1) { - // Reset cancelation flag before executing the statement. + mCancellationSignalAttachCount += 1; + if (mCancellationSignalAttachCount == 1) { + // Reset cancellation flag before executing the statement. nativeResetCancel(mConnectionPtr, true /*cancelable*/); // After this point, onCancel() may be called concurrently. - cancelationSignal.setOnCancelListener(this); + cancellationSignal.setOnCancelListener(this); } } } - private void detachCancelationSignal(CancelationSignal cancelationSignal) { - if (cancelationSignal != null) { - assert mCancelationSignalAttachCount > 0; + private void detachCancellationSignal(CancellationSignal cancellationSignal) { + if (cancellationSignal != null) { + assert mCancellationSignalAttachCount > 0; - mCancelationSignalAttachCount -= 1; - if (mCancelationSignalAttachCount == 0) { + mCancellationSignalAttachCount -= 1; + if (mCancellationSignalAttachCount == 0) { // After this point, onCancel() cannot be called concurrently. - cancelationSignal.setOnCancelListener(null); + cancellationSignal.setOnCancelListener(null); - // Reset cancelation flag after executing the statement. + // Reset cancellation flag after executing the statement. nativeResetCancel(mConnectionPtr, false /*cancelable*/); } } } - // CancelationSignal.OnCancelationListener callback. + // CancellationSignal.OnCancelListener callback. // This method may be called on a different thread than the executing statement. - // However, it will only be called between calls to attachCancelationSignal and - // detachCancelationSignal, while a statement is executing. We can safely assume + // However, it will only be called between calls to attachCancellationSignal and + // detachCancellationSignal, while a statement is executing. We can safely assume // that the SQLite connection is still alive. @Override public void onCancel() { diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index d3357387bbf9..236948ebb17a 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -18,7 +18,7 @@ package android.database.sqlite; import dalvik.system.CloseGuard; -import android.content.CancelationSignal; +import android.content.CancellationSignal; import android.content.OperationCanceledException; import android.database.sqlite.SQLiteDebug.DbStats; import android.os.SystemClock; @@ -284,7 +284,7 @@ public final class SQLiteConnectionPool implements Closeable { * @param sql If not null, try to find a connection that already has * the specified SQL statement in its prepared statement cache. * @param connectionFlags The connection request flags. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The connection that was acquired, never null. * * @throws IllegalStateException if the pool has been closed. @@ -292,8 +292,8 @@ public final class SQLiteConnectionPool implements Closeable { * @throws OperationCanceledException if the operation was canceled. */ public SQLiteConnection acquireConnection(String sql, int connectionFlags, - CancelationSignal cancelationSignal) { - return waitForConnection(sql, connectionFlags, cancelationSignal); + CancellationSignal cancellationSignal) { + return waitForConnection(sql, connectionFlags, cancellationSignal); } /** @@ -503,7 +503,7 @@ public final class SQLiteConnectionPool implements Closeable { // Might throw. private SQLiteConnection waitForConnection(String sql, int connectionFlags, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { final boolean wantPrimaryConnection = (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0; @@ -512,8 +512,8 @@ public final class SQLiteConnectionPool implements Closeable { throwIfClosedLocked(); // Abort if canceled. - if (cancelationSignal != null) { - cancelationSignal.throwIfCanceled(); + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); } // Try to acquire a connection. @@ -550,9 +550,9 @@ public final class SQLiteConnectionPool implements Closeable { mConnectionWaiterQueue = waiter; } - if (cancelationSignal != null) { + if (cancellationSignal != null) { final int nonce = waiter.mNonce; - cancelationSignal.setOnCancelListener(new CancelationSignal.OnCancelListener() { + cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() { @Override public void onCancel() { synchronized (mLock) { @@ -588,8 +588,8 @@ public final class SQLiteConnectionPool implements Closeable { final SQLiteConnection connection = waiter.mAssignedConnection; final RuntimeException ex = waiter.mException; if (connection != null || ex != null) { - if (cancelationSignal != null) { - cancelationSignal.setOnCancelListener(null); + if (cancellationSignal != null) { + cancellationSignal.setOnCancelListener(null); } recycleConnectionWaiterLocked(waiter); if (connection != null) { diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 7db7bfb703a2..505f83ee8948 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -16,7 +16,7 @@ package android.database.sqlite; -import android.content.CancelationSignal; +import android.content.CancellationSignal; import android.content.ContentValues; import android.content.OperationCanceledException; import android.content.res.Resources; @@ -967,7 +967,7 @@ public class SQLiteDatabase extends SQLiteClosable { * default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. * @return A {@link Cursor} object, which is positioned before the first entry. Note that @@ -976,9 +976,9 @@ public class SQLiteDatabase extends SQLiteClosable { */ public Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, - String having, String orderBy, String limit, CancelationSignal cancelationSignal) { + String having, String orderBy, String limit, CancellationSignal cancellationSignal) { return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, - groupBy, having, orderBy, limit, cancelationSignal); + groupBy, having, orderBy, limit, cancellationSignal); } /** @@ -1049,7 +1049,7 @@ public class SQLiteDatabase extends SQLiteClosable { * default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. * @return A {@link Cursor} object, which is positioned before the first entry. Note that @@ -1059,13 +1059,13 @@ public class SQLiteDatabase extends SQLiteClosable { public Cursor queryWithFactory(CursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, - String having, String orderBy, String limit, CancelationSignal cancelationSignal) { + String having, String orderBy, String limit, CancellationSignal cancellationSignal) { throwIfNotOpen(); // fail fast String sql = SQLiteQueryBuilder.buildQueryString( distinct, table, columns, selection, groupBy, having, orderBy, limit); return rawQueryWithFactory(cursorFactory, sql, selectionArgs, - findEditTable(table), cancelationSignal); + findEditTable(table), cancellationSignal); } /** @@ -1163,15 +1163,15 @@ public class SQLiteDatabase extends SQLiteClosable { * @param selectionArgs You may include ?s in where clause in the query, * which will be replaced by the values from selectionArgs. The * values will be bound as Strings. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. * @return A {@link Cursor} object, which is positioned before the first entry. Note that * {@link Cursor}s are not synchronized, see the documentation for more details. */ public Cursor rawQuery(String sql, String[] selectionArgs, - CancelationSignal cancelationSignal) { - return rawQueryWithFactory(null, sql, selectionArgs, null, cancelationSignal); + CancellationSignal cancellationSignal) { + return rawQueryWithFactory(null, sql, selectionArgs, null, cancellationSignal); } /** @@ -1201,7 +1201,7 @@ public class SQLiteDatabase extends SQLiteClosable { * which will be replaced by the values from selectionArgs. The * values will be bound as Strings. * @param editTable the name of the first table, which is editable - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. * @return A {@link Cursor} object, which is positioned before the first entry. Note that @@ -1209,11 +1209,11 @@ public class SQLiteDatabase extends SQLiteClosable { */ public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, - String editTable, CancelationSignal cancelationSignal) { + String editTable, CancellationSignal cancellationSignal) { throwIfNotOpen(); // fail fast SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, - cancelationSignal); + cancellationSignal); return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory, selectionArgs); } diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java index c490dc66f8d1..3375e7476367 100644 --- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java +++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java @@ -16,7 +16,7 @@ package android.database.sqlite; -import android.content.CancelationSignal; +import android.content.CancellationSignal; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase.CursorFactory; @@ -29,19 +29,19 @@ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver { private final SQLiteDatabase mDatabase; private final String mEditTable; private final String mSql; - private final CancelationSignal mCancelationSignal; + private final CancellationSignal mCancellationSignal; private SQLiteQuery mQuery; public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { mDatabase = db; mEditTable = editTable; mSql = sql; - mCancelationSignal = cancelationSignal; + mCancellationSignal = cancellationSignal; } public Cursor query(CursorFactory factory, String[] selectionArgs) { - final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancelationSignal); + final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal); final Cursor cursor; try { query.bindAllArgsAsStrings(selectionArgs); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index f3da2a6117fc..9f0edfbc7369 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -16,7 +16,7 @@ package android.database.sqlite; -import android.content.CancelationSignal; +import android.content.CancellationSignal; import android.database.DatabaseUtils; import java.util.Arrays; @@ -38,7 +38,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { private final Object[] mBindArgs; SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs, - CancelationSignal cancelationSignalForPrepare) { + CancellationSignal cancellationSignalForPrepare) { mDatabase = db; mSql = sql.trim(); @@ -57,7 +57,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { SQLiteStatementInfo info = new SQLiteStatementInfo(); db.getThreadSession().prepare(mSql, db.getThreadDefaultConnectionFlags(assumeReadOnly), - cancelationSignalForPrepare, info); + cancellationSignalForPrepare, info); mReadOnly = info.readOnly; mColumnNames = info.columnNames; mNumParameters = info.numParameters; diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index df2e260656c4..30e77b57068d 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -16,7 +16,7 @@ package android.database.sqlite; -import android.content.CancelationSignal; +import android.content.CancellationSignal; import android.content.OperationCanceledException; import android.database.CursorWindow; import android.util.Log; @@ -31,12 +31,12 @@ import android.util.Log; public final class SQLiteQuery extends SQLiteProgram { private static final String TAG = "SQLiteQuery"; - private final CancelationSignal mCancelationSignal; + private final CancellationSignal mCancellationSignal; - SQLiteQuery(SQLiteDatabase db, String query, CancelationSignal cancelationSignal) { - super(db, query, null, cancelationSignal); + SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) { + super(db, query, null, cancellationSignal); - mCancelationSignal = cancelationSignal; + mCancellationSignal = cancellationSignal; } /** @@ -61,7 +61,7 @@ public final class SQLiteQuery extends SQLiteProgram { try { int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(), window, startPos, requiredPos, countAllRows, getConnectionFlags(), - mCancelationSignal); + mCancellationSignal); return numRows; } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 89469cbac38d..6f84b5e2e118 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -16,7 +16,7 @@ package android.database.sqlite; -import android.content.CancelationSignal; +import android.content.CancellationSignal; import android.content.OperationCanceledException; import android.database.Cursor; import android.database.DatabaseUtils; @@ -292,7 +292,7 @@ public class SQLiteQueryBuilder String selection, String[] selectionArgs, String groupBy, String having, String sortOrder) { return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder, - null /* limit */, null /* cancelationSignal */); + null /* limit */, null /* cancellationSignal */); } /** @@ -362,7 +362,7 @@ public class SQLiteQueryBuilder * will use the default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. * @return a cursor over the result set @@ -371,7 +371,7 @@ public class SQLiteQueryBuilder */ public Cursor query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, - String having, String sortOrder, String limit, CancelationSignal cancelationSignal) { + String having, String sortOrder, String limit, CancellationSignal cancellationSignal) { if (mTables == null) { return null; } @@ -387,7 +387,7 @@ public class SQLiteQueryBuilder String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy, having, sortOrder, limit); validateQuerySql(db, sqlForValidation, - cancelationSignal); // will throw if query is invalid + cancellationSignal); // will throw if query is invalid } String sql = buildQuery( @@ -400,7 +400,7 @@ public class SQLiteQueryBuilder return db.rawQueryWithFactory( mFactory, sql, selectionArgs, SQLiteDatabase.findEditTable(mTables), - cancelationSignal); // will throw if query is invalid + cancellationSignal); // will throw if query is invalid } /** @@ -408,9 +408,9 @@ public class SQLiteQueryBuilder * If the SQL statement is not valid, this method will throw a {@link SQLiteException}. */ private void validateQuerySql(SQLiteDatabase db, String sql, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { db.getThreadSession().prepare(sql, - db.getThreadDefaultConnectionFlags(true /*readOnly*/), cancelationSignal, null); + db.getThreadDefaultConnectionFlags(true /*readOnly*/), cancellationSignal, null); } /** diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java index b5a3e31948af..43efb0355f56 100644 --- a/core/java/android/database/sqlite/SQLiteSession.java +++ b/core/java/android/database/sqlite/SQLiteSession.java @@ -16,7 +16,7 @@ package android.database.sqlite; -import android.content.CancelationSignal; +import android.content.CancellationSignal; import android.content.OperationCanceledException; import android.database.CursorWindow; import android.database.DatabaseUtils; @@ -280,7 +280,7 @@ public final class SQLiteSession { * @param transactionListener The transaction listener, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * * @throws IllegalStateException if {@link #setTransactionSuccessful} has already been * called for the current transaction. @@ -293,21 +293,21 @@ public final class SQLiteSession { */ public void beginTransaction(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { throwIfTransactionMarkedSuccessful(); beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags, - cancelationSignal); + cancellationSignal); } private void beginTransactionUnchecked(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags, - CancelationSignal cancelationSignal) { - if (cancelationSignal != null) { - cancelationSignal.throwIfCanceled(); + CancellationSignal cancellationSignal) { + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); } if (mTransactionStack == null) { - acquireConnection(null, connectionFlags, cancelationSignal); // might throw + acquireConnection(null, connectionFlags, cancellationSignal); // might throw } try { // Set up the transaction such that we can back out safely @@ -317,14 +317,14 @@ public final class SQLiteSession { switch (transactionMode) { case TRANSACTION_MODE_IMMEDIATE: mConnection.execute("BEGIN IMMEDIATE;", null, - cancelationSignal); // might throw + cancellationSignal); // might throw break; case TRANSACTION_MODE_EXCLUSIVE: mConnection.execute("BEGIN EXCLUSIVE;", null, - cancelationSignal); // might throw + cancellationSignal); // might throw break; default: - mConnection.execute("BEGIN;", null, cancelationSignal); // might throw + mConnection.execute("BEGIN;", null, cancellationSignal); // might throw break; } } @@ -335,7 +335,7 @@ public final class SQLiteSession { transactionListener.onBegin(); // might throw } catch (RuntimeException ex) { if (mTransactionStack == null) { - mConnection.execute("ROLLBACK;", null, cancelationSignal); // might throw + mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw } throw ex; } @@ -384,7 +384,7 @@ public final class SQLiteSession { * This method must be called exactly once for each call to {@link #beginTransaction}. * </p> * - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * * @throws IllegalStateException if there is no current transaction. * @throws SQLiteException if an error occurs. @@ -394,16 +394,16 @@ public final class SQLiteSession { * @see #setTransactionSuccessful * @see #yieldTransaction */ - public void endTransaction(CancelationSignal cancelationSignal) { + public void endTransaction(CancellationSignal cancellationSignal) { throwIfNoTransaction(); assert mConnection != null; - endTransactionUnchecked(cancelationSignal); + endTransactionUnchecked(cancellationSignal); } - private void endTransactionUnchecked(CancelationSignal cancelationSignal) { - if (cancelationSignal != null) { - cancelationSignal.throwIfCanceled(); + private void endTransactionUnchecked(CancellationSignal cancellationSignal) { + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); } final Transaction top = mTransactionStack; @@ -434,9 +434,9 @@ public final class SQLiteSession { } else { try { if (successful) { - mConnection.execute("COMMIT;", null, cancelationSignal); // might throw + mConnection.execute("COMMIT;", null, cancellationSignal); // might throw } else { - mConnection.execute("ROLLBACK;", null, cancelationSignal); // might throw + mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw } } finally { releaseConnection(); // might throw @@ -487,7 +487,7 @@ public final class SQLiteSession { * @param throwIfUnsafe If true, then instead of returning false when no * transaction is in progress, a nested transaction is in progress, or when * the transaction has already been marked successful, throws {@link IllegalStateException}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return True if the transaction was actually yielded. * * @throws IllegalStateException if <code>throwIfNested</code> is true and @@ -500,7 +500,7 @@ public final class SQLiteSession { * @see #endTransaction */ public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (throwIfUnsafe) { throwIfNoTransaction(); throwIfTransactionMarkedSuccessful(); @@ -518,13 +518,13 @@ public final class SQLiteSession { } return yieldTransactionUnchecked(sleepAfterYieldDelayMillis, - cancelationSignal); // might throw + cancellationSignal); // might throw } private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis, - CancelationSignal cancelationSignal) { - if (cancelationSignal != null) { - cancelationSignal.throwIfCanceled(); + CancellationSignal cancellationSignal) { + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); } if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) { @@ -534,7 +534,7 @@ public final class SQLiteSession { final int transactionMode = mTransactionStack.mMode; final SQLiteTransactionListener listener = mTransactionStack.mListener; final int connectionFlags = mConnectionFlags; - endTransactionUnchecked(cancelationSignal); // might throw + endTransactionUnchecked(cancellationSignal); // might throw if (sleepAfterYieldDelayMillis > 0) { try { @@ -545,7 +545,7 @@ public final class SQLiteSession { } beginTransactionUnchecked(transactionMode, listener, connectionFlags, - cancelationSignal); // might throw + cancellationSignal); // might throw return true; } @@ -566,24 +566,24 @@ public final class SQLiteSession { * @param sql The SQL statement to prepare. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate * with information about the statement, or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error. * @throws OperationCanceledException if the operation was canceled. */ - public void prepare(String sql, int connectionFlags, CancelationSignal cancelationSignal, + public void prepare(String sql, int connectionFlags, CancellationSignal cancellationSignal, SQLiteStatementInfo outStatementInfo) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (cancelationSignal != null) { - cancelationSignal.throwIfCanceled(); + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); } - acquireConnection(sql, connectionFlags, cancelationSignal); // might throw + acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { mConnection.prepare(sql, outStatementInfo); // might throw } finally { @@ -598,25 +598,25 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * * @throws SQLiteException if an error occurs, such as a syntax error * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ public void execute(String sql, Object[] bindArgs, int connectionFlags, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return; } - acquireConnection(sql, connectionFlags, cancelationSignal); // might throw + acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - mConnection.execute(sql, bindArgs, cancelationSignal); // might throw + mConnection.execute(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -629,7 +629,7 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The value of the first column in the first row of the result set * as a <code>long</code>, or zero if none. * @@ -638,18 +638,18 @@ public final class SQLiteSession { * @throws OperationCanceledException if the operation was canceled. */ public long executeForLong(String sql, Object[] bindArgs, int connectionFlags, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return 0; } - acquireConnection(sql, connectionFlags, cancelationSignal); // might throw + acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - return mConnection.executeForLong(sql, bindArgs, cancelationSignal); // might throw + return mConnection.executeForLong(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -662,7 +662,7 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The value of the first column in the first row of the result set * as a <code>String</code>, or null if none. * @@ -671,18 +671,18 @@ public final class SQLiteSession { * @throws OperationCanceledException if the operation was canceled. */ public String executeForString(String sql, Object[] bindArgs, int connectionFlags, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return null; } - acquireConnection(sql, connectionFlags, cancelationSignal); // might throw + acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - return mConnection.executeForString(sql, bindArgs, cancelationSignal); // might throw + return mConnection.executeForString(sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -696,7 +696,7 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The file descriptor for a shared memory region that contains * the value of the first column in the first row of the result set as a BLOB, * or null if none. @@ -706,19 +706,19 @@ public final class SQLiteSession { * @throws OperationCanceledException if the operation was canceled. */ public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, - int connectionFlags, CancelationSignal cancelationSignal) { + int connectionFlags, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return null; } - acquireConnection(sql, connectionFlags, cancelationSignal); // might throw + acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForBlobFileDescriptor(sql, bindArgs, - cancelationSignal); // might throw + cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -732,7 +732,7 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The number of rows that were changed. * * @throws SQLiteException if an error occurs, such as a syntax error @@ -740,19 +740,19 @@ public final class SQLiteSession { * @throws OperationCanceledException if the operation was canceled. */ public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return 0; } - acquireConnection(sql, connectionFlags, cancelationSignal); // might throw + acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForChangedRowCount(sql, bindArgs, - cancelationSignal); // might throw + cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -766,7 +766,7 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The row id of the last row that was inserted, or 0 if none. * * @throws SQLiteException if an error occurs, such as a syntax error @@ -774,19 +774,19 @@ public final class SQLiteSession { * @throws OperationCanceledException if the operation was canceled. */ public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { return 0; } - acquireConnection(sql, connectionFlags, cancelationSignal); // might throw + acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForLastInsertedRowId(sql, bindArgs, - cancelationSignal); // might throw + cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -808,7 +808,7 @@ public final class SQLiteSession { * regagless of whether they fit in the window. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return The number of rows that were counted during query execution. Might * not be all rows in the result set unless <code>countAllRows</code> is true. * @@ -818,7 +818,7 @@ public final class SQLiteSession { */ public int executeForCursorWindow(String sql, Object[] bindArgs, CursorWindow window, int startPos, int requiredPos, boolean countAllRows, - int connectionFlags, CancelationSignal cancelationSignal) { + int connectionFlags, CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -826,16 +826,16 @@ public final class SQLiteSession { throw new IllegalArgumentException("window must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) { + if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { window.clear(); return 0; } - acquireConnection(sql, connectionFlags, cancelationSignal); // might throw + acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { return mConnection.executeForCursorWindow(sql, bindArgs, window, startPos, requiredPos, countAllRows, - cancelationSignal); // might throw + cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -854,7 +854,7 @@ public final class SQLiteSession { * @param bindArgs The arguments to bind, or null if none. * @param connectionFlags The connection flags to use if a connection must be * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancelationSignal A signal to cancel the operation in progress, or null if none. + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * @return True if the statement was of a special form that was handled here, * false otherwise. * @@ -863,36 +863,36 @@ public final class SQLiteSession { * @throws OperationCanceledException if the operation was canceled. */ private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags, - CancelationSignal cancelationSignal) { - if (cancelationSignal != null) { - cancelationSignal.throwIfCanceled(); + CancellationSignal cancellationSignal) { + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); } final int type = DatabaseUtils.getSqlStatementType(sql); switch (type) { case DatabaseUtils.STATEMENT_BEGIN: beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags, - cancelationSignal); + cancellationSignal); return true; case DatabaseUtils.STATEMENT_COMMIT: setTransactionSuccessful(); - endTransaction(cancelationSignal); + endTransaction(cancellationSignal); return true; case DatabaseUtils.STATEMENT_ABORT: - endTransaction(cancelationSignal); + endTransaction(cancellationSignal); return true; } return false; } private void acquireConnection(String sql, int connectionFlags, - CancelationSignal cancelationSignal) { + CancellationSignal cancellationSignal) { if (mConnection == null) { assert mConnectionUseCount == 0; mConnection = mConnectionPool.acquireConnection(sql, connectionFlags, - cancelationSignal); // might throw + cancellationSignal); // might throw mConnectionFlags = connectionFlags; } mConnectionUseCount += 1; diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index a569317e2500..2eef8f441b69 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -142,8 +142,19 @@ public class ConnectivityManager { * If an application uses the network in the background, it should listen * for this broadcast and stop using the background data if the value is * {@code false}. + * <p> + * + * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability + * of background data depends on several combined factors, and + * this broadcast is no longer sent. Instead, when background + * data is unavailable, {@link #getActiveNetworkInfo()} will now + * appear disconnected. During first boot after a platform + * upgrade, this broadcast will be sent once if + * {@link #getBackgroundDataSetting()} was {@code false} before + * the upgrade. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl index a45ec545d632..31dc965a4c02 100644 --- a/core/java/android/net/INetworkPolicyListener.aidl +++ b/core/java/android/net/INetworkPolicyListener.aidl @@ -21,5 +21,6 @@ oneway interface INetworkPolicyListener { void onUidRulesChanged(int uid, int uidRules); void onMeteredIfacesChanged(in String[] meteredIfaces); + void onRestrictBackgroundChanged(boolean restrictBackground); } diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 633c38e0c66d..442535af55ca 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -43,7 +43,7 @@ interface INetworkPolicyManager { NetworkPolicy[] getNetworkPolicies(); /** Snooze limit on policy matching given template. */ - void snoozePolicy(in NetworkTemplate template); + void snoozeLimit(in NetworkTemplate template); /** Control if background data is restricted system-wide. */ void setRestrictBackground(boolean restrictBackground); diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java index d9ea7001dd1d..04cf1a3f06ba 100644 --- a/core/java/android/net/NetworkPolicy.java +++ b/core/java/android/net/NetworkPolicy.java @@ -38,18 +38,25 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { public int cycleDay; public long warningBytes; public long limitBytes; - public long lastSnooze; + public long lastWarningSnooze; + public long lastLimitSnooze; public boolean metered; private static final long DEFAULT_MTU = 1500; - public NetworkPolicy(NetworkTemplate template, int cycleDay, long warningBytes, long limitBytes, - long lastSnooze, boolean metered) { + public NetworkPolicy(NetworkTemplate template, int cycleDay, long warningBytes, + long limitBytes, boolean metered) { + this(template, cycleDay, warningBytes, limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, metered); + } + + public NetworkPolicy(NetworkTemplate template, int cycleDay, long warningBytes, + long limitBytes, long lastWarningSnooze, long lastLimitSnooze, boolean metered) { this.template = checkNotNull(template, "missing NetworkTemplate"); this.cycleDay = cycleDay; this.warningBytes = warningBytes; this.limitBytes = limitBytes; - this.lastSnooze = lastSnooze; + this.lastWarningSnooze = lastWarningSnooze; + this.lastLimitSnooze = lastLimitSnooze; this.metered = metered; } @@ -58,7 +65,8 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { cycleDay = in.readInt(); warningBytes = in.readLong(); limitBytes = in.readLong(); - lastSnooze = in.readLong(); + lastWarningSnooze = in.readLong(); + lastLimitSnooze = in.readLong(); metered = in.readInt() != 0; } @@ -68,7 +76,8 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { dest.writeInt(cycleDay); dest.writeLong(warningBytes); dest.writeLong(limitBytes); - dest.writeLong(lastSnooze); + dest.writeLong(lastWarningSnooze); + dest.writeLong(lastLimitSnooze); dest.writeInt(metered ? 1 : 0); } @@ -78,6 +87,13 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { } /** + * Test if given measurement is over {@link #warningBytes}. + */ + public boolean isOverWarning(long totalBytes) { + return warningBytes != WARNING_DISABLED && totalBytes >= warningBytes; + } + + /** * Test if given measurement is near enough to {@link #limitBytes} to be * considered over-limit. */ @@ -88,6 +104,14 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { return limitBytes != LIMIT_DISABLED && totalBytes >= limitBytes; } + /** + * Clear any existing snooze values, setting to {@link #SNOOZE_NEVER}. + */ + public void clearSnooze() { + lastWarningSnooze = SNOOZE_NEVER; + lastLimitSnooze = SNOOZE_NEVER; + } + /** {@inheritDoc} */ public int compareTo(NetworkPolicy another) { if (another == null || another.limitBytes == LIMIT_DISABLED) { @@ -103,7 +127,8 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { @Override public int hashCode() { - return Objects.hashCode(template, cycleDay, warningBytes, limitBytes, lastSnooze, metered); + return Objects.hashCode(template, cycleDay, warningBytes, limitBytes, lastWarningSnooze, + lastLimitSnooze, metered); } @Override @@ -111,8 +136,10 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { if (obj instanceof NetworkPolicy) { final NetworkPolicy other = (NetworkPolicy) obj; return cycleDay == other.cycleDay && warningBytes == other.warningBytes - && limitBytes == other.limitBytes && lastSnooze == other.lastSnooze - && metered == other.metered && Objects.equal(template, other.template); + && limitBytes == other.limitBytes + && lastWarningSnooze == other.lastWarningSnooze + && lastLimitSnooze == other.lastLimitSnooze && metered == other.metered + && Objects.equal(template, other.template); } return false; } @@ -120,8 +147,9 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { @Override public String toString() { return "NetworkPolicy[" + template + "]: cycleDay=" + cycleDay + ", warningBytes=" - + warningBytes + ", limitBytes=" + limitBytes + ", lastSnooze=" + lastSnooze - + ", metered=" + metered; + + warningBytes + ", limitBytes=" + limitBytes + ", lastWarningSnooze=" + + lastWarningSnooze + ", lastLimitSnooze=" + lastLimitSnooze + ", metered=" + + metered; } public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() { diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index dfdea383d6c7..973fac190974 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -44,6 +44,13 @@ public class TrafficStats { */ public final static int UNSUPPORTED = -1; + /** @hide */ + public static final long KB_IN_BYTES = 1024; + /** @hide */ + public static final long MB_IN_BYTES = KB_IN_BYTES * 1024; + /** @hide */ + public static final long GB_IN_BYTES = MB_IN_BYTES * 1024; + /** * Special UID value used when collecting {@link NetworkStatsHistory} for * removed applications. diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 5e9abb7ba612..fd6bed7ee761 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -135,6 +135,8 @@ import java.util.concurrent.atomic.AtomicInteger; * <p>There are a few threading rules that must be followed for this class to * work properly:</p> * <ul> + * <li>The AsyncTask class must be loaded on the UI thread. This is done + * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li> * <li>The task instance must be created on the UI thread.</li> * <li>{@link #execute} must be invoked on the UI thread.</li> * <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute}, diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 24569fa6e9b1..577fc434836c 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -49,6 +49,7 @@ public class Binder implements IBinder { private static final boolean FIND_POTENTIAL_LEAKS = false; private static final String TAG = "Binder"; + /* mObject is used by native code, do not remove or rename */ private int mObject; private IInterface mOwner; private String mDescriptor; @@ -70,7 +71,35 @@ public class Binder implements IBinder { * incoming transaction, then its own uid is returned. */ public static final native int getCallingUid(); - + + /** + * Return the original ID of the user assigned to the process that sent you the current + * transaction that is being processed. This uid can be used with higher-level system services + * to determine its identity and check permissions. If the current thread is not currently + * executing an incoming transaction, then its own uid is returned. + * <p/> + * This value cannot be reset by calls to {@link #clearCallingIdentity()}. + * @hide + */ + public static final int getOrigCallingUid() { + if (UserId.MU_ENABLED) { + return getOrigCallingUidNative(); + } else { + return getCallingUid(); + } + } + + private static final native int getOrigCallingUidNative(); + + /** + * Utility function to return the user id of the calling process. + * @return userId of the calling process, extracted from the callingUid + * @hide + */ + public static final int getOrigCallingUser() { + return UserId.getUserId(getOrigCallingUid()); + } + /** * Reset the identity of the incoming IPC on the current thread. This can * be useful if, while handling an incoming call, you will be calling diff --git a/core/java/android/os/UserId.java b/core/java/android/os/UserId.java new file mode 100644 index 000000000000..4124d51a2da1 --- /dev/null +++ b/core/java/android/os/UserId.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * @hide + */ +public final class UserId { + /** + * Range of IDs allocated for a user. + * + * @hide + */ + public static final int PER_USER_RANGE = 100000; + + public static final int USER_ALL = -1; + + /** + * Enable multi-user related side effects. Set this to false if there are problems with single + * user usecases. + * */ + public static final boolean MU_ENABLED = true; + + /** + * 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 final boolean isSameUser(int uid1, int uid2) { + return getUserId(uid1) == getUserId(uid2); + } + + /** + * Checks to see if both uids are referring to the same app id, ignoring the user id part of the + * uids. + * @param uid1 uid to compare + * @param uid2 other uid to compare + * @return whether the appId is the same for both uids + * @hide + */ + public static final boolean isSameApp(int uid1, int uid2) { + return getAppId(uid1) == getAppId(uid2); + } + + /** + * Returns the user id for a given uid. + * @hide + */ + public static final int getUserId(int uid) { + if (MU_ENABLED) { + return uid / PER_USER_RANGE; + } else { + return 0; + } + } + + /** + * Returns the uid that is composed from the userId and the appId. + * @hide + */ + public static final int getUid(int userId, int appId) { + if (MU_ENABLED) { + return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); + } else { + return appId; + } + } + + /** + * Returns the app id (or base uid) for a given uid, stripping out the user id from it. + * @hide + */ + public static final int getAppId(int uid) { + return uid % PER_USER_RANGE; + } +} diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 4e016723eba3..d3ad63d02fe3 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -62,6 +62,26 @@ public final class MediaStore { public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END"; /** + * The method name used by the media scanner and mtp to tell the media provider to + * rescan and reclassify that have become unhidden because of renaming folders or + * removing nomedia files + * @hide + */ + public static final String UNHIDE_CALL = "unhide"; + + /** + * This is for internal use by the media scanner only. + * Name of the (optional) Uri parameter that determines whether to skip deleting + * the file pointed to by the _data column, when deleting the database entry. + * The only appropriate value for this parameter is "false", in which case the + * delete will be skipped. Note especially that setting this to true, or omitting + * the parameter altogether, will perform the default action, which is different + * for different types of media. + * @hide + */ + public static final String PARAM_DELETE_DATA = "deletedata"; + + /** * Activity Action: Launch a music player. * The activity should be able to play, browse, or manipulate music files stored on the device. * diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f14d27e7e60a..375e5e4f6dc6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1678,6 +1678,13 @@ public final class Settings { public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale"; /** + * Scaling factor for Animator-based animations. This affects both the start delay and + * duration of all such animations. Setting to 0 will cause animations to end immediately. + * The default value is 1. + */ + public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale"; + + /** * Scaling factor for normal window animations. Setting to 0 will disable window * animations. * @hide @@ -2475,6 +2482,11 @@ public final class Settings { Uri.parse("content://" + AUTHORITY + "/secure"); /** + * Whether user has enabled development settings. + */ + public static final String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled"; + + /** * Whether ADB is enabled. */ public static final String ADB_ENABLED = "adb_enabled"; diff --git a/core/java/android/view/AccessibilityNodeInfoCache.java b/core/java/android/view/AccessibilityNodeInfoCache.java new file mode 100644 index 000000000000..244a49191f74 --- /dev/null +++ b/core/java/android/view/AccessibilityNodeInfoCache.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.util.LongSparseArray; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +/** + * Simple cache for AccessibilityNodeInfos. The cache is mapping an + * accessibility id to an info. The cache allows storing of + * <code>null</code> values. It also tracks accessibility events + * and invalidates accordingly. + * + * @hide + */ +public class AccessibilityNodeInfoCache { + + private final boolean ENABLED = true; + + /** + * @return A new <strong>not synchronized</strong> AccessibilityNodeInfoCache. + */ + public static AccessibilityNodeInfoCache newAccessibilityNodeInfoCache() { + return new AccessibilityNodeInfoCache(); + } + + /** + * @return A new <strong>synchronized</strong> AccessibilityNodeInfoCache. + */ + public static AccessibilityNodeInfoCache newSynchronizedAccessibilityNodeInfoCache() { + return new AccessibilityNodeInfoCache() { + private final Object mLock = new Object(); + + @Override + public void clear() { + synchronized(mLock) { + super.clear(); + } + } + + @Override + public AccessibilityNodeInfo get(long accessibilityNodeId) { + synchronized(mLock) { + return super.get(accessibilityNodeId); + } + } + + @Override + public void put(long accessibilityNodeId, AccessibilityNodeInfo info) { + synchronized(mLock) { + super.put(accessibilityNodeId, info); + } + } + + @Override + public void remove(long accessibilityNodeId) { + synchronized(mLock) { + super.remove(accessibilityNodeId); + } + } + }; + } + + private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl; + + private AccessibilityNodeInfoCache() { + if (ENABLED) { + mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>(); + } else { + mCacheImpl = null; + } + } + + /** + * The cache keeps track of {@link AccessibilityEvent}s and invalidates + * cached nodes as appropriate. + * + * @param event An event. + */ + public void onAccessibilityEvent(AccessibilityEvent event) { + final int eventType = event.getEventType(); + switch (eventType) { + case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: + case AccessibilityEvent.TYPE_VIEW_SCROLLED: + clear(); + break; + case AccessibilityEvent.TYPE_VIEW_FOCUSED: + case AccessibilityEvent.TYPE_VIEW_SELECTED: + case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: + case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: + final long accessibilityNodeId = event.getSourceNodeId(); + remove(accessibilityNodeId); + break; + } + } + + /** + * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id. + * + * @param accessibilityNodeId The info accessibility node id. + * @return The cached {@link AccessibilityNodeInfo} or null if such not found. + */ + public AccessibilityNodeInfo get(long accessibilityNodeId) { + if (ENABLED) { + return mCacheImpl.get(accessibilityNodeId); + } else { + return null; + } + } + + /** + * Caches an {@link AccessibilityNodeInfo} given its accessibility node id. + * + * @param accessibilityNodeId The info accessibility node id. + * @param info The {@link AccessibilityNodeInfo} to cache. + */ + public void put(long accessibilityNodeId, AccessibilityNodeInfo info) { + if (ENABLED) { + mCacheImpl.put(accessibilityNodeId, info); + } + } + + /** + * Returns whether the cache contains an accessibility node id key. + * + * @param accessibilityNodeId The key for which to check. + * @return True if the key is in the cache. + */ + public boolean containsKey(long accessibilityNodeId) { + if (ENABLED) { + return (mCacheImpl.indexOfKey(accessibilityNodeId) >= 0); + } else { + return false; + } + } + + /** + * Removes a cached {@link AccessibilityNodeInfo}. + * + * @param accessibilityNodeId The info accessibility node id. + */ + public void remove(long accessibilityNodeId) { + if (ENABLED) { + mCacheImpl.remove(accessibilityNodeId); + } + } + + /** + * Clears the cache. + */ + public void clear() { + if (ENABLED) { + mCacheImpl.clear(); + } + } +} diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java index 34e7d4d746bb..0349a2be3fbf 100644 --- a/core/java/android/view/ActionMode.java +++ b/core/java/android/view/ActionMode.java @@ -18,9 +18,15 @@ package android.view; /** - * Represents a contextual mode of the user interface. Action modes can be used for - * modal interactions with content and replace parts of the normal UI until finished. - * Examples of good action modes include selection modes, search, content editing, etc. + * Represents a contextual mode of the user interface. Action modes can be used to provide + * alternative interaction modes and replace parts of the normal UI until finished. + * Examples of good action modes include text selection and contextual actions. + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For information about how to provide contextual actions with {@code ActionMode}, + * read the <a href="{@docRoot}guide/topics/ui/menu.html#context-menu">Menus</a> + * developer guide.</p> + * </div> */ public abstract class ActionMode { private Object mTag; diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 63de12836512..3ee275cb6399 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -26,7 +26,11 @@ import android.os.SystemProperties; import android.util.Log; /** - * Coodinates animations and drawing for UI on a particular thread. + * Coordinates animations and drawing for UI on a particular thread. + * + * This object is thread-safe. Other threads can add and remove listeners + * or schedule work to occur at a later time on the UI thread. + * * @hide */ public final class Choreographer extends Handler { @@ -44,7 +48,7 @@ public final class Choreographer extends Handler { private static final long DEFAULT_FRAME_DELAY = 10; // The number of milliseconds between animation frames. - private static long sFrameDelay = DEFAULT_FRAME_DELAY; + private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY; // Thread local storage for the choreographer. private static final ThreadLocal<Choreographer> sThreadInstance = @@ -75,6 +79,8 @@ public final class Choreographer extends Handler { private static final int MSG_DO_ANIMATION = 0; private static final int MSG_DO_DRAW = 1; + private final Object mLock = new Object(); + private final Looper mLooper; private OnAnimateListener[] mOnAnimateListeners; @@ -94,8 +100,8 @@ public final class Choreographer extends Handler { } /** - * Gets the choreographer for this thread. - * Must be called on the UI thread. + * Gets the choreographer for the calling thread. Must be called from + * a thread that already has a {@link android.os.Looper} associated with it. * * @return The choreographer for this thread. * @throws IllegalStateException if the thread does not have a looper. @@ -138,9 +144,14 @@ public final class Choreographer extends Handler { /** * Schedules animation (and drawing) to occur on the next frame synchronization boundary. - * Must be called on the UI thread. */ public void scheduleAnimation() { + synchronized (mLock) { + scheduleAnimationLocked(); + } + } + + private void scheduleAnimationLocked() { if (!mAnimationScheduled) { mAnimationScheduled = true; if (USE_VSYNC) { @@ -163,23 +174,47 @@ public final class Choreographer extends Handler { } /** + * Returns true if {@link #scheduleAnimation()} has been called but + * {@link OnAnimateListener#onAnimate() OnAnimateListener.onAnimate()} has + * not yet been called. + */ + public boolean isAnimationScheduled() { + synchronized (mLock) { + return mAnimationScheduled; + } + } + + /** * Schedules drawing to occur on the next frame synchronization boundary. * Must be called on the UI thread. */ public void scheduleDraw() { - if (!mDrawScheduled) { - mDrawScheduled = true; - if (USE_ANIMATION_TIMER_FOR_DRAW) { - scheduleAnimation(); - } else { - if (DEBUG) { - Log.d(TAG, "Scheduling draw immediately."); + synchronized (mLock) { + if (!mDrawScheduled) { + mDrawScheduled = true; + if (USE_ANIMATION_TIMER_FOR_DRAW) { + scheduleAnimationLocked(); + } else { + if (DEBUG) { + Log.d(TAG, "Scheduling draw immediately."); + } + sendEmptyMessage(MSG_DO_DRAW); } - sendEmptyMessage(MSG_DO_DRAW); } } } + /** + * Returns true if {@link #scheduleDraw()} has been called but + * {@link OnDrawListener#onDraw() OnDrawListener.onDraw()} has + * not yet been called. + */ + public boolean isDrawScheduled() { + synchronized (mLock) { + return mDrawScheduled; + } + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -193,60 +228,75 @@ public final class Choreographer extends Handler { } private void doAnimation() { - if (mAnimationScheduled) { + doAnimationInner(); + + if (USE_ANIMATION_TIMER_FOR_DRAW) { + doDraw(); + } + } + + private void doAnimationInner() { + final long start; + final OnAnimateListener[] listeners; + synchronized (mLock) { + if (!mAnimationScheduled) { + return; // no work to do + } mAnimationScheduled = false; - final long start = SystemClock.uptimeMillis(); + start = SystemClock.uptimeMillis(); if (DEBUG) { Log.d(TAG, "Performing animation: " + Math.max(0, start - mLastAnimationTime) + " ms have elapsed since previous animation."); } mLastAnimationTime = start; - final OnAnimateListener[] listeners = mOnAnimateListeners; - if (listeners != null) { - for (int i = 0; i < listeners.length; i++) { - listeners[i].onAnimate(); - } - } + listeners = mOnAnimateListeners; + } - if (DEBUG) { - Log.d(TAG, "Animation took " + (SystemClock.uptimeMillis() - start) + " ms."); + if (listeners != null) { + for (int i = 0; i < listeners.length; i++) { + listeners[i].onAnimate(); } } - if (USE_ANIMATION_TIMER_FOR_DRAW) { - doDraw(); + if (DEBUG) { + Log.d(TAG, "Animation took " + (SystemClock.uptimeMillis() - start) + " ms."); } } private void doDraw() { - if (mDrawScheduled) { + final long start; + final OnDrawListener[] listeners; + synchronized (mLock) { + if (!mDrawScheduled) { + return; // no work to do + } mDrawScheduled = false; - final long start = SystemClock.uptimeMillis(); + start = SystemClock.uptimeMillis(); if (DEBUG) { Log.d(TAG, "Performing draw: " + Math.max(0, start - mLastDrawTime) + " ms have elapsed since previous draw."); } mLastDrawTime = start; - final OnDrawListener[] listeners = mOnDrawListeners; - if (listeners != null) { - for (int i = 0; i < listeners.length; i++) { - listeners[i].onDraw(); - } - } + listeners = mOnDrawListeners; + } - if (DEBUG) { - Log.d(TAG, "Draw took " + (SystemClock.uptimeMillis() - start) + " ms."); + if (listeners != null) { + for (int i = 0; i < listeners.length; i++) { + listeners[i].onDraw(); } } + + if (DEBUG) { + Log.d(TAG, "Draw took " + (SystemClock.uptimeMillis() - start) + " ms."); + } } /** * Adds an animation listener. - * Must be called on the UI thread. * * @param listener The listener to add. */ @@ -259,13 +309,14 @@ public final class Choreographer extends Handler { Log.d(TAG, "Adding onAnimate listener: " + listener); } - mOnAnimateListeners = ArrayUtils.appendElement(OnAnimateListener.class, - mOnAnimateListeners, listener); + synchronized (mLock) { + mOnAnimateListeners = ArrayUtils.appendElement(OnAnimateListener.class, + mOnAnimateListeners, listener); + } } /** * Removes an animation listener. - * Must be called on the UI thread. * * @param listener The listener to remove. */ @@ -278,14 +329,15 @@ public final class Choreographer extends Handler { Log.d(TAG, "Removing onAnimate listener: " + listener); } - mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class, - mOnAnimateListeners, listener); - stopTimingLoopIfNoListeners(); + synchronized (mLock) { + mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class, + mOnAnimateListeners, listener); + stopTimingLoopIfNoListenersLocked(); + } } /** * Adds a draw listener. - * Must be called on the UI thread. * * @param listener The listener to add. */ @@ -298,8 +350,10 @@ public final class Choreographer extends Handler { Log.d(TAG, "Adding onDraw listener: " + listener); } - mOnDrawListeners = ArrayUtils.appendElement(OnDrawListener.class, - mOnDrawListeners, listener); + synchronized (mLock) { + mOnDrawListeners = ArrayUtils.appendElement(OnDrawListener.class, + mOnDrawListeners, listener); + } } /** @@ -317,12 +371,14 @@ public final class Choreographer extends Handler { Log.d(TAG, "Removing onDraw listener: " + listener); } - mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class, - mOnDrawListeners, listener); - stopTimingLoopIfNoListeners(); + synchronized (mLock) { + mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class, + mOnDrawListeners, listener); + stopTimingLoopIfNoListenersLocked(); + } } - private void stopTimingLoopIfNoListeners() { + private void stopTimingLoopIfNoListenersLocked() { if (mOnDrawListeners == null && mOnAnimateListeners == null) { if (DEBUG) { Log.d(TAG, "Stopping timing loop."); diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index c08a402412c5..1e92b43a3203 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -189,7 +189,7 @@ class GLES20Canvas extends HardwareCanvas { } private static native int nGetMaximumTextureWidth(); - private static native int nGetMaximumTextureHeight(); + private static native int nGetMaximumTextureHeight(); /////////////////////////////////////////////////////////////////////////// // Setup @@ -268,6 +268,24 @@ class GLES20Canvas extends HardwareCanvas { private static native void nFinish(int renderer); + /** + * Returns the size of the stencil buffer required by the underlying + * implementation. + * + * @return The minimum number of bits the stencil buffer must. Always >= 0. + * + * @hide + */ + public static int getStencilSize() { + return nGetStencilSize(); + } + + private static native int nGetStencilSize(); + + /////////////////////////////////////////////////////////////////////////// + // Functor + /////////////////////////////////////////////////////////////////////////// + @Override public boolean callDrawGLFunction(int drawGLFunction) { return nCallDrawGLFunction(mRenderer, drawGLFunction); @@ -275,7 +293,6 @@ class GLES20Canvas extends HardwareCanvas { private static native boolean nCallDrawGLFunction(int renderer, int drawGLFunction); - /////////////////////////////////////////////////////////////////////////// // Memory /////////////////////////////////////////////////////////////////////////// @@ -361,6 +378,12 @@ class GLES20Canvas extends HardwareCanvas { private static native int nGetDisplayListSize(int displayList); + static void setDisplayListName(int displayList, String name) { + nSetDisplayListName(displayList, name); + } + + private static native void nSetDisplayListName(int displayList, String name); + @Override public boolean drawDisplayList(DisplayList displayList, int width, int height, Rect dirty) { return nDrawDisplayList(mRenderer, @@ -700,6 +723,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { + if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); // Shaders are ignored when drawing patches int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; final int nativePaint = paint == null ? 0 : paint.mNativePaint; @@ -713,6 +737,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { + if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; final int nativePaint = paint == null ? 0 : paint.mNativePaint; @@ -725,6 +750,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { + if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; final int nativePaint = paint == null ? 0 : paint.mNativePaint; @@ -738,6 +764,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { + if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; final int nativePaint = paint == null ? 0 : paint.mNativePaint; @@ -761,6 +788,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { + if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; final int nativePaint = paint == null ? 0 : paint.mNativePaint; @@ -809,6 +837,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint) { + if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); if (meshWidth < 0 || meshHeight < 0 || vertOffset < 0 || colorOffset < 0) { throw new ArrayIndexOutOfBoundsException(); } diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index 0cb944957130..969c9ab0710f 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -31,10 +31,17 @@ class GLES20DisplayList extends DisplayList { private GLES20RecordingCanvas mCanvas; private boolean mValid; + // Used for debugging + private final String mName; + // The native display list will be destroyed when this object dies. // DO NOT overwrite this reference once it is set. private DisplayListFinalizer mFinalizer; + GLES20DisplayList(String name) { + mName = name; + } + int getNativeDisplayList() { if (!mValid || mFinalizer == null) { throw new IllegalStateException("The display list is not valid."); @@ -75,6 +82,7 @@ class GLES20DisplayList extends DisplayList { mCanvas.end(mFinalizer.mNativeDisplayList); } else { mFinalizer = new DisplayListFinalizer(mCanvas.end(0)); + GLES20Canvas.setDisplayListName(mFinalizer.mNativeDisplayList, mName); } mCanvas.recycle(); mCanvas = null; diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 1c9cbbfea53b..9e8a228edfa1 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -283,9 +283,12 @@ public abstract class HardwareRenderer { * Creates a new display list that can be used to record batches of * drawing operations. * + * @param name The name of the display list, used for debugging purpose. + * May be null + * * @return A new display list. */ - public abstract DisplayList createDisplayList(); + public abstract DisplayList createDisplayList(String name); /** * Creates a new hardware layer. A hardware layer built by calling this @@ -1047,7 +1050,7 @@ public abstract class HardwareRenderer { EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, - EGL_STENCIL_SIZE, 0, + EGL_STENCIL_SIZE, GLES20Canvas.getStencilSize(), EGL_SURFACE_TYPE, EGL_WINDOW_BIT | (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0), EGL_NONE @@ -1094,8 +1097,8 @@ public abstract class HardwareRenderer { } @Override - public DisplayList createDisplayList() { - return new GLES20DisplayList(); + public DisplayList createDisplayList(String name) { + return new GLES20DisplayList(name); } @Override diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 8cac57d21572..7658d041cb1f 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2166,15 +2166,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal float mScaleY = 1f; /** - * The amount of scale in the x direction around the pivot point. A - * value of 1 means no scaling is applied. + * The x location of the point around which the view is rotated and scaled. */ @ViewDebug.ExportedProperty float mPivotX = 0f; /** - * The amount of scale in the y direction around the pivot point. A - * value of 1 means no scaling is applied. + * The y location of the point around which the view is rotated and scaled. */ @ViewDebug.ExportedProperty float mPivotY = 0f; @@ -3761,8 +3759,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Called when this view wants to give up focus. This will cause - * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called. + * Called when this view wants to give up focus. If focus is cleared + * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called. + * <p> + * <strong>Note:</strong> When a View clears focus the framework is trying + * to give focus to the first focusable View from the top. Hence, if this + * View is the first from the top that can take focus, then its focus will + * not be cleared nor will the focus change callback be invoked. + * </p> */ public void clearFocus() { if (DBG) { @@ -3770,6 +3774,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } if ((mPrivateFlags & FOCUSED) != 0) { + // If this is the first focusable do not clear focus since the we + // try to give it focus every time a view clears its focus. Hence, + // the view that would gain focus already has it. + View firstFocusable = getFirstFocusable(); + if (firstFocusable == this) { + return; + } + mPrivateFlags &= ~FOCUSED; if (mParent != null) { @@ -3778,9 +3790,24 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onFocusChanged(false, 0, null); refreshDrawableState(); + + // The view cleared focus and invoked the callbacks, so now is the + // time to give focus to the the first focusable to ensure that the + // gain focus is announced after clear focus. + if (firstFocusable != null) { + firstFocusable.requestFocus(FOCUS_FORWARD); + } } } + private View getFirstFocusable() { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + return viewRoot.focusSearch(null, FOCUS_FORWARD); + } + return null; + } + /** * Called to clear the focus of a view that is about to be removed. * Doesn't call clearChildFocus, which prevents this view from taking @@ -6822,7 +6849,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((changed & VISIBILITY_MASK) != 0) { if (mParent instanceof ViewGroup) { - ((ViewGroup) mParent).onChildVisibilityChanged(this, (flags & VISIBILITY_MASK)); + ((ViewGroup) mParent).onChildVisibilityChanged(this, (changed & VISIBILITY_MASK), + (flags & VISIBILITY_MASK)); ((View) mParent).invalidate(true); } else if (mParent != null) { mParent.invalidateChild(this, null); @@ -7282,6 +7310,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The degrees of rotation. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getRotation() { return mTransformationInfo != null ? mTransformationInfo.mRotation : 0; } @@ -7323,6 +7352,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The degrees of Y rotation. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getRotationY() { return mTransformationInfo != null ? mTransformationInfo.mRotationY : 0; } @@ -7369,6 +7399,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The degrees of X rotation. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getRotationX() { return mTransformationInfo != null ? mTransformationInfo.mRotationX : 0; } @@ -7416,6 +7447,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getPivotY() * @return The scaling factor. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getScaleX() { return mTransformationInfo != null ? mTransformationInfo.mScaleX : 1; } @@ -7454,6 +7486,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getPivotY() * @return The scaling factor. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getScaleY() { return mTransformationInfo != null ? mTransformationInfo.mScaleY : 1; } @@ -7492,6 +7525,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getPivotY() * @return The x location of the pivot point. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getPivotX() { return mTransformationInfo != null ? mTransformationInfo.mPivotX : 0; } @@ -7536,6 +7570,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getPivotY() * @return The y location of the pivot point. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getPivotY() { return mTransformationInfo != null ? mTransformationInfo.mPivotY : 0; } @@ -7576,6 +7611,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p>By default this is 1.0f. * @return The opacity of the view. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getAlpha() { return mTransformationInfo != null ? mTransformationInfo.mAlpha : 1; } @@ -7589,6 +7625,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * equivalent to calling {@link #setLayerType(int, android.graphics.Paint)} and * setting a hardware layer.</p> * + * <p>Note that setting alpha to a translucent value (0 < alpha < 1) may have + * performance implications. It is generally best to use the alpha property sparingly and + * transiently, as in the case of fading animations.</p> + * * @param alpha The opacity of the view. * * @see #setLayerType(int, android.graphics.Paint) @@ -7886,6 +7926,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The visual x position of this view, in pixels. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getX() { return mLeft + (mTransformationInfo != null ? mTransformationInfo.mTranslationX : 0); } @@ -7908,6 +7949,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The visual y position of this view, in pixels. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getY() { return mTop + (mTransformationInfo != null ? mTransformationInfo.mTranslationY : 0); } @@ -7931,6 +7973,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The horizontal position of this view relative to its left position, in pixels. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getTranslationX() { return mTransformationInfo != null ? mTransformationInfo.mTranslationX : 0; } @@ -7967,6 +8010,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The vertical position of this view relative to its top position, * in pixels. */ + @ViewDebug.ExportedProperty(category = "drawing") public float getTranslationY() { return mTransformationInfo != null ? mTransformationInfo.mTranslationY : 0; } @@ -10373,7 +10417,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // we copy in child display lists into ours in drawChild() mRecreateDisplayList = true; if (mDisplayList == null) { - mDisplayList = mAttachInfo.mHardwareRenderer.createDisplayList(); + final String name = getClass().getSimpleName(); + mDisplayList = mAttachInfo.mHardwareRenderer.createDisplayList(name); // If we're creating a new display list, make sure our parent gets invalidated // since they will need to recreate their display list to account for this // new child display list. @@ -12654,6 +12699,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT); } + if (getAccessibilityNodeProvider() != null) { + throw new IllegalStateException("Views with AccessibilityNodeProvider" + + " can't have children."); + } + mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; @@ -12684,9 +12734,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * </p> * * <p> - * The actual mesurement work of a view is performed in + * The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only - * {@link #onMeasure(int, int)} can and must be overriden by subclasses. + * {@link #onMeasure(int, int)} can and must be overridden by subclasses. * </p> * * diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index dda695fcca91..d3af61841295 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -675,11 +675,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public void clearFocus() { - super.clearFocus(); - - // clear any child focus if it exists - if (mFocused != null) { + if (DBG) { + System.out.println(this + " clearFocus()"); + } + if (mFocused == null) { + super.clearFocus(); + } else { mFocused.clearFocus(); + mFocused = null; } } @@ -691,12 +694,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (DBG) { System.out.println(this + " unFocus()"); } - - super.unFocus(); - if (mFocused != null) { + if (mFocused == null) { + super.unFocus(); + } else { mFocused.unFocus(); + mFocused = null; } - mFocused = null; } /** @@ -888,18 +891,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Called when a view's visibility has changed. Notify the parent to take any appropriate + * action. + * + * @param child The view whose visibility has changed + * @param oldVisibility The previous visibility value (GONE, INVISIBLE, or VISIBLE). + * @param newVisibility The new visibility value (GONE, INVISIBLE, or VISIBLE). * @hide - * @param child - * @param visibility */ - protected void onChildVisibilityChanged(View child, int visibility) { + protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) { if (mTransition != null) { - if (visibility == VISIBLE) { - mTransition.showChild(this, child); + if (newVisibility == VISIBLE) { + mTransition.showChild(this, child, oldVisibility); } else { - mTransition.hideChild(this, child); - } - if (visibility != VISIBLE) { + mTransition.hideChild(this, child, newVisibility); // Only track this on disappearing views - appearing views are already visible // and don't need special handling during drawChild() if (mVisibilityChangingChildren == null) { @@ -914,7 +919,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // in all cases, for drags if (mCurrentDrag != null) { - if (visibility == VISIBLE) { + if (newVisibility == VISIBLE) { notifyChildOfDrag(child); } } @@ -3351,6 +3356,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { + if (getAccessibilityNodeProvider() != null) { + throw new IllegalStateException("Views with AccessibilityNodeProvider" + + " can't have children."); + } + if (mTransition != null) { // Don't prevent other add transitions from completing, but cancel remove // transitions to let them complete the process before we add to the container diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index 89a1ef26918d..0fdcd0f2d879 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -113,6 +113,10 @@ public class ViewPropertyAnimator { * on that list are added to the list of properties associated with that animator. */ ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>(); + private Runnable mPendingSetupAction; + private Runnable mPendingCleanupAction; + private Runnable mPendingOnStartAction; + private Runnable mPendingOnEndAction; /** * Constants used to associate a property being requested and the mechanism used to set @@ -199,6 +203,10 @@ public class ViewPropertyAnimator { */ private HashMap<Animator, PropertyBundle> mAnimatorMap = new HashMap<Animator, PropertyBundle>(); + private HashMap<Animator, Runnable> mAnimatorSetupMap; + private HashMap<Animator, Runnable> mAnimatorCleanupMap; + private HashMap<Animator, Runnable> mAnimatorOnStartMap; + private HashMap<Animator, Runnable> mAnimatorOnEndMap; /** * This is the information we need to set each property during the animation. @@ -614,6 +622,93 @@ public class ViewPropertyAnimator { } /** + * The View associated with this ViewPropertyAnimator will have its + * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to + * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation. This state + * is not persistent, either on the View or on this ViewPropertyAnimator: the layer type + * of the View will be restored when the animation ends to what it was when this method was + * called, and this setting on ViewPropertyAnimator is only valid for the next animation. + * Note that calling this method and then independently setting the layer type of the View + * (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will result + * in some inconsistency, including having the layer type restored to its pre-withLayer() + * value when the animation ends. + * + * @see View#setLayerType(int, android.graphics.Paint) + * @return This object, allowing calls to methods in this class to be chained. + */ + public ViewPropertyAnimator withLayer() { + mPendingSetupAction= new Runnable() { + @Override + public void run() { + mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + }; + final int currentLayerType = mView.getLayerType(); + mPendingCleanupAction = new Runnable() { + @Override + public void run() { + mView.setLayerType(currentLayerType, null); + } + }; + if (mAnimatorSetupMap == null) { + mAnimatorSetupMap = new HashMap<Animator, Runnable>(); + } + if (mAnimatorCleanupMap == null) { + mAnimatorCleanupMap = new HashMap<Animator, Runnable>(); + } + + return this; + } + + /** + * Specifies an action to take place when the next animation runs. If there is a + * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the + * action will run after that startDelay expires, when the actual animation begins. + * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate + * choreographing ViewPropertyAnimator animations with other animations or actions + * in the application. + * + * @param runnable The action to run when the next animation starts. + * @return This object, allowing calls to methods in this class to be chained. + */ + public ViewPropertyAnimator withStartAction(Runnable runnable) { + mPendingOnStartAction = runnable; + if (runnable != null && mAnimatorOnStartMap == null) { + mAnimatorOnStartMap = new HashMap<Animator, Runnable>(); + } + return this; + } + + /** + * Specifies an action to take place when the next animation ends. The action is only + * run if the animation ends normally; if the ViewPropertyAnimator is canceled during + * that animation, the runnable will not run. + * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate + * choreographing ViewPropertyAnimator animations with other animations or actions + * in the application. + * + * <p>For example, the following code animates a view to x=200 and then back to 0:</p> + * <pre> + * Runnable endAction = new Runnable() { + * public void run() { + * view.animate().x(0); + * } + * }; + * view.animate().x(200).onEnd(endAction); + * </pre> + * + * @param runnable The action to run when the next animation ends. + * @return This object, allowing calls to methods in this class to be chained. + */ + public ViewPropertyAnimator withEndAction(Runnable runnable) { + mPendingOnEndAction = runnable; + if (runnable != null && mAnimatorOnEndMap == null) { + mAnimatorOnEndMap = new HashMap<Animator, Runnable>(); + } + return this; + } + + /** * Starts the underlying Animator for a set of properties. We use a single animator that * simply runs from 0 to 1, and then use that fractional value to set each property * value accordingly. @@ -630,6 +725,22 @@ public class ViewPropertyAnimator { propertyMask |= nameValuesHolder.mNameConstant; } mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); + if (mPendingSetupAction != null) { + mAnimatorSetupMap.put(animator, mPendingSetupAction); + mPendingSetupAction = null; + } + if (mPendingCleanupAction != null) { + mAnimatorCleanupMap.put(animator, mPendingCleanupAction); + mPendingCleanupAction = null; + } + if (mPendingOnStartAction != null) { + mAnimatorOnStartMap.put(animator, mPendingOnStartAction); + mPendingOnStartAction = null; + } + if (mPendingOnEndAction != null) { + mAnimatorOnEndMap.put(animator, mPendingOnEndAction); + mPendingOnEndAction = null; + } animator.addUpdateListener(mAnimatorEventListener); animator.addListener(mAnimatorEventListener); if (mStartDelaySet) { @@ -800,6 +911,20 @@ public class ViewPropertyAnimator { implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { @Override public void onAnimationStart(Animator animation) { + if (mAnimatorSetupMap != null) { + Runnable r = mAnimatorSetupMap.get(animation); + if (r != null) { + r.run(); + } + mAnimatorSetupMap.remove(animation); + } + if (mAnimatorOnStartMap != null) { + Runnable r = mAnimatorOnStartMap.get(animation); + if (r != null) { + r.run(); + } + mAnimatorOnStartMap.remove(animation); + } if (mListener != null) { mListener.onAnimationStart(animation); } @@ -810,6 +935,9 @@ public class ViewPropertyAnimator { if (mListener != null) { mListener.onAnimationCancel(animation); } + if (mAnimatorOnEndMap != null) { + mAnimatorOnEndMap.remove(animation); + } } @Override @@ -824,6 +952,20 @@ public class ViewPropertyAnimator { if (mListener != null) { mListener.onAnimationEnd(animation); } + if (mAnimatorOnEndMap != null) { + Runnable r = mAnimatorOnEndMap.get(animation); + if (r != null) { + r.run(); + } + mAnimatorOnEndMap.remove(animation); + } + if (mAnimatorCleanupMap != null) { + Runnable r = mAnimatorCleanupMap.get(animation); + if (r != null) { + r.run(); + } + mAnimatorCleanupMap.remove(animation); + } mAnimatorMap.remove(animation); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1a4bdf4bc373..cbf4b5a21d7e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -18,11 +18,13 @@ package android.view; import android.Manifest; import android.animation.LayoutTransition; +import android.animation.ValueAnimator; import android.app.ActivityManagerNative; import android.content.ClipDescription; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -52,6 +54,7 @@ import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; +import android.util.LongSparseArray; import android.util.Pool; import android.util.Poolable; import android.util.PoolableManager; @@ -81,7 +84,6 @@ import com.android.internal.view.RootViewSurfaceTaker; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -139,6 +141,10 @@ public final class ViewRootImpl extends Handler implements ViewParent, static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList<ComponentCallbacks>(); + private static boolean sUseRenderThread = false; + private static boolean sRenderThreadQueried = false; + private static final Object[] sRenderThreadQueryLock = new Object[0]; + long mLastTrackballTime = 0; final TrackballAxis mTrackballAxisX = new TrackballAxis(); final TrackballAxis mTrackballAxisY = new TrackballAxis(); @@ -168,6 +174,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, View mView; View mFocusedView; View mRealFocusedView; // this is not set to null in touch mode + View mOldFocusedView; int mViewVisibility; boolean mAppVisible = true; int mOrigWindowType = -1; @@ -301,6 +308,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent; + AccessibilityPrefetchStrategy mAccessibilityPrefetchStrategy; + private final int mDensity; /** @@ -315,8 +324,11 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (!mInitialized) { try { InputMethodManager imm = InputMethodManager.getInstance(mainLooper); - sWindowSession = Display.getWindowManager().openSession( + IWindowManager windowManager = Display.getWindowManager(); + sWindowSession = windowManager.openSession( imm.getClient(), imm.getInputContext()); + float animatorScale = windowManager.getAnimationScale(2); + ValueAnimator.setDurationScale(animatorScale); mInitialized = true; } catch (RemoteException e) { } @@ -377,6 +389,31 @@ public final class ViewRootImpl extends Handler implements ViewParent, mChoreographer = Choreographer.getInstance(); } + /** + * @return True if the application requests the use of a separate render thread, + * false otherwise + */ + private static boolean isRenderThreadRequested(Context context) { + synchronized (sRenderThreadQueryLock) { + if (!sRenderThreadQueried) { + final PackageManager packageManager = context.getPackageManager(); + final String packageName = context.getApplicationInfo().packageName; + try { + ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, + PackageManager.GET_META_DATA); + if (applicationInfo.metaData != null) { + sUseRenderThread = applicationInfo.metaData.getBoolean( + "android.graphics.renderThread", false); + } + } catch (PackageManager.NameNotFoundException e) { + } finally { + sRenderThreadQueried = true; + } + } + return sUseRenderThread; + } + } + public static void addFirstDrawHandler(Runnable callback) { synchronized (sFirstDrawHandlers) { if (!sFirstDrawComplete) { @@ -447,7 +484,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, // If the application owns the surface, don't enable hardware acceleration if (mSurfaceHolder == null) { - enableHardwareAcceleration(attrs); + enableHardwareAcceleration(mView.getContext(), attrs); } boolean restore = false; @@ -607,7 +644,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) { + private void enableHardwareAcceleration(Context context, WindowManager.LayoutParams attrs) { mAttachInfo.mHardwareAccelerated = false; mAttachInfo.mHardwareAccelerationRequested = false; @@ -640,20 +677,27 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled && forceHwAccelerated)) { // Don't enable hardware acceleration when we're not on the main thread - if (!HardwareRenderer.sSystemRendererDisabled - && Looper.getMainLooper() != Looper.myLooper()) { - Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware " + if (!HardwareRenderer.sSystemRendererDisabled && + Looper.getMainLooper() != Looper.myLooper()) { + Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware " + "acceleration outside of the main thread, aborting"); return; } - final boolean translucent = attrs.format != PixelFormat.OPAQUE; + boolean renderThread = isRenderThreadRequested(context); + if (renderThread) { + Log.i(HardwareRenderer.LOG_TAG, "Render threat initiated"); + } + if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroy(true); - } + } + + final boolean translucent = attrs.format != PixelFormat.OPAQUE; mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent); mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested = mAttachInfo.mHardwareRenderer != null; + } else if (fakeHwAccelerated) { // The window had wanted to use hardware acceleration, but this // is not allowed in its process. By setting this flag, it can @@ -2226,32 +2270,33 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void requestChildFocus(View child, View focused) { checkThread(); - if (mFocusedView != focused) { - mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused); - scheduleTraversals(); + + if (DEBUG_INPUT_RESIZE) { + Log.v(TAG, "Request child focus: focus now " + focused); } + + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, focused); + scheduleTraversals(); + mFocusedView = mRealFocusedView = focused; - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now " - + mFocusedView); } public void clearChildFocus(View child) { checkThread(); - View oldFocus = mFocusedView; + if (DEBUG_INPUT_RESIZE) { + Log.v(TAG, "Clearing child focus"); + } - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Clearing child focus"); - mFocusedView = mRealFocusedView = null; - if (mView != null && !mView.hasFocus()) { - // If a view gets the focus, the listener will be invoked from requestChildFocus() - if (!mView.requestFocus(View.FOCUS_FORWARD)) { - mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null); - } - } else if (oldFocus != null) { - mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null); + mOldFocusedView = mFocusedView; + + // Invoke the listener only if there is no view to take focus + if (focusSearch(null, View.FOCUS_FORWARD) == null) { + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, null); } - } + mFocusedView = mRealFocusedView = null; + } public void focusableViewAvailable(View v) { checkThread(); @@ -2724,6 +2769,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, mView.unFocus(); mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null); mFocusedView = null; + mOldFocusedView = null; return true; } } @@ -3438,11 +3484,11 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (args.localChanges != 0) { if (mAttachInfo != null) { mAttachInfo.mSystemUiVisibility = - (mAttachInfo.mSystemUiVisibility&~args.localChanges) - | (args.localValue&args.localChanges); + (mAttachInfo.mSystemUiVisibility & ~args.localChanges) | + (args.localValue & args.localChanges); + mAttachInfo.mRecomputeGlobalAttributes = true; } mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges); - mAttachInfo.mRecomputeGlobalAttributes = true; scheduleTraversals(); } mView.dispatchSystemUiVisibilityChanged(args.globalVisibility); @@ -3480,6 +3526,17 @@ public final class ViewRootImpl extends Handler implements ViewParent, return mAccessibilityInteractionController; } + public AccessibilityPrefetchStrategy getAccessibilityPrefetchStrategy() { + if (mView == null) { + throw new IllegalStateException("getAccessibilityPrefetchStrategy" + + " called when there is no mView"); + } + if (mAccessibilityPrefetchStrategy == null) { + mAccessibilityPrefetchStrategy = new AccessibilityPrefetchStrategy(); + } + return mAccessibilityPrefetchStrategy; + } + private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { @@ -3585,7 +3642,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, mView.debug(); } - public void dumpGfxInfo(PrintWriter pw, int[] info) { + public void dumpGfxInfo(int[] info) { if (mView != null) { getGfxInfo(mView, info); } else { @@ -3697,7 +3754,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, * Represents a pending input event that is waiting in a queue. * * Input events are processed in serial order by the timestamp specified by - * {@link InputEvent#getEventTime()}. In general, the input dispatcher delivers + * {@link InputEvent#getEventTimeNano()}. In general, the input dispatcher delivers * one input event to the application at a time and waits for the application * to finish handling it before delivering the next one. * @@ -3706,7 +3763,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, * needing a queue on the application's side. */ private static final class QueuedInputEvent { - public static final int FLAG_DELIVER_POST_IME = 1 << 0; + public static final int FLAG_DELIVER_POST_IME = 1; public QueuedInputEvent mNext; @@ -3980,6 +4037,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (mView == null) { return false; } + getAccessibilityPrefetchStrategy().onAccessibilityEvent(event); mAccessibilityManager.sendAccessibilityEvent(event); return true; } @@ -4539,6 +4597,13 @@ public final class ViewRootImpl extends Handler implements ViewParent, viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId, interactionId, callback, interrogatingPid, interrogatingTid); + } else { + // We cannot make the call and notify the caller so it does not wait. + try { + callback.setFindAccessibilityNodeInfosResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } } } @@ -4550,28 +4615,49 @@ public final class ViewRootImpl extends Handler implements ViewParent, viewRootImpl.getAccessibilityInteractionController() .performAccessibilityActionClientThread(accessibilityNodeId, action, interactionId, callback, interogatingPid, interrogatingTid); + } else { + // We cannot make the call and notify the caller so it does not + try { + callback.setPerformAccessibilityActionResult(false, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } } } - public void findAccessibilityNodeInfoByViewId(int viewId, + public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() - .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback, - interrogatingPid, interrogatingTid); + .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId, + interactionId, callback, interrogatingPid, interrogatingTid); + } else { + // We cannot make the call and notify the caller so it does not + try { + callback.setFindAccessibilityNodeInfoResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } } } - public void findAccessibilityNodeInfosByText(String text, long accessibilityNodeId, + public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() - .findAccessibilityNodeInfosByTextClientThread(text, accessibilityNodeId, + .findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text, interactionId, callback, interrogatingPid, interrogatingTid); + } else { + // We cannot make the call and notify the caller so it does not + try { + callback.setFindAccessibilityNodeInfosResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } } } } @@ -4649,6 +4735,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, long interrogatingTid) { Message message = Message.obtain(); message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID; + message.arg1 = interrogatingPid; SomeArgs args = mPool.acquire(); args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); @@ -4671,40 +4758,47 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { SomeArgs args = (SomeArgs) message.obj; + final int interrogatingPid = message.arg1; final int accessibilityViewId = args.argi1; final int virtualDescendantId = args.argi2; final int interactionId = args.argi3; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; mPool.release(args); - AccessibilityNodeInfo info = null; + List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; + infos.clear(); try { View target = findViewByAccessibilityId(accessibilityViewId); if (target != null && target.getVisibility() == View.VISIBLE) { AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); if (provider != null) { - info = provider.createAccessibilityNodeInfo(virtualDescendantId); + infos.add(provider.createAccessibilityNodeInfo(virtualDescendantId)); } else if (virtualDescendantId == View.NO_ID) { - info = target.createAccessibilityNodeInfo(); + getAccessibilityPrefetchStrategy().prefetchAccessibilityNodeInfos( + interrogatingPid, target, infos); } } } finally { try { - callback.setFindAccessibilityNodeInfoResult(info, interactionId); + callback.setFindAccessibilityNodeInfosResult(infos, interactionId); + infos.clear(); } catch (RemoteException re) { /* ignore - the other side will time out */ } } } - public void findAccessibilityNodeInfoByViewIdClientThread(int viewId, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, - long interrogatingTid) { + public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId, + int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, + int interrogatingPid, long interrogatingTid) { Message message = Message.obtain(); message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID; - message.arg1 = viewId; - message.arg2 = interactionId; - message.obj = callback; + message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); + SomeArgs args = mPool.acquire(); + args.argi1 = viewId; + args.argi2 = interactionId; + args.arg1 = callback; + message.obj = args; // If the interrogation is performed by the same thread as the main UI // thread in this process, set the message as a static reference so // after this call completes the same thread but in the interrogating @@ -4720,17 +4814,26 @@ public final class ViewRootImpl extends Handler implements ViewParent, } public void findAccessibilityNodeInfoByViewIdUiThread(Message message) { - final int viewId = message.arg1; - final int interactionId = message.arg2; + final int accessibilityViewId = message.arg1; + SomeArgs args = (SomeArgs) message.obj; + final int viewId = args.argi1; + final int interactionId = args.argi2; final IAccessibilityInteractionConnectionCallback callback = - (IAccessibilityInteractionConnectionCallback) message.obj; - + (IAccessibilityInteractionConnectionCallback) args.arg1; + mPool.release(args); AccessibilityNodeInfo info = null; try { - View root = ViewRootImpl.this.mView; - View target = root.findViewById(viewId); - if (target != null && target.getVisibility() == View.VISIBLE) { - info = target.createAccessibilityNodeInfo(); + View root = null; + if (accessibilityViewId != View.NO_ID) { + root = findViewByAccessibilityId(accessibilityViewId); + } else { + root = ViewRootImpl.this.mView; + } + if (root != null) { + View target = root.findViewById(viewId); + if (target != null && target.getVisibility() == View.VISIBLE) { + info = target.createAccessibilityNodeInfo(); + } } } finally { try { @@ -4741,8 +4844,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - public void findAccessibilityNodeInfosByTextClientThread(String text, - long accessibilityNodeId, int interactionId, + public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, + String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid) { Message message = Message.obtain(); @@ -4779,7 +4882,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, mPool.release(args); List<AccessibilityNodeInfo> infos = null; try { - View target = null; + View target; if (accessibilityViewId != View.NO_ID) { target = findViewByAccessibilityId(accessibilityViewId); } else { @@ -4934,4 +5037,88 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } } + + /** + * This class encapsulates a prefetching strategy for the accessibility APIs for + * querying window content.It is responsible to prefetch a batch of + * AccessibilityNodeInfos in addition to the one for a requested node. It caches + * the ids of the prefeteched nodes such that they are fetched only once. + */ + class AccessibilityPrefetchStrategy { + private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 100; + + // We need to keep track of what we have sent for each interrogating + // process. Usually there will be only one such process but we + // should support the general case. Note that the accessibility event + // stream will take care of clearing caches of querying processes that + // are not longer alive, so we do not waste memory. + private final LongSparseArray<AccessibilityNodeInfoCache> mAccessibilityNodeInfoCaches = + new LongSparseArray<AccessibilityNodeInfoCache>(); + + private AccessibilityNodeInfoCache getCacheForInterrogatingPid(long interrogatingPid) { + AccessibilityNodeInfoCache cache = mAccessibilityNodeInfoCaches.get(interrogatingPid); + if (cache == null) { + cache = AccessibilityNodeInfoCache.newAccessibilityNodeInfoCache(); + mAccessibilityNodeInfoCaches.put(interrogatingPid, cache); + } + return cache; + } + + public void onAccessibilityEvent(AccessibilityEvent event) { + final int cacheCount = mAccessibilityNodeInfoCaches.size(); + for (int i = 0; i < cacheCount; i++) { + AccessibilityNodeInfoCache cache = mAccessibilityNodeInfoCaches.valueAt(i); + cache.onAccessibilityEvent(event); + } + } + + public void prefetchAccessibilityNodeInfos(long interrogatingPid, View root, + List<AccessibilityNodeInfo> outInfos) { + addAndCacheNotCachedNodeInfo(interrogatingPid, root, outInfos); + addAndCacheNotCachedPredecessorInfos(interrogatingPid, root, outInfos); + addAndCacheNotCachedDescendantInfos(interrogatingPid, root, outInfos); + } + + private void addAndCacheNotCachedNodeInfo(long interrogatingPid, + View view, List<AccessibilityNodeInfo> outInfos) { + final long accessibilityNodeId = AccessibilityNodeInfo.makeNodeId( + view.getAccessibilityViewId(), View.NO_ID); + AccessibilityNodeInfoCache cache = getCacheForInterrogatingPid(interrogatingPid); + if (!cache.containsKey(accessibilityNodeId)) { + // Account for the ids of the fetched infos. The infos will be + // cached in the window querying process. We just need to know + // which infos are cached to avoid fetching a cached one again. + cache.put(accessibilityNodeId, null); + outInfos.add(view.createAccessibilityNodeInfo()); + } + } + + private void addAndCacheNotCachedPredecessorInfos(long interrogatingPid, View view, + List<AccessibilityNodeInfo> outInfos) { + ViewParent predecessor = view.getParent(); + while (predecessor instanceof View + && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + View predecessorView = (View) predecessor; + addAndCacheNotCachedNodeInfo(interrogatingPid, predecessorView, outInfos); + predecessor = predecessor.getParent(); + } + } + + private void addAndCacheNotCachedDescendantInfos(long interrogatingPid, View view, + List<AccessibilityNodeInfo> outInfos) { + if (outInfos.size() > MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE + || view.getAccessibilityNodeProvider() != null) { + return; + } + addAndCacheNotCachedNodeInfo(interrogatingPid, view, outInfos); + if (view instanceof ViewGroup) { + ViewGroup rootGroup = (ViewGroup) view; + final int childCount = rootGroup.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = rootGroup.getChildAt(i); + addAndCacheNotCachedDescendantInfos(interrogatingPid, child, outInfos); + } + } + } + } } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index d7113374bdc3..6bdc4e865660 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -490,7 +490,7 @@ public class WindowManagerImpl implements WindowManager { for (int i = 0; i < count; i++) { ViewRootImpl root = mRoots[i]; - root.dumpGfxInfo(pw, info); + root.dumpGfxInfo(info); String name = root.getClass().getName() + '@' + Integer.toHexString(hashCode()); diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 6ec2e8d22563..75267bb870f0 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -536,7 +536,7 @@ public interface WindowManagerPolicy { /** * Return the available screen width that we should report for the * configuration. This must be no larger than - * {@link #getNonDecorDisplayWidth(int, int)}; it may be smaller than + * {@link #getNonDecorDisplayWidth(int, int, int)}; it may be smaller than * that to account for more transient decoration like a status bar. */ public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation); @@ -544,7 +544,7 @@ public interface WindowManagerPolicy { /** * Return the available screen height that we should report for the * configuration. This must be no larger than - * {@link #getNonDecorDisplayHeight(int, int)}; it may be smaller than + * {@link #getNonDecorDisplayHeight(int, int, int)}; it may be smaller than * that to account for more transient decoration like a status bar. */ public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation); @@ -754,12 +754,8 @@ public interface WindowManagerPolicy { * Called when layout of the windows is finished. After this function has * returned, all windows given to layoutWindow() <em>must</em> have had a * frame assigned. - * - * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT}, - * {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER}, - * or {@link #FINISH_LAYOUT_REDO_ANIM}. */ - public int finishLayoutLw(); + public void finishLayoutLw(); /** Layout state may have changed (so another layout will be performed) */ static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001; @@ -822,7 +818,7 @@ public interface WindowManagerPolicy { public interface ScreenOnListener { void onScreenOn(); - }; + } /** * Called when the power manager would like to turn the screen on. diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 95c070cf5b2c..072fdd86ed08 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -24,7 +24,9 @@ import android.os.SystemClock; import android.util.Log; import android.util.LongSparseArray; import android.util.SparseArray; +import android.view.AccessibilityNodeInfoCache; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -97,6 +99,11 @@ public final class AccessibilityInteractionClient private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = new SparseArray<IAccessibilityServiceConnection>(); + // The connection cache is shared between all interrogating threads since + // at any given time there is only one window allowing querying. + private static final AccessibilityNodeInfoCache sAccessibilityNodeInfoCache = + AccessibilityNodeInfoCache.newSynchronizedAccessibilityNodeInfoCache(); + /** * @return The client for the current thread. */ @@ -145,7 +152,9 @@ public final class AccessibilityInteractionClient * Finds an {@link AccessibilityNodeInfo} by accessibility id. * * @param connectionId The id of a connection for interacting with the system. - * @param accessibilityWindowId A unique window id. + * @param accessibilityWindowId A unique window id. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID} + * to query the currently active window. * @param accessibilityNodeId A unique node accessibility id * (accessibility view and virtual descendant id). * @return An {@link AccessibilityNodeInfo} if found, null otherwise. @@ -155,16 +164,22 @@ public final class AccessibilityInteractionClient try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { + AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get(accessibilityNodeId); + if (cachedInfo != null) { + return cachedInfo; + } final int interactionId = mInteractionIdCounter.getAndIncrement(); final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId( accessibilityWindowId, accessibilityNodeId, interactionId, this, Thread.currentThread().getId()); // If the scale is zero the call has failed. if (windowScale > 0) { - AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( + List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - finalizeAccessibilityNodeInfo(info, connectionId, windowScale); - return info; + finalizeAccessibilityNodeInfos(infos, connectionId, windowScale); + if (infos != null && !infos.isEmpty()) { + return infos.get(0); + } } } else { if (DEBUG) { @@ -181,22 +196,30 @@ public final class AccessibilityInteractionClient } /** - * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed - * in the currently active window and starts from the root View in the window. + * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in + * the window whose id is specified and starts from the node whose accessibility + * id is specified. * * @param connectionId The id of a connection for interacting with the system. + * @param accessibilityWindowId A unique window id. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id from where to start the search. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID} + * to start from the root. * @param viewId The id of the view. * @return An {@link AccessibilityNodeInfo} if found, null otherwise. */ - public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int connectionId, - int viewId) { + public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int connectionId, + int accessibilityWindowId, long accessibilityNodeId, int viewId) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final float windowScale = - connection.findAccessibilityNodeInfoByViewIdInActiveWindow(viewId, - interactionId, this, Thread.currentThread().getId()); + connection.findAccessibilityNodeInfoByViewId(accessibilityWindowId, + accessibilityNodeId, viewId, interactionId, this, + Thread.currentThread().getId()); // If the scale is zero the call has failed. if (windowScale > 0) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( @@ -220,64 +243,27 @@ public final class AccessibilityInteractionClient /** * Finds {@link AccessibilityNodeInfo}s by View text. The match is case - * insensitive containment. The search is performed in the currently - * active window and starts from the root View in the window. - * - * @param connectionId The id of a connection for interacting with the system. - * @param text The searched text. - * @return A list of found {@link AccessibilityNodeInfo}s. - */ - public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow( - int connectionId, String text) { - try { - IAccessibilityServiceConnection connection = getConnection(connectionId); - if (connection != null) { - final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = - connection.findAccessibilityNodeInfosByTextInActiveWindow(text, - interactionId, this, Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { - List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( - interactionId); - finalizeAccessibilityNodeInfos(infos, connectionId, windowScale); - return infos; - } - } else { - if (DEBUG) { - Log.w(LOG_TAG, "No connection for connection id: " + connectionId); - } - } - } catch (RemoteException re) { - if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote" - + " findAccessibilityNodeInfosByViewTextInActiveWindow", re); - } - } - return null; - } - - /** - * Finds {@link AccessibilityNodeInfo}s by View text. The match is case * insensitive containment. The search is performed in the window whose - * id is specified and starts from the View whose accessibility id is + * id is specified and starts from the node whose accessibility id is * specified. * * @param connectionId The id of a connection for interacting with the system. + * @param accessibilityWindowId A unique window id. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id from where to start the search. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID} * @param text The searched text. - * @param accessibilityWindowId A unique window id. - * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id) from - * where to start the search. Use {@link android.view.View#NO_ID} to start from the root. * @return A list of found {@link AccessibilityNodeInfo}s. */ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId, - String text, int accessibilityWindowId, long accessibilityNodeId) { + int accessibilityWindowId, long accessibilityNodeId, String text) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.findAccessibilityNodeInfosByText(text, - accessibilityWindowId, accessibilityNodeId, interactionId, this, + final float windowScale = connection.findAccessibilityNodeInfosByText( + accessibilityWindowId, accessibilityNodeId, text, interactionId, this, Thread.currentThread().getId()); // If the scale is zero the call has failed. if (windowScale > 0) { @@ -304,7 +290,9 @@ public final class AccessibilityInteractionClient * Performs an accessibility action on an {@link AccessibilityNodeInfo}. * * @param connectionId The id of a connection for interacting with the system. - * @param accessibilityWindowId The id of the window. + * @param accessibilityWindowId A unique window id. Use + * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID} + * to query the currently active window. * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id). * @param action The action to perform. * @return Whether the action was performed. @@ -319,7 +307,7 @@ public final class AccessibilityInteractionClient accessibilityWindowId, accessibilityNodeId, action, interactionId, this, Thread.currentThread().getId()); if (success) { - return getPerformAccessibilityActionResult(interactionId); + return getPerformAccessibilityActionResultAndClear(interactionId); } } else { if (DEBUG) { @@ -334,6 +322,24 @@ public final class AccessibilityInteractionClient return false; } + public void clearCache() { + if (DEBUG) { + Log.w(LOG_TAG, "clearCache()"); + } + sAccessibilityNodeInfoCache.clear(); + } + + public void removeCachedNode(long accessibilityNodeId) { + if (DEBUG) { + Log.w(LOG_TAG, "removeCachedNode(" + accessibilityNodeId +")"); + } + sAccessibilityNodeInfoCache.remove(accessibilityNodeId); + } + + public void onAccessibilityEvent(AccessibilityEvent event) { + sAccessibilityNodeInfoCache.onAccessibilityEvent(event); + } + /** * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}. * @@ -358,6 +364,9 @@ public final class AccessibilityInteractionClient if (interactionId > mInteractionId) { mFindAccessibilityNodeInfoResult = info; mInteractionId = interactionId; + if (info != null) { + sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info); + } } mInstanceLock.notifyAll(); } @@ -386,8 +395,20 @@ public final class AccessibilityInteractionClient int interactionId) { synchronized (mInstanceLock) { if (interactionId > mInteractionId) { - mFindAccessibilityNodeInfosResult = infos; + // If the call is not an IPC, i.e. it is made from the same process, we need to + // instantiate new result list to avoid passing internal instances to clients. + final boolean isIpcCall = (queryLocalInterface(getInterfaceDescriptor()) == null); + if (!isIpcCall) { + mFindAccessibilityNodeInfosResult = new ArrayList<AccessibilityNodeInfo>(infos); + } else { + mFindAccessibilityNodeInfosResult = infos; + } mInteractionId = interactionId; + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i ++) { + AccessibilityNodeInfo info = infos.get(i); + sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info); + } } mInstanceLock.notifyAll(); } @@ -399,7 +420,7 @@ public final class AccessibilityInteractionClient * @param interactionId The interaction id to match the result with the request. * @return Whether the action was performed. */ - private boolean getPerformAccessibilityActionResult(int interactionId) { + private boolean getPerformAccessibilityActionResultAndClear(int interactionId) { synchronized (mInstanceLock) { final boolean success = waitForResultTimedLocked(interactionId); final boolean result = success ? mPerformAccessibilityActionResult : false; diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 6939c2cf1ddf..d7d67928e158 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -380,8 +380,8 @@ public class AccessibilityNodeInfo implements Parcelable { return Collections.emptyList(); } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfosByText(mConnectionId, text, mWindowId, - mSourceNodeId); + return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId, + text); } /** @@ -903,6 +903,17 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the id of the source node. + * + * @return The id. + * + * @hide + */ + public long getSourceNodeId() { + return mSourceNodeId; + } + + /** * Sets if this instance is sealed. * * @param sealed Whether is sealed. diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 07aeb9ae5b7d..b60f50eb5d9d 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -564,6 +564,17 @@ public class AccessibilityRecord { } /** + * Gets the id of the source node. + * + * @return The id. + * + * @hide + */ + public long getSourceNodeId() { + return mSourceNodeId; + } + + /** * Sets the unique id of the IAccessibilityServiceConnection over which * this instance can send requests to the system. * diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index a90c427f4ab8..ae6869cad9f5 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -31,13 +31,13 @@ oneway interface IAccessibilityInteractionConnection { IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid); - void findAccessibilityNodeInfoByViewId(int id, int interactionId, + void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int id, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid); - void findAccessibilityNodeInfosByText(String text, long accessibilityNodeId, - int interactionId, IAccessibilityInteractionConnectionCallback callback, - int interrogatingPid, long interrogatingTid); + void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, + long interrogatingTid); void performAccessibilityAction(long accessibilityNodeId, int action, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index c3794bec3ef0..320c75da0233 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -49,5 +49,7 @@ interface IAccessibilityManager { void removeAccessibilityInteractionConnection(IWindow windowToken); - void registerEventListener(IEventListener client); + void registerUiTestAutomationService(IEventListener listener, in AccessibilityServiceInfo info); + + void unregisterUiTestAutomationService(IEventListener listener); } diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java index 30bbb04b72d3..ee9b9492ebeb 100644 --- a/core/java/android/webkit/Network.java +++ b/core/java/android/webkit/Network.java @@ -169,7 +169,9 @@ class Network { if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) return; - NetworkInfo info = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); + final ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo info = connManager.getActiveNetworkInfo(); if (info != null) mRoaming = info.isRoaming(); }; diff --git a/core/java/android/webkit/WebCoreThreadWatchdog.java b/core/java/android/webkit/WebCoreThreadWatchdog.java index d100260c0616..0541d5db4157 100644 --- a/core/java/android/webkit/WebCoreThreadWatchdog.java +++ b/core/java/android/webkit/WebCoreThreadWatchdog.java @@ -40,9 +40,6 @@ class WebCoreThreadWatchdog implements Runnable { // WebCore thread unresponsive. private static final int TIMED_OUT = 101; - // Message to tell the Watchdog thread to terminate. - private static final int QUIT = 102; - // Wait 10s after hearing back from the WebCore thread before checking it's still alive. private static final int HEARTBEAT_PERIOD = 10 * 1000; @@ -57,7 +54,6 @@ class WebCoreThreadWatchdog implements Runnable { private Handler mWebCoreThreadHandler; private Handler mHandler; private boolean mPaused; - private boolean mPendingQuit; private static WebCoreThreadWatchdog sInstance; @@ -88,12 +84,6 @@ class WebCoreThreadWatchdog implements Runnable { } } - public synchronized static void quit() { - if (sInstance != null) { - sInstance.quitWatchdog(); - } - } - private void setContext(Context context) { mContext = context; } @@ -103,19 +93,6 @@ class WebCoreThreadWatchdog implements Runnable { mWebCoreThreadHandler = webCoreThreadHandler; } - private void quitWatchdog() { - if (mHandler == null) { - // The thread hasn't started yet, so set a flag to stop it starting. - mPendingQuit = true; - return; - } - // Clear any pending messages, and then post a quit to the WatchDog handler. - mHandler.removeMessages(TIMED_OUT); - mHandler.removeMessages(IS_ALIVE); - mWebCoreThreadHandler.removeMessages(EventHub.HEARTBEAT); - mHandler.obtainMessage(QUIT).sendToTarget(); - } - private void pauseWatchdog() { mPaused = true; @@ -146,12 +123,8 @@ class WebCoreThreadWatchdog implements Runnable { mHandler.sendMessageDelayed(mHandler.obtainMessage(TIMED_OUT), TIMEOUT_PERIOD); } - private boolean createHandler() { + private void createHandler() { synchronized (WebCoreThreadWatchdog.class) { - if (mPendingQuit) { - return false; - } - mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -206,15 +179,9 @@ class WebCoreThreadWatchdog implements Runnable { .setIcon(android.R.drawable.ic_dialog_alert) .show(); break; - - case QUIT: - Looper.myLooper().quit(); - break; } } }; - - return true; } } @@ -222,9 +189,7 @@ class WebCoreThreadWatchdog implements Runnable { public void run() { Looper.prepare(); - if (!createHandler()) { - return; - } + createHandler(); // Send the initial control to WebViewCore and start the timeout timer as long as we aren't // paused. diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index cc8eef26055d..fbafc648fb20 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -367,12 +367,52 @@ public class WebView extends AbsoluteLayout private class WebViewInputConnection extends BaseInputConnection { // Used for mapping characters to keys typed. private KeyCharacterMap mKeyCharacterMap; + private boolean mIsKeySentByMe; public WebViewInputConnection() { super(WebView.this, true); } @Override + public boolean sendKeyEvent(KeyEvent event) { + // Latin IME occasionally sends delete codes directly using + // sendKeyEvents. WebViewInputConnection should treat this + // as a deleteSurroundingText. + if (!mIsKeySentByMe + && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + Editable editable = getEditable(); + int selectionStart = Selection.getSelectionStart(editable); + int selectionEnd = Selection.getSelectionEnd(editable); + if (selectionEnd > 0 && (selectionStart == selectionEnd)) { + int action = event.getAction(); + if (action == KeyEvent.ACTION_UP) { + return deleteSurroundingText(1, 0); + } else if (action == KeyEvent.ACTION_DOWN) { + return true; // the delete will happen in ACTION_UP + } + } + } + return super.sendKeyEvent(event); + } + + public void setTextAndKeepSelection(CharSequence text) { + Editable editable = getEditable(); + int selectionStart = Selection.getSelectionStart(editable); + int selectionEnd = Selection.getSelectionEnd(editable); + editable.replace(0, editable.length(), text); + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + // Since the text has changed, do not allow the IME to replace the + // existing text as though it were a completion. + imm.restartInput(WebView.this); + } + // Keep the previous selection. + selectionStart = Math.min(selectionStart, editable.length()); + selectionEnd = Math.min(selectionEnd, editable.length()); + setSelection(selectionStart, selectionEnd); + } + + @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { Editable editable = getEditable(); int start = getComposingSpanStart(editable); @@ -393,7 +433,8 @@ public class WebView extends AbsoluteLayout @Override public boolean commitText(CharSequence text, int newCursorPosition) { setComposingText(text, newCursorPosition); - finishComposingText(); + int cursorPosition = Selection.getSelectionEnd(getEditable()); + setComposingRegion(cursorPosition, cursorPosition); return true; } @@ -417,6 +458,7 @@ public class WebView extends AbsoluteLayout * @param text The new text to replace the changed text. */ private void setNewText(int start, int end, CharSequence text) { + mIsKeySentByMe = true; Editable editable = getEditable(); CharSequence original = editable.subSequence(start, end); boolean isCharacterAdd = false; @@ -434,10 +476,8 @@ public class WebView extends AbsoluteLayout } if (isCharacterAdd) { sendCharacter(text.charAt(textLength - 1)); - mTextGeneration++; } else if (isCharacterDelete) { sendDeleteKey(); - mTextGeneration++; } else if (textLength != originalLength || !TextUtils.regionMatches(text, 0, original, 0, textLength)) { @@ -447,6 +487,7 @@ public class WebView extends AbsoluteLayout REPLACE_TEXT, start, end, text.toString()); mPrivateHandler.sendMessage(replaceMessage); } + mIsKeySentByMe = false; } /** @@ -509,7 +550,7 @@ public class WebView extends AbsoluteLayout private final RectF mVisibleContentRect = new RectF(); private boolean mGLViewportEmpty = false; WebViewInputConnection mInputConnection = null; - + private int mFieldPointer; /** * Transportation object for returning WebView across thread boundaries. @@ -789,14 +830,21 @@ public class WebView extends AbsoluteLayout // know to handle Shift and arrows natively first private boolean mAccessibilityScriptInjected; - static final boolean USE_JAVA_TEXT_SELECTION = true; - static final boolean DEBUG_TEXT_HANDLES = false; - private Region mTextSelectionRegion = new Region(); - private Paint mTextSelectionPaint; private Drawable mSelectHandleLeft; private Drawable mSelectHandleRight; + private Rect mSelectCursorBase = new Rect(); + private int mSelectCursorBaseLayerId; + private Rect mSelectCursorExtent = new Rect(); + private int mSelectCursorExtentLayerId; + private Rect mSelectDraggingCursor; + private Point mSelectDraggingOffset = new Point(); + static final int HANDLE_ID_START = 0; + static final int HANDLE_ID_END = 1; + static final int HANDLE_ID_BASE = 2; + static final int HANDLE_ID_EXTENT = 3; static boolean sDisableNavcache = false; + static boolean sEnableWebTextView = false; // the color used to highlight the touch rectangles static final int HIGHLIGHT_COLOR = 0x6633b5e5; // the region indicating where the user touched on the screen @@ -1418,7 +1466,6 @@ public class WebView extends AbsoluteLayout private void init() { OnTrimMemoryListener.init(getContext()); sDisableNavcache = nativeDisableNavcache(); - setWillNotDraw(false); setFocusable(true); setFocusableInTouchMode(true); @@ -2607,7 +2654,7 @@ public class WebView extends AbsoluteLayout checkThread(); mContentWidth = 0; mContentHeight = 0; - setBaseLayer(0, null, false, false, false); + setBaseLayer(0, null, false, false); mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); } @@ -2656,12 +2703,6 @@ public class WebView extends AbsoluteLayout return mZoomManager.getScale(); } - // Called by JNI. Returns the scale to apply to the text selection handles - /* package */ float getTextHandleScale() { - float density = mContext.getResources().getDisplayMetrics().density; - return density / getScale(); - } - /** * Compute the reading level scale of the WebView * @param scale The current scale. @@ -2781,6 +2822,34 @@ public class WebView extends AbsoluteLayout return result; } + int getBlockLeftEdge(int x, int y, float readingScale) { + if (!sDisableNavcache) { + return nativeGetBlockLeftEdge(x, y, readingScale); + } + + float invReadingScale = 1.0f / readingScale; + int readingWidth = (int) (getViewWidth() * invReadingScale); + int left = NO_LEFTEDGE; + if (mFocusedNode != null) { + final int length = mFocusedNode.mEnclosingParentRects.length; + for (int i = 0; i < length; i++) { + Rect rect = mFocusedNode.mEnclosingParentRects[i]; + if (rect.width() < mFocusedNode.mHitTestSlop) { + // ignore bounding boxes that are too small + continue; + } else if (left != NO_LEFTEDGE && rect.width() > readingWidth) { + // stop when bounding box doesn't fit the screen width + // at reading scale + break; + } + + left = rect.left; + } + } + + return left; + } + // Called by JNI when the DOM has changed the focus. Clear the focus so // that new keys will go to the newly focused field private void domChangedFocus() { @@ -3852,6 +3921,16 @@ public class WebView extends AbsoluteLayout if (x == mScrollingLayerRect.left && y == mScrollingLayerRect.top) { return; } + if (mSelectingText) { + int dx = mScrollingLayerRect.left - x; + int dy = mScrollingLayerRect.top - y; + if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) { + mSelectCursorBase.offset(dx, dy); + } + if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) { + mSelectCursorExtent.offset(dx, dy); + } + } nativeScrollLayer(mCurrentScrollingLayerId, x, y); mScrollingLayerRect.left = x; mScrollingLayerRect.top = y; @@ -4470,6 +4549,8 @@ public class WebView extends AbsoluteLayout if (canvas.isHardwareAccelerated()) { mZoomManager.setHardwareAccelerated(); + } else { + mWebViewCore.resumeWebKitDraw(); } int saveCount = canvas.save(); @@ -4624,12 +4705,7 @@ public class WebView extends AbsoluteLayout * Select the word at the indicated content coordinates. */ boolean selectText(int x, int y) { - if (!setUpSelect(true, x, y)) { - return false; - } - nativeSetExtendSelection(); - mDrawSelectionPointer = false; - mTouchMode = TOUCH_DRAG_MODE; + mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y); return true; } @@ -4717,11 +4793,19 @@ public class WebView extends AbsoluteLayout } void setBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator, - boolean isPictureAfterFirstLayout, boolean registerPageSwapCallback) { + boolean isPictureAfterFirstLayout) { if (mNativeClass == 0) return; - nativeSetBaseLayer(mNativeClass, layer, invalRegion, showVisualIndicator, - isPictureAfterFirstLayout, registerPageSwapCallback); + boolean queueFull; + queueFull = nativeSetBaseLayer(mNativeClass, layer, invalRegion, + showVisualIndicator, isPictureAfterFirstLayout); + + if (layer == 0 || isPictureAfterFirstLayout) { + mWebViewCore.resumeWebKitDraw(); + } else if (queueFull) { + mWebViewCore.pauseWebKitDraw(); + } + if (mHTML5VideoViewProxy != null) { mHTML5VideoViewProxy.setBaseLayer(layer); } @@ -4830,11 +4914,8 @@ public class WebView extends AbsoluteLayout int extras = DRAW_EXTRAS_NONE; if (mFindIsUp) { extras = DRAW_EXTRAS_FIND; - } else if (mSelectingText && (!USE_JAVA_TEXT_SELECTION || DEBUG_TEXT_HANDLES)) { + } else if (mSelectingText) { extras = DRAW_EXTRAS_SELECTION; - nativeSetSelectionPointer(mNativeClass, - mDrawSelectionPointer, - mZoomManager.getInvScale(), mSelectX, mSelectY - getTitleHeight()); } else if (drawCursorRing) { extras = DRAW_EXTRAS_CURSOR_RING; } @@ -4879,7 +4960,7 @@ public class WebView extends AbsoluteLayout } canvas.restoreToCount(saveCount); - if (mSelectingText && USE_JAVA_TEXT_SELECTION) { + if (mSelectingText) { drawTextSelectionHandles(canvas); } @@ -4901,30 +4982,12 @@ public class WebView extends AbsoluteLayout } private void drawTextSelectionHandles(Canvas canvas) { - if (mTextSelectionPaint == null) { - mTextSelectionPaint = new Paint(); - mTextSelectionPaint.setColor(HIGHLIGHT_COLOR); - } - mTextSelectionRegion.setEmpty(); - nativeGetTextSelectionRegion(mNativeClass, mTextSelectionRegion); - Rect r = new Rect(); - RegionIterator iter = new RegionIterator(mTextSelectionRegion); - Rect clip = canvas.getClipBounds(); - while (iter.next(r)) { - r.set(contentToViewDimension(r.left), - contentToViewDimension(r.top), - contentToViewDimension(r.right), - contentToViewDimension(r.bottom)); - if (r.intersect(clip)) { - canvas.drawRect(r, mTextSelectionPaint); - } - } if (mSelectHandleLeft == null) { mSelectHandleLeft = mContext.getResources().getDrawable( com.android.internal.R.drawable.text_select_handle_left); } int[] handles = new int[4]; - nativeGetSelectionHandles(mNativeClass, handles); + getSelectionHandles(handles); int start_x = contentToViewDimension(handles[0]); int start_y = contentToViewDimension(handles[1]); int end_x = contentToViewDimension(handles[2]); @@ -4942,14 +5005,31 @@ public class WebView extends AbsoluteLayout mSelectHandleRight.setBounds(end_x, end_y, end_x + mSelectHandleRight.getIntrinsicWidth(), end_y + mSelectHandleRight.getIntrinsicHeight()); - if (DEBUG_TEXT_HANDLES) { - mSelectHandleLeft.setAlpha(125); - mSelectHandleRight.setAlpha(125); - } mSelectHandleLeft.draw(canvas); mSelectHandleRight.draw(canvas); } + /** + * Takes an int[4] array as an output param with the values being + * startX, startY, endX, endY + */ + private void getSelectionHandles(int[] handles) { + handles[0] = mSelectCursorBase.right; + handles[1] = mSelectCursorBase.bottom - + (mSelectCursorBase.height() / 4); + handles[2] = mSelectCursorExtent.left; + handles[3] = mSelectCursorExtent.bottom + - (mSelectCursorExtent.height() / 4); + if (!nativeIsBaseFirst(mNativeClass)) { + int swap = handles[0]; + handles[0] = handles[2]; + handles[2] = swap; + swap = handles[1]; + handles[1] = handles[3]; + handles[3] = swap; + } + } + // draw history private boolean mDrawHistory = false; private Picture mHistoryPicture = null; @@ -5009,7 +5089,7 @@ public class WebView extends AbsoluteLayout /* package */ void deleteSelection(int start, int end) { mTextGeneration++; WebViewCore.TextSelectionData data - = new WebViewCore.TextSelectionData(start, end); + = new WebViewCore.TextSelectionData(start, end, 0); mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0, data); } @@ -5090,6 +5170,9 @@ public class WebView extends AbsoluteLayout * multiline, and what text it contains. It also removes it if necessary. */ /* package */ void rebuildWebTextView() { + if (!sEnableWebTextView) { + return; // always use WebKit's text entry + } // If the WebView does not have focus, do nothing until it gains focus. if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) { return; @@ -5462,15 +5545,6 @@ public class WebView extends AbsoluteLayout return pinScrollTo(mContentWidth, mScrollY, true, 0); } } - if (mSelectingText) { - int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT - ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0; - int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ? - -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0; - int multiplier = event.getRepeatCount() + 1; - moveSelection(xRate * multiplier, yRate * multiplier); - return true; - } if (navHandledKey(keyCode, 1, false, event.getEventTime())) { playSoundEffect(keyCodeToSoundsEffect(keyCode)); return true; @@ -5623,14 +5697,8 @@ public class WebView extends AbsoluteLayout mGotCenterDown = false; if (mSelectingText) { - if (mExtendSelection) { - copySelection(); - selectionDone(); - } else { - mExtendSelection = true; - nativeSetExtendSelection(); - invalidate(); // draw the i-beam instead of the arrow - } + copySelection(); + selectionDone(); return true; // discard press if copy in progress } @@ -5676,21 +5744,7 @@ public class WebView extends AbsoluteLayout return false; } - /* - * Enter selecting text mode, and see if CAB should be shown. - * Returns true if the WebView is now in - * selecting text mode (including if it was already in that mode, and this - * method did nothing). - */ - private boolean setUpSelect(boolean selectWord, int x, int y) { - if (0 == mNativeClass) return false; // client isn't initialized - if (inFullScreenMode()) return false; - if (mSelectingText) return true; - nativeResetSelection(); - if (selectWord && !nativeWordSelection(x, y)) { - selectionDone(); - return false; - } + private boolean startSelectActionMode() { mSelectCallback = new SelectActionModeCallback(); mSelectCallback.setWebView(this); if (startActionMode(mSelectCallback) == null) { @@ -5699,52 +5753,41 @@ public class WebView extends AbsoluteLayout selectionDone(); return false; } - mExtendSelection = false; - mSelectingText = mDrawSelectionPointer = true; - if (DEBUG_TEXT_HANDLES) { - // Debugging text handles requires running in software mode - setLayerType(LAYER_TYPE_SOFTWARE, null); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + return true; + } + + private void syncSelectionCursors() { + mSelectCursorBaseLayerId = + nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE, mSelectCursorBase); + mSelectCursorExtentLayerId = + nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT, mSelectCursorExtent); + } + + private boolean setupWebkitSelect() { + syncSelectionCursors(); + if (!startSelectActionMode()) { + selectionDone(); + return false; } - // don't let the picture change during text selection - WebViewCore.pauseUpdatePicture(mWebViewCore); - if (nativeHasCursorNode()) { - Rect rect = nativeCursorNodeBounds(); - mSelectX = contentToViewX(rect.left); - mSelectY = contentToViewY(rect.top); - } else if (mLastTouchY > getVisibleTitleHeightImpl()) { - mSelectX = mScrollX + mLastTouchX; - mSelectY = mScrollY + mLastTouchY; + mSelectingText = true; + mTouchMode = TOUCH_DRAG_MODE; + return true; + } + + private void updateWebkitSelection() { + int[] handles = null; + if (mSelectingText) { + handles = new int[4]; + handles[0] = mSelectCursorBase.centerX(); + handles[1] = mSelectCursorBase.centerY(); + handles[2] = mSelectCursorExtent.centerX(); + handles[3] = mSelectCursorExtent.centerY(); } else { - mSelectX = mScrollX + getViewWidth() / 2; - mSelectY = mScrollY + getViewHeightWithTitle() / 2; + nativeSetTextSelection(mNativeClass, 0); } - nativeHideCursor(); - mMinAutoScrollX = 0; - mMaxAutoScrollX = getViewWidth(); - mMinAutoScrollY = 0; - mMaxAutoScrollY = getViewHeightWithTitle(); - mCurrentScrollingLayerId = nativeScrollableLayer(viewToContentX(mSelectX), - viewToContentY(mSelectY), mScrollingLayerRect, - mScrollingLayerBounds); - if (mCurrentScrollingLayerId != 0) { - if (mScrollingLayerRect.left != mScrollingLayerRect.right) { - mMinAutoScrollX = Math.max(mMinAutoScrollX, - contentToViewX(mScrollingLayerBounds.left)); - mMaxAutoScrollX = Math.min(mMaxAutoScrollX, - contentToViewX(mScrollingLayerBounds.right)); - } - if (mScrollingLayerRect.top != mScrollingLayerRect.bottom) { - mMinAutoScrollY = Math.max(mMinAutoScrollY, - contentToViewY(mScrollingLayerBounds.top)); - mMaxAutoScrollY = Math.min(mMaxAutoScrollY, - contentToViewY(mScrollingLayerBounds.bottom)); - } - } - mMinAutoScrollX += SELECT_SCROLL; - mMaxAutoScrollX -= SELECT_SCROLL; - mMinAutoScrollY += SELECT_SCROLL; - mMaxAutoScrollY -= SELECT_SCROLL; - return true; + mWebViewCore.removeMessages(EventHub.SELECT_TEXT); + mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles); } /** @@ -5755,7 +5798,6 @@ public class WebView extends AbsoluteLayout @Deprecated public void emulateShiftHeld() { checkThread(); - setUpSelect(false, 0, 0); } /** @@ -5764,17 +5806,7 @@ public class WebView extends AbsoluteLayout * @hide This is an implementation detail. */ public void selectAll() { - if (0 == mNativeClass) return; // client isn't initialized - if (inFullScreenMode()) return; - if (!mSelectingText) { - // retrieve a point somewhere within the text - Point select = nativeSelectableText(); - if (!selectText(select.x, select.y)) return; - } - nativeSelectAll(); - mDrawSelectionPointer = false; - mExtendSelection = true; - invalidate(); + mWebViewCore.sendMessage(EventHub.SELECT_ALL); } /** @@ -5783,17 +5815,11 @@ public class WebView extends AbsoluteLayout void selectionDone() { if (mSelectingText) { mSelectingText = false; - if (DEBUG_TEXT_HANDLES) { - // Debugging text handles required running in software mode, set - // back to default now - setLayerType(LAYER_TYPE_NONE, null); - } // finish is idempotent, so this is fine even if selectionDone was // called by mSelectCallback.onDestroyActionMode mSelectCallback.finish(); mSelectCallback = null; - WebViewCore.resumePriority(); - WebViewCore.resumeUpdatePicture(mWebViewCore); + updateWebkitSelection(); invalidate(); // redraw without selection mAutoScrollX = 0; mAutoScrollY = 0; @@ -5821,7 +5847,7 @@ public class WebView extends AbsoluteLayout .getSystemService(Context.CLIPBOARD_SERVICE); cm.setText(selection); int[] handles = new int[4]; - nativeGetSelectionHandles(mNativeClass, handles); + getSelectionHandles(handles); mWebViewCore.sendMessage(EventHub.COPY_TEXT, handles); } invalidate(); // remove selection region and pointer @@ -5836,7 +5862,7 @@ public class WebView extends AbsoluteLayout public void cutSelection() { copySelection(); int[] handles = new int[4]; - nativeGetSelectionHandles(mNativeClass, handles); + getSelectionHandles(handles); mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles); } @@ -5854,7 +5880,7 @@ public class WebView extends AbsoluteLayout CharSequence pasteText = clipItem.getText(); if (pasteText != null) { int[] handles = new int[4]; - nativeGetSelectionHandles(mNativeClass, handles); + getSelectionHandles(handles); mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles); mWebViewCore.sendMessage(EventHub.INSERT_TEXT, pasteText.toString()); @@ -6406,13 +6432,28 @@ public class WebView extends AbsoluteLayout EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION, (eventTime - mLastTouchUpTime), eventTime); } - if (mSelectingText) { - mDrawSelectionPointer = false; - mSelectionStarted = nativeStartSelection(contentX, contentY); + mSelectionStarted = false; + if (mSelectingText && mSelectHandleLeft != null + && mSelectHandleRight != null) { + int shiftedY = y - getTitleHeight() + mScrollY; + int shiftedX = x + mScrollX; + if (mSelectHandleLeft.getBounds() + .contains(shiftedX, shiftedY)) { + mSelectionStarted = true; + mSelectDraggingCursor = mSelectCursorBase; + } else if (mSelectHandleRight.getBounds() + .contains(shiftedX, shiftedY)) { + mSelectionStarted = true; + mSelectDraggingCursor = mSelectCursorExtent; + } + if (mSelectDraggingCursor != null) { + mSelectDraggingOffset.set( + mSelectDraggingCursor.left - contentX, + mSelectDraggingCursor.top - contentY); + } if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "select=" + contentX + "," + contentY); } - invalidate(); } } // Trigger the link @@ -6478,6 +6519,26 @@ public class WebView extends AbsoluteLayout removeTouchHighlight(); } } + if (mSelectingText && mSelectionStarted) { + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "extend=" + contentX + "," + contentY); + } + ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + if (deltaX != 0 || deltaY != 0) { + mSelectDraggingCursor.offsetTo( + contentX + mSelectDraggingOffset.x, + contentY + mSelectDraggingOffset.y); + updateWebkitSelection(); + mLastTouchX = x; + mLastTouchY = y; + invalidate(); + } + break; + } + // pass the touch events from UI thread to WebCore thread if (shouldForwardTouchEvent() && mConfirmMove && (firstMove || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) { @@ -6520,30 +6581,6 @@ public class WebView extends AbsoluteLayout } else { mVelocityTracker.addMovement(ev); } - if (mSelectingText && mSelectionStarted) { - if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, "extend=" + contentX + "," + contentY); - } - ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - mAutoScrollX = x <= mMinAutoScrollX ? -SELECT_SCROLL - : x >= mMaxAutoScrollX ? SELECT_SCROLL : 0; - mAutoScrollY = y <= mMinAutoScrollY ? -SELECT_SCROLL - : y >= mMaxAutoScrollY ? SELECT_SCROLL : 0; - if ((mAutoScrollX != 0 || mAutoScrollY != 0) - && !mSentAutoScrollMessage) { - mSentAutoScrollMessage = true; - mPrivateHandler.sendEmptyMessageDelayed( - SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL); - } - if (deltaX != 0 || deltaY != 0) { - nativeExtendSelection(contentX, contentY); - invalidate(); - } - break; - } if (mTouchMode != TOUCH_DRAG_MODE && mTouchMode != TOUCH_DRAG_LAYER_MODE) { @@ -6758,7 +6795,7 @@ public class WebView extends AbsoluteLayout } else { if (mSelectingText) { // tapping on selection or controls does nothing - if (!nativeHitSelection(contentX, contentY)) { + if (!mSelectionStarted) { selectionDone(); } break; @@ -7055,6 +7092,12 @@ public class WebView extends AbsoluteLayout if (mOverScrollGlow != null) { mOverScrollGlow.releaseAll(); } + + if (mSelectingText) { + mSelectionStarted = false; + syncSelectionCursors(); + invalidate(); + } } private void cancelTouch() { @@ -7119,8 +7162,6 @@ public class WebView extends AbsoluteLayout private int mTrackballYMove = 0; private boolean mSelectingText = false; private boolean mSelectionStarted = false; - private boolean mExtendSelection = false; - private boolean mDrawSelectionPointer = false; private static final int TRACKBALL_KEY_TIMEOUT = 1000; private static final int TRACKBALL_TIMEOUT = 200; private static final int TRACKBALL_WAIT = 100; @@ -7189,14 +7230,8 @@ public class WebView extends AbsoluteLayout mTrackballDown = false; mTrackballUpTime = time; if (mSelectingText) { - if (mExtendSelection) { - copySelection(); - selectionDone(); - } else { - mExtendSelection = true; - nativeSetExtendSelection(); - invalidate(); // draw the i-beam instead of the arrow - } + copySelection(); + selectionDone(); return true; // discard press if copy in progress } if (DebugFlags.WEB_VIEW) { @@ -7239,42 +7274,6 @@ public class WebView extends AbsoluteLayout return true; } - void moveSelection(float xRate, float yRate) { - if (mNativeClass == 0) - return; - int width = getViewWidth(); - int height = getViewHeight(); - mSelectX += xRate; - mSelectY += yRate; - int maxX = width + mScrollX; - int maxY = height + mScrollY; - mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET - , mSelectX)); - mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET - , mSelectY)); - if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, "moveSelection" - + " mSelectX=" + mSelectX - + " mSelectY=" + mSelectY - + " mScrollX=" + mScrollX - + " mScrollY=" + mScrollY - + " xRate=" + xRate - + " yRate=" + yRate - ); - } - nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY)); - int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET - : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET - : 0; - int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET - : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET - : 0; - pinScrollBy(scrollX, scrollY, true, 0); - Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); - requestRectangleOnScreen(select); - invalidate(); - } - private int scaleTrackballX(float xRate, int width) { int xMove = (int) (xRate / TRACKBALL_SCALE * width); int nextXMove = xMove; @@ -7328,21 +7327,6 @@ public class WebView extends AbsoluteLayout float yRate = mTrackballRemainsY * 1000 / elapsed; int viewWidth = getViewWidth(); int viewHeight = getViewHeight(); - if (mSelectingText) { - if (!mDrawSelectionPointer) { - // The last selection was made by touch, disabling drawing the - // selection pointer. Allow the trackball to adjust the - // position of the touch control. - mSelectX = contentToViewX(nativeSelectionX()); - mSelectY = contentToViewY(nativeSelectionY()); - mDrawSelectionPointer = mExtendSelection = true; - nativeSetExtendSelection(); - } - moveSelection(scaleTrackballX(xRate, viewWidth), - scaleTrackballY(yRate, viewHeight)); - mTrackballRemainsX = mTrackballRemainsY = 0; - return; - } float ax = Math.abs(xRate); float ay = Math.abs(yRate); float maxA = Math.max(ax, ay); @@ -8751,14 +8735,17 @@ public class WebView extends AbsoluteLayout case UPDATE_TEXTFIELD_TEXT_MSG_ID: // Make sure that the textfield is currently focused // and representing the same node as the pointer. - if (inEditingMode() && - mWebTextView.isSameTextField(msg.arg1)) { - if (msg.arg2 == mTextGeneration) { - String text = (String) msg.obj; - if (null == text) { - text = ""; - } + if (msg.arg2 == mTextGeneration) { + String text = (String) msg.obj; + if (null == text) { + text = ""; + } + if (inEditingMode() && + mWebTextView.isSameTextField(msg.arg1)) { mWebTextView.setTextAndKeepSelection(text); + } else if (mInputConnection != null && + mFieldPointer == msg.arg1) { + mInputConnection.setTextAndKeepSelection(text); } } break; @@ -9049,15 +9036,8 @@ public class WebView extends AbsoluteLayout case INIT_EDIT_FIELD: if (mInputConnection != null) { mTextGeneration = 0; - String text = (String)msg.obj; - mInputConnection.beginBatchEdit(); - Editable editable = mInputConnection.getEditable(); - editable.replace(0, editable.length(), text); - int start = msg.arg1; - int end = msg.arg2; - mInputConnection.setComposingRegion(end, end); - mInputConnection.setSelection(start, end); - mInputConnection.endBatchEdit(); + mFieldPointer = msg.arg1; + mInputConnection.setTextAndKeepSelection((String) msg.obj); } break; @@ -9107,6 +9087,7 @@ public class WebView extends AbsoluteLayout /** @hide Called by JNI when pages are swapped (only occurs with hardware * acceleration) */ protected void pageSwapCallback(boolean notifyAnimationStarted) { + mWebViewCore.resumeWebKitDraw(); if (inEditingMode()) { didUpdateWebTextViewDimensions(ANYWHERE); } @@ -9129,13 +9110,9 @@ public class WebView extends AbsoluteLayout boolean isPictureAfterFirstLayout = viewState != null; if (updateBaseLayer) { - // Request a callback on pageSwap (to reposition the webtextview) - boolean registerPageSwapCallback = - !mZoomManager.isFixedLengthAnimationInProgress() && inEditingMode(); - setBaseLayer(draw.mBaseLayer, draw.mInvalRegion, getSettings().getShowVisualIndicator(), - isPictureAfterFirstLayout, registerPageSwapCallback); + isPictureAfterFirstLayout); } final Point viewSize = draw.mViewSize; // We update the layout (i.e. request a layout from the @@ -9200,10 +9177,22 @@ public class WebView extends AbsoluteLayout if (inEditingMode() && mWebTextView.isSameTextField(nodePointer)) { mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd); - } else if (mInputConnection != null){ + } else if (mInputConnection != null && mFieldPointer == nodePointer) { mInputConnection.setSelection(data.mStart, data.mEnd); } } + + nativeSetTextSelection(mNativeClass, data.mSelectTextPtr); + if (data.mSelectTextPtr != 0) { + if (!mSelectingText) { + setupWebkitSelect(); + } else if (!mSelectionStarted) { + syncSelectionCursors(); + } + } else { + selectionDone(); + } + invalidate(); } // Class used to use a dropdown for a <select> element @@ -9801,11 +9790,6 @@ public class WebView extends AbsoluteLayout } } - /** @hide call pageSwapCallback upon next page swap */ - protected void registerPageSwapCallback() { - nativeRegisterPageSwapCallback(mNativeClass); - } - /** @hide discard all textures from tiles */ protected void discardAllTextures() { nativeDiscardAllTextures(); @@ -9952,7 +9936,6 @@ public class WebView extends AbsoluteLayout private native boolean nativeMoveCursor(int keyCode, int count, boolean noScroll); private native int nativeMoveGeneration(); - private native void nativeMoveSelection(int x, int y); /** * @return true if the page should get the shift and arrow keys, rather * than select text/navigation. @@ -9962,24 +9945,16 @@ public class WebView extends AbsoluteLayout */ private native boolean nativePageShouldHandleShiftAndArrows(); private native boolean nativePointInNavCache(int x, int y, int slop); - // Like many other of our native methods, you must make sure that - // mNativeClass is not null before calling this method. - private native void nativeResetSelection(); - private native Point nativeSelectableText(); - private native void nativeSelectAll(); private native void nativeSelectBestAt(Rect rect); private native void nativeSelectAt(int x, int y); - private native int nativeSelectionX(); - private native int nativeSelectionY(); private native int nativeFindIndex(); private native void nativeSetExtendSelection(); private native void nativeSetFindIsEmpty(); private native void nativeSetFindIsUp(boolean isUp); private native void nativeSetHeightCanMeasure(boolean measure); - private native void nativeSetBaseLayer(int nativeInstance, + private native boolean nativeSetBaseLayer(int nativeInstance, int layer, Region invalRegion, - boolean showVisualIndicator, boolean isPictureAfterFirstLayout, - boolean registerPageSwapCallback); + boolean showVisualIndicator, boolean isPictureAfterFirstLayout); private native int nativeGetBaseLayer(); private native void nativeShowCursorTimed(); private native void nativeReplaceBaseContent(int content); @@ -9991,7 +9966,6 @@ public class WebView extends AbsoluteLayout private native void nativeStopGL(); private native Rect nativeSubtractLayers(Rect content); private native int nativeTextGeneration(); - private native void nativeRegisterPageSwapCallback(int nativeInstance); private native void nativeDiscardAllTextures(); private native void nativeTileProfilingStart(); private native float nativeTileProfilingStop(); @@ -10026,12 +10000,14 @@ public class WebView extends AbsoluteLayout private native int nativeGetBackgroundColor(); native boolean nativeSetProperty(String key, String value); native String nativeGetProperty(String key); - private native void nativeGetTextSelectionRegion(int instance, Region region); - private native void nativeGetSelectionHandles(int instance, int[] handles); /** * See {@link ComponentCallbacks2} for the trim levels and descriptions */ private static native void nativeOnTrimMemory(int level); private static native void nativeSetPauseDrawing(int instance, boolean pause); private static native boolean nativeDisableNavcache(); + private static native void nativeSetTextSelection(int instance, int selection); + private static native int nativeGetHandleLayerId(int instance, int handle, + Rect cursorLocation); + private static native boolean nativeIsBaseFirst(int instance); } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index fe5158177540..8a9c12d43c74 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -835,12 +835,14 @@ public final class WebViewCore { } static class TextSelectionData { - public TextSelectionData(int start, int end) { + public TextSelectionData(int start, int end, int selectTextPtr) { mStart = start; mEnd = end; + mSelectTextPtr = selectTextPtr; } int mStart; int mEnd; + int mSelectTextPtr; } static class TouchUpData { @@ -870,6 +872,7 @@ public final class WebViewCore { Rect[] mTouchRects; boolean mEditable; int mTapHighlightColor = WebView.HIGHLIGHT_COLOR; + Rect[] mEnclosingParentRects; // These are the input values that produced this hit test int mHitTestX; @@ -1118,6 +1121,9 @@ public final class WebViewCore { static final int COPY_TEXT = 210; static final int DELETE_TEXT = 211; static final int INSERT_TEXT = 212; + static final int SELECT_TEXT = 213; + static final int SELECT_WORD_AT = 214; + static final int SELECT_ALL = 215; // Private handler for WebCore messages. private Handler mHandler; @@ -1194,7 +1200,6 @@ public final class WebViewCore { mSettings.onDestroyed(); mNativeClass = 0; mWebView = null; - WebCoreThreadWatchdog.quit(); } break; @@ -1747,6 +1752,25 @@ public final class WebViewCore { case INSERT_TEXT: nativeInsertText(mNativeClass, (String) msg.obj); break; + case SELECT_TEXT: { + int[] args = (int[]) msg.obj; + if (args == null) { + nativeClearTextSelection(mNativeClass); + } else { + nativeSelectText(mNativeClass, args[0], + args[1], args[2], args[3]); + } + break; + } + case SELECT_WORD_AT: { + int x = msg.arg1; + int y = msg.arg2; + nativeSelectWordAt(mNativeClass, x, y); + break; + } + case SELECT_ALL: + nativeSelectAll(mNativeClass); + break; } } }; @@ -2146,7 +2170,36 @@ public final class WebViewCore { .obtainMessage(WebView.INVAL_RECT_MSG_ID)); } + private Boolean m_skipDrawFlag = false; + private boolean m_drawWasSkipped = false; + + void pauseWebKitDraw() { + synchronized (m_skipDrawFlag) { + if (!m_skipDrawFlag) { + m_skipDrawFlag = true; + } + } + } + + void resumeWebKitDraw() { + synchronized (m_skipDrawFlag) { + if (m_skipDrawFlag && m_drawWasSkipped) { + // a draw was dropped, send a retry + m_drawWasSkipped = false; + mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW)); + } + m_skipDrawFlag = false; + } + } + private void webkitDraw() { + synchronized (m_skipDrawFlag) { + if (m_skipDrawFlag) { + m_drawWasSkipped = true; + return; + } + } + mDrawIsScheduled = false; DrawData draw = new DrawData(); if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start"); @@ -2155,7 +2208,7 @@ public final class WebViewCore { if (draw.mBaseLayer == 0) { if (mWebView != null && !mWebView.isPaused()) { if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message"); - mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW)); + mEventHub.sendMessageDelayed(Message.obtain(null, EventHub.WEBKIT_DRAW), 10); } else { if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, webview paused"); } @@ -2700,11 +2753,11 @@ public final class WebViewCore { // called by JNI private void updateTextSelection(int pointer, int start, int end, - int textGeneration) { + int textGeneration, int selectionPtr) { if (mWebView != null) { Message.obtain(mWebView.mPrivateHandler, WebView.UPDATE_TEXT_SELECTION_MSG_ID, pointer, textGeneration, - new TextSelectionData(start, end)).sendToTarget(); + new TextSelectionData(start, end, selectionPtr)).sendToTarget(); } } @@ -2723,12 +2776,16 @@ public final class WebViewCore { } // called by JNI - private void initEditField(String text, int start, int end) { + private void initEditField(int pointer, String text, int start, int end) { if (mWebView == null) { return; } Message.obtain(mWebView.mPrivateHandler, - WebView.INIT_EDIT_FIELD, start, end, text).sendToTarget(); + WebView.INIT_EDIT_FIELD, pointer, 0, text).sendToTarget(); + Message.obtain(mWebView.mPrivateHandler, + WebView.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID, pointer, + 0, new TextSelectionData(start, end, 0)) + .sendToTarget(); } private native void nativeUpdateFrameCacheIfLoading(int nativeClass); @@ -2766,17 +2823,6 @@ public final class WebViewCore { } // called by JNI - private void requestKeyboardWithSelection(int pointer, int selStart, - int selEnd, int textGeneration) { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, - WebView.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID, pointer, - textGeneration, new TextSelectionData(selStart, selEnd)) - .sendToTarget(); - } - } - - // called by JNI private void requestKeyboard(boolean showKeyboard) { if (mWebView != null) { Message.obtain(mWebView.mPrivateHandler, @@ -3026,4 +3072,9 @@ public final class WebViewCore { */ private native String nativeGetText(int nativeClass, int startX, int startY, int endX, int endY); + private native void nativeSelectText(int nativeClass, + int startX, int startY, int endX, int endY); + private native void nativeClearTextSelection(int nativeClass); + private native void nativeSelectWordAt(int nativeClass, int x, int y); + private native void nativeSelectAll(int nativeClass); } diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 8ffba647ba82..ae2d6173cb84 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -717,7 +717,7 @@ class ZoomManager { private void zoomToReadingLevel() { final float readingScale = getReadingLevelScale(); - int left = mWebView.nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale); + int left = mWebView.getBlockLeftEdge(mAnchorX, mAnchorY, readingScale); if (left != WebView.NO_LEFTEDGE) { // add a 5pt padding to the left edge. int viewLeft = mWebView.contentToViewX(left < 5 ? 0 : (left - 5)) diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 7d58011c2b51..984ec79bee3e 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -842,9 +842,11 @@ public class GridLayout extends ViewGroup { * @hide */ @Override - protected void onChildVisibilityChanged(View child, int visibility) { - super.onChildVisibilityChanged(child, visibility); - invalidateStructure(); + protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) { + super.onChildVisibilityChanged(child, oldVisibility, newVisibility); + if (oldVisibility == GONE || newVisibility == GONE) { + invalidateStructure(); + } } // Measurement diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index e20d12a5ec23..67fd059f1955 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1651,6 +1651,7 @@ public class ListView extends AbsListView { // are focusable if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) { final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild && + focusLayoutRestoreView != null && focusLayoutRestoreView.requestFocus()) || sel.requestFocus(); if (!focusWasTaken) { // selected item didn't take focus, fine, but still want diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index d395fb2fd287..84e86af0840d 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -56,13 +56,13 @@ import com.android.internal.R; /** * A widget that enables the user to select a number form a predefined range. * The widget presents an input field and up and down buttons for selecting the - * current value. Pressing/long pressing the up and down buttons increments and + * current value. Pressing/long-pressing the up and down buttons increments and * decrements the current value respectively. Touching the input field shows a - * scroll wheel, tapping on which while shown and not moving allows direct edit - * of the current value. Sliding motions up or down hide the buttons and the - * input field, show the scroll wheel, and rotate the latter. Flinging is + * scroll wheel, which when touched allows direct edit + * of the current value. Sliding gestures up or down hide the buttons and the + * input filed, show and rotate the scroll wheel. Flinging is * also supported. The widget enables mapping from positions to strings such - * that instead the position index the corresponding string is displayed. + * that, instead of the position index, the corresponding string is displayed. * <p> * For an example of using this widget, see {@link android.widget.TimePicker}. * </p> diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 1592061906ab..62afd61de6f4 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -427,13 +427,22 @@ public class RemoteViews implements Parcelable, Filter { public SetOnClickPendingIntent(Parcel parcel) { viewId = parcel.readInt(); - pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); + + // We check a flag to determine if the parcel contains a PendingIntent. + if (parcel.readInt() != 0) { + pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); + } } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(TAG); dest.writeInt(viewId); - pendingIntent.writeToParcel(dest, 0 /* no flags */); + + // We use a flag to indicate whether the parcel contains a valid object. + dest.writeInt(pendingIntent != null ? 1 : 0); + if (pendingIntent != null) { + pendingIntent.writeToParcel(dest, 0 /* no flags */); + } } @Override @@ -445,35 +454,39 @@ public class RemoteViews implements Parcelable, Filter { // sense, do they mean to set a PendingIntent template for the AdapterView's children? if (mIsWidgetCollectionChild) { Log.e("RemoteViews", "Cannot setOnClickPendingIntent for collection item " + - "(id: " + viewId + ")"); + "(id: " + viewId + ")"); // TODO: return; We'll let this slide until apps are up to date. } - if (target != null && pendingIntent != null) { - OnClickListener listener = new OnClickListener() { - public void onClick(View v) { - // Find target view location in screen coordinates and - // fill into PendingIntent before sending. - final float appScale = v.getContext().getResources() - .getCompatibilityInfo().applicationScale; - final int[] pos = new int[2]; - v.getLocationOnScreen(pos); - - final Rect rect = new Rect(); - rect.left = (int) (pos[0] * appScale + 0.5f); - rect.top = (int) (pos[1] * appScale + 0.5f); - rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); - rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); - - final Intent intent = new Intent(); - intent.setSourceBounds(rect); - startIntentSafely(v.getContext(), pendingIntent, intent); - } - }; + if (target != null) { + // If the pendingIntent is null, we clear the onClickListener + OnClickListener listener = null; + if (pendingIntent != null) { + listener = new OnClickListener() { + public void onClick(View v) { + // Find target view location in screen coordinates and + // fill into PendingIntent before sending. + final float appScale = v.getContext().getResources() + .getCompatibilityInfo().applicationScale; + final int[] pos = new int[2]; + v.getLocationOnScreen(pos); + + final Rect rect = new Rect(); + rect.left = (int) (pos[0] * appScale + 0.5f); + rect.top = (int) (pos[1] * appScale + 0.5f); + rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); + rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); + + final Intent intent = new Intent(); + intent.setSourceBounds(rect); + startIntentSafely(v.getContext(), pendingIntent, intent); + } + }; + } target.setOnClickListener(listener); } } - + int viewId; PendingIntent pendingIntent; @@ -667,6 +680,9 @@ public class RemoteViews implements Parcelable, Filter { Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId) + " methodName=" + this.methodName + " type=" + this.type); } + + // For some values that may have been null, we first check a flag to see if they were + // written to the parcel. switch (this.type) { case BOOLEAN: this.value = in.readInt() != 0; @@ -699,16 +715,22 @@ public class RemoteViews implements Parcelable, Filter { this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); break; case URI: - this.value = Uri.CREATOR.createFromParcel(in); + if (in.readInt() != 0) { + this.value = Uri.CREATOR.createFromParcel(in); + } break; case BITMAP: - this.value = Bitmap.CREATOR.createFromParcel(in); + if (in.readInt() != 0) { + this.value = Bitmap.CREATOR.createFromParcel(in); + } break; case BUNDLE: this.value = in.readBundle(); break; case INTENT: - this.value = Intent.CREATOR.createFromParcel(in); + if (in.readInt() != 0) { + this.value = Intent.CREATOR.createFromParcel(in); + } break; default: break; @@ -725,6 +747,9 @@ public class RemoteViews implements Parcelable, Filter { Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId) + " methodName=" + this.methodName + " type=" + this.type); } + + // For some values which are null, we record an integer flag to indicate whether + // we have written a valid value to the parcel. switch (this.type) { case BOOLEAN: out.writeInt((Boolean) this.value ? 1 : 0); @@ -757,16 +782,25 @@ public class RemoteViews implements Parcelable, Filter { TextUtils.writeToParcel((CharSequence)this.value, out, flags); break; case URI: - ((Uri)this.value).writeToParcel(out, flags); + out.writeInt(this.value != null ? 1 : 0); + if (this.value != null) { + ((Uri)this.value).writeToParcel(out, flags); + } break; case BITMAP: - ((Bitmap)this.value).writeToParcel(out, flags); + out.writeInt(this.value != null ? 1 : 0); + if (this.value != null) { + ((Bitmap)this.value).writeToParcel(out, flags); + } break; case BUNDLE: out.writeBundle((Bundle) this.value); break; case INTENT: - ((Intent)this.value).writeToParcel(out, flags); + out.writeInt(this.value != null ? 1 : 0); + if (this.value != null) { + ((Intent)this.value).writeToParcel(out, flags); + } break; default: break; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 40d8a779c914..971d910736f2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -256,10 +256,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private float mShadowRadius, mShadowDx, mShadowDy; - private static final int PREDRAW_NOT_REGISTERED = 0; - private static final int PREDRAW_PENDING = 1; - private static final int PREDRAW_DONE = 2; - private int mPreDrawState = PREDRAW_NOT_REGISTERED; + private boolean mPreDrawRegistered; private TextUtils.TruncateAt mEllipsize = null; @@ -365,8 +362,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private SpellChecker mSpellChecker; - private boolean mSoftInputShownOnFocus = true; - // The alignment to pass to Layout, or null if not resolved. private Layout.Alignment mLayoutAlignment; @@ -2384,29 +2379,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Sets whether the soft input method will be made visible when this - * TextView gets focused. The default is true. - * - * @attr ref android.R.styleable#TextView_softInputShownOnFocus - * @hide - */ - @android.view.RemotableViewMethod - public final void setSoftInputShownOnFocus(boolean show) { - mSoftInputShownOnFocus = show; - } - - /** - * Returns whether the soft input method will be made visible when this - * TextView gets focused. The default is true. - * - * @attr ref android.R.styleable#TextView_softInputShownOnFocus - * @hide - */ - public final boolean getSoftInputShownOnFocus() { - return mSoftInputShownOnFocus; - } - - /** * Returns the list of URLSpans attached to the text * (by {@link Linkify} or otherwise) if any. You can call * {@link URLSpan#getURL} on them to find where they link to @@ -4387,26 +4359,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void registerForPreDraw() { - final ViewTreeObserver observer = getViewTreeObserver(); - - if (mPreDrawState == PREDRAW_NOT_REGISTERED) { - observer.addOnPreDrawListener(this); - mPreDrawState = PREDRAW_PENDING; - } else if (mPreDrawState == PREDRAW_DONE) { - mPreDrawState = PREDRAW_PENDING; + if (!mPreDrawRegistered) { + getViewTreeObserver().addOnPreDrawListener(this); + mPreDrawRegistered = true; } - - // else state is PREDRAW_PENDING, so keep waiting. } /** * {@inheritDoc} */ public boolean onPreDraw() { - if (mPreDrawState != PREDRAW_PENDING) { - return true; - } - if (mLayout == null) { assumeLayout(); } @@ -4457,7 +4419,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener startSelectionActionMode(); } - mPreDrawState = PREDRAW_DONE; + getViewTreeObserver().removeOnPreDrawListener(this); + mPreDrawRegistered = false; + return !changed; } @@ -4492,10 +4456,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - final ViewTreeObserver observer = getViewTreeObserver(); - if (mPreDrawState != PREDRAW_NOT_REGISTERED) { - observer.removeOnPreDrawListener(this); - mPreDrawState = PREDRAW_NOT_REGISTERED; + if (mPreDrawRegistered) { + getViewTreeObserver().removeOnPreDrawListener(this); + mPreDrawRegistered = false; } if (mError != null) { @@ -4768,12 +4731,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override protected void onDraw(Canvas canvas) { - if (mPreDrawState == PREDRAW_DONE) { - final ViewTreeObserver observer = getViewTreeObserver(); - observer.removeOnPreDrawListener(this); - mPreDrawState = PREDRAW_NOT_REGISTERED; - } - if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return; restartMarqueeIfNeeded(); @@ -4877,10 +4834,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int extendedPaddingTop = getExtendedPaddingTop(); int extendedPaddingBottom = getExtendedPaddingBottom(); + final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; + final int maxScrollY = mLayout.getHeight() - vspace; + float clipLeft = compoundPaddingLeft + scrollX; - float clipTop = extendedPaddingTop + scrollY; + float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; float clipRight = right - left - compoundPaddingRight + scrollX; - float clipBottom = bottom - top - extendedPaddingBottom + scrollY; + float clipBottom = bottom - top + scrollY - + ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); if (mShadowRadius != 0) { clipLeft += Math.min(0, mShadowDx - mShadowRadius); @@ -5040,7 +5001,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextDisplayList == null || !mTextDisplayList.isValid() || !mTextDisplayListIsValid) { if (mTextDisplayList == null) { - mTextDisplayList = getHardwareRenderer().createDisplayList(); + mTextDisplayList = getHardwareRenderer().createDisplayList("Text"); } final HardwareCanvas hardwareCanvas = mTextDisplayList.start(); @@ -5539,7 +5500,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && mLayout != null && onCheckIsTextEditor()) { InputMethodManager imm = InputMethodManager.peekInstance(); viewClicked(imm); - if (imm != null && mSoftInputShownOnFocus) { + if (imm != null) { imm.showSoftInput(this, 0); } } @@ -8395,7 +8356,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Show the IME, except when selecting in read-only text. final InputMethodManager imm = InputMethodManager.peekInstance(); viewClicked(imm); - if (!mTextIsSelectable && mSoftInputShownOnFocus) { + if (!mTextIsSelectable) { handled |= imm != null && imm.showSoftInput(this, 0); } @@ -10190,7 +10151,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } final boolean selectionStarted = mSelectionActionMode != null || willExtract; - if (selectionStarted && !mTextIsSelectable && mSoftInputShownOnFocus) { + if (selectionStarted && !mTextIsSelectable) { // Show the IME to be able to replace text, except when selecting non editable text. final InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { diff --git a/core/java/com/android/internal/util/FastMath.java b/core/java/com/android/internal/util/FastMath.java index efd087193a3c..88a17e6ba259 100644 --- a/core/java/com/android/internal/util/FastMath.java +++ b/core/java/com/android/internal/util/FastMath.java @@ -26,8 +26,8 @@ public class FastMath { * thought it may return slightly different results. It does not try to * handle (in any meaningful way) NaN or infinities. */ - public static int round(float x) { - long lx = (long)(x * (65536 * 256f)); - return (int)((lx + 0x800000) >> 24); + public static int round(float value) { + long lx = (long) (value * (65536 * 256f)); + return (int) ((lx + 0x800000) >> 24); } } diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java index ebd355aa272c..d51ced11d96b 100644 --- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java +++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java @@ -653,6 +653,7 @@ public class MultiWaveView extends View { case MotionEvent.ACTION_CANCEL: handleMove(event); + handleCancel(event); handled = true; break; } @@ -678,6 +679,12 @@ public class MultiWaveView extends View { if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE"); switchToState(STATE_FINISH, event.getX(), event.getY()); } + + private void handleCancel(MotionEvent event) { + if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL"); + mActiveTarget = -1; // Drop the active target if canceled. + switchToState(STATE_FINISH, event.getX(), event.getY()); + } private void handleMove(MotionEvent event) { if (!mDragging) { diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java index c4be51328997..8d57e5df5b0c 100644 --- a/core/java/com/google/android/mms/pdu/PduPersister.java +++ b/core/java/com/google/android/mms/pdu/PduPersister.java @@ -510,13 +510,26 @@ public class PduPersister { * @throws MmsException Failed to load some fields of a PDU. */ public GenericPdu load(Uri uri) throws MmsException { - PduCacheEntry cacheEntry = PDU_CACHE_INSTANCE.get(uri); - if (cacheEntry != null) { - return cacheEntry.getPdu(); + PduCacheEntry cacheEntry; + synchronized(PDU_CACHE_INSTANCE) { + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "load: ", e); + } + cacheEntry = PDU_CACHE_INSTANCE.get(uri); + if (cacheEntry != null) { + return cacheEntry.getPdu(); + } + } + // Tell the cache to indicate to other callers that this item + // is currently being updated. + PDU_CACHE_INSTANCE.setUpdating(uri, true); } Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri, - PDU_PROJECTION, null, null, null); + PDU_PROJECTION, null, null, null); PduHeaders headers = new PduHeaders(); Set<Entry<Integer, Integer>> set; long msgId = ContentUris.parseId(uri); @@ -634,9 +647,14 @@ public class PduPersister { "Unrecognized PDU type: " + Integer.toHexString(msgType)); } - cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); - PDU_CACHE_INSTANCE.put(uri, cacheEntry); - return pdu; + synchronized(PDU_CACHE_INSTANCE ) { + assert(PDU_CACHE_INSTANCE.get(uri) == null); + // Update the cache entry with the real info + cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); + PDU_CACHE_INSTANCE.put(uri, cacheEntry); + PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead + return pdu; + } } private void persistAddress( @@ -818,6 +836,17 @@ public class PduPersister { * @throws MmsException Bad URI or updating failed. */ public void updateHeaders(Uri uri, SendReq sendReq) { + synchronized(PDU_CACHE_INSTANCE) { + // If the cache item is getting updated, wait until it's done updating before + // purging it. + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "updateHeaders: ", e); + } + } + } PDU_CACHE_INSTANCE.purge(uri); ContentValues values = new ContentValues(10); @@ -969,52 +998,72 @@ public class PduPersister { */ public void updateParts(Uri uri, PduBody body) throws MmsException { - PduCacheEntry cacheEntry = PDU_CACHE_INSTANCE.get(uri); - if (cacheEntry != null) { - ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body); - } + try { + PduCacheEntry cacheEntry; + synchronized(PDU_CACHE_INSTANCE) { + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "updateParts: ", e); + } + cacheEntry = PDU_CACHE_INSTANCE.get(uri); + if (cacheEntry != null) { + ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body); + } + } + // Tell the cache to indicate to other callers that this item + // is currently being updated. + PDU_CACHE_INSTANCE.setUpdating(uri, true); + } - ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>(); - HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>(); + ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>(); + HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>(); - int partsNum = body.getPartsNum(); - StringBuilder filter = new StringBuilder().append('('); - for (int i = 0; i < partsNum; i++) { - PduPart part = body.getPart(i); - Uri partUri = part.getDataUri(); - if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) { - toBeCreated.add(part); - } else { - toBeUpdated.put(partUri, part); + int partsNum = body.getPartsNum(); + StringBuilder filter = new StringBuilder().append('('); + for (int i = 0; i < partsNum; i++) { + PduPart part = body.getPart(i); + Uri partUri = part.getDataUri(); + if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) { + toBeCreated.add(part); + } else { + toBeUpdated.put(partUri, part); - // Don't use 'i > 0' to determine whether we should append - // 'AND' since 'i = 0' may be skipped in another branch. - if (filter.length() > 1) { - filter.append(" AND "); - } + // Don't use 'i > 0' to determine whether we should append + // 'AND' since 'i = 0' may be skipped in another branch. + if (filter.length() > 1) { + filter.append(" AND "); + } - filter.append(Part._ID); - filter.append("!="); - DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment()); + filter.append(Part._ID); + filter.append("!="); + DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment()); + } } - } - filter.append(')'); + filter.append(')'); - long msgId = ContentUris.parseId(uri); + long msgId = ContentUris.parseId(uri); - // Remove the parts which doesn't exist anymore. - SqliteWrapper.delete(mContext, mContentResolver, - Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"), - filter.length() > 2 ? filter.toString() : null, null); + // Remove the parts which doesn't exist anymore. + SqliteWrapper.delete(mContext, mContentResolver, + Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"), + filter.length() > 2 ? filter.toString() : null, null); - // Create new parts which didn't exist before. - for (PduPart part : toBeCreated) { - persistPart(part, msgId); - } + // Create new parts which didn't exist before. + for (PduPart part : toBeCreated) { + persistPart(part, msgId); + } - // Update the modified parts. - for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) { - updatePart(e.getKey(), e.getValue()); + // Update the modified parts. + for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) { + updatePart(e.getKey(), e.getValue()); + } + } finally { + synchronized(PDU_CACHE_INSTANCE) { + PDU_CACHE_INSTANCE.setUpdating(uri, false); + PDU_CACHE_INSTANCE.notifyAll(); + } } } @@ -1029,15 +1078,32 @@ public class PduPersister { if (uri == null) { throw new MmsException("Uri may not be null."); } + long msgId = -1; + try { + msgId = ContentUris.parseId(uri); + } catch (NumberFormatException e) { + // the uri ends with "inbox" or something else like that + } + boolean existingUri = msgId != -1; - Integer msgBox = MESSAGE_BOX_MAP.get(uri); - if (msgBox == null) { + if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) { throw new MmsException( "Bad destination, must be one of " + "content://mms/inbox, content://mms/sent, " + "content://mms/drafts, content://mms/outbox, " + "content://mms/temp."); } + synchronized(PDU_CACHE_INSTANCE) { + // If the cache item is getting updated, wait until it's done updating before + // purging it. + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "persist1: ", e); + } + } + } PDU_CACHE_INSTANCE.purge(uri); PduHeaders header = pdu.getPduHeaders(); @@ -1145,14 +1211,20 @@ public class PduPersister { } } - Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); - if (res == null) { - throw new MmsException("persist() failed: return null."); + Uri res = null; + if (existingUri) { + res = uri; + SqliteWrapper.update(mContext, mContentResolver, res, values, null, null); + } else { + res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); + if (res == null) { + throw new MmsException("persist() failed: return null."); + } + // Get the real ID of the PDU and update all parts which were + // saved with the dummy ID. + msgId = ContentUris.parseId(res); } - // Get the real ID of the PDU and update all parts which were - // saved with the dummy ID. - long msgId = ContentUris.parseId(res); values = new ContentValues(1); values.put(Part.MSG_ID, msgId); SqliteWrapper.update(mContext, mContentResolver, @@ -1163,7 +1235,9 @@ public class PduPersister { // persisted PDU is '8', we should return "content://mms/inbox/8" // instead of "content://mms/8". // FIXME: Should the MmsProvider be responsible for this??? - res = Uri.parse(uri + "/" + msgId); + if (!existingUri) { + res = Uri.parse(uri + "/" + msgId); + } // Save address information. for (int addrType : ADDRESS_FIELDS) { diff --git a/core/java/com/google/android/mms/util/PduCache.java b/core/java/com/google/android/mms/util/PduCache.java index 059af72a0daf..87cb48e53370 100644 --- a/core/java/com/google/android/mms/util/PduCache.java +++ b/core/java/com/google/android/mms/util/PduCache.java @@ -73,10 +73,12 @@ public final class PduCache extends AbstractCache<Uri, PduCacheEntry> { private final HashMap<Integer, HashSet<Uri>> mMessageBoxes; private final HashMap<Long, HashSet<Uri>> mThreads; + private final HashSet<Uri> mUpdating; private PduCache() { mMessageBoxes = new HashMap<Integer, HashSet<Uri>>(); mThreads = new HashMap<Long, HashSet<Uri>>(); + mUpdating = new HashSet<Uri>(); } synchronized public static final PduCache getInstance() { @@ -111,9 +113,22 @@ public final class PduCache extends AbstractCache<Uri, PduCacheEntry> { msgBox.add(finalKey); thread.add(finalKey); } + setUpdating(uri, false); return result; } + synchronized public void setUpdating(Uri uri, boolean updating) { + if (updating) { + mUpdating.add(uri); + } else { + mUpdating.remove(uri); + } + } + + synchronized public boolean isUpdating(Uri uri) { + return mUpdating.contains(uri); + } + @Override synchronized public PduCacheEntry purge(Uri uri) { int match = URI_MATCHER.match(uri); diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 23ee8f8ba1b0..ef6af74d6d33 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -67,7 +67,7 @@ public: static void freeCaches(JNIEnv* env, jobject) { // these are called in no particular order SkImageRef_GlobalPool::SetRAMUsed(0); - SkGraphics::SetFontCacheUsed(0); + SkGraphics::PurgeFontCache(); } static jboolean isOpaque(JNIEnv* env, jobject jcanvas) { diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 47ffd948c6f0..a1d41eeb2179 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -518,12 +518,6 @@ JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env) bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { JNIEnv* env = vm2env(fVM); - // If allocating in the Java heap, only allow a single object to be - // allocated for the lifetime of this object. - if (fStorageObj != NULL) { - SkDebugf("WARNING: One-shot allocator has already allocated (alloc count = %d)\n", fAllocCount); -// sk_throw(); - } fStorageObj = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable); fAllocCount += 1; return fStorageObj != NULL; diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp index c797a73159e5..7f5d54dd332a 100644 --- a/core/jni/android/graphics/TextLayoutCache.cpp +++ b/core/jni/android/graphics/TextLayoutCache.cpp @@ -29,7 +29,10 @@ extern "C" { namespace android { //-------------------------------------------------------------------------------------------------- -#define TYPEFACE_ARABIC "/system/fonts/DroidNaskh-Regular.ttf" +// Using DroidSansArabic for shaping Arabic with Harfbuzz because its metrics are more compatible +// with the "Roboto" metrics (compared to DroidNaskh-Regular). When we will have an Arabic font +// whose metrics are similar to the Roboto ones, then we will need to use it for shaping. +#define TYPEFACE_ARABIC "/system/fonts/DroidSansArabic.ttf" #define TYPE_FACE_HEBREW_REGULAR "/system/fonts/DroidSansHebrew-Regular.ttf" #define TYPE_FACE_HEBREW_BOLD "/system/fonts/DroidSansHebrew-Bold.ttf" #define TYPEFACE_BENGALI "/system/fonts/Lohit-Bengali.ttf" @@ -37,6 +40,8 @@ namespace android { ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine); +static KeyedVector<UChar, UChar> gBidiMirrored; + //-------------------------------------------------------------------------------------------------- TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) : @@ -350,6 +355,23 @@ TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) { mShaperItem.font = &mFontRec; mShaperItem.font->userData = &mShapingPaint; + + // Fill the BiDi mirrored chars map + // See: http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt + gBidiMirrored.add('(', ')'); + gBidiMirrored.add(')', '('); + gBidiMirrored.add('[', ']'); + gBidiMirrored.add(']', '['); + gBidiMirrored.add('{', '}'); + gBidiMirrored.add('}', '{'); + gBidiMirrored.add('<', '>'); + gBidiMirrored.add('>', '<'); + gBidiMirrored.add(0x00ab, 0x00bb); // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + gBidiMirrored.add(0x00bb, 0x00ab); // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + gBidiMirrored.add(0x2039, 0x203a); // SINGLE LEFT-POINTING ANGLE QUOTATION MARK + gBidiMirrored.add(0x203a, 0x2039); // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + gBidiMirrored.add(0x2264, 0x2265); // LESS-THAN OR EQUAL TO + gBidiMirrored.add(0x2265, 0x2264); // GREATER-THAN OR EQUAL TO } TextLayoutShaper::~TextLayoutShaper() { @@ -577,6 +599,31 @@ void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars } } + // Reverse "BiDi mirrored chars" in RTL mode only + // See: http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt + // This is a workaround because Harfbuzz is not able to do mirroring in all cases and + // script-run splitting with Harfbuzz is splitting on parenthesis + if (isRTL) { + for (ssize_t i = 0; i < ssize_t(count); i++) { + UChar ch = chars[i]; + ssize_t index = gBidiMirrored.indexOfKey(ch); + // Skip non "BiDi mirrored" chars + if (index < 0) { + continue; + } + if (!useNormalizedString) { + useNormalizedString = true; + mNormalizedString.setTo(false /* not terminated*/, chars, count); + } + UChar result = gBidiMirrored.valueAt(index); + mNormalizedString.setCharAt(i, result); +#if DEBUG_GLYPHS + ALOGD("Rewriting codepoint '%d' to '%d' at position %d", + ch, mNormalizedString[i], int(i)); +#endif + } + } + #if DEBUG_GLYPHS if (useNormalizedString) { ALOGD("Will use normalized string '%s', length = %d", diff --git a/core/jni/android_media_ToneGenerator.cpp b/core/jni/android_media_ToneGenerator.cpp index 53a05015785f..26e82aa92ed4 100644 --- a/core/jni/android_media_ToneGenerator.cpp +++ b/core/jni/android_media_ToneGenerator.cpp @@ -48,7 +48,7 @@ static jboolean android_media_ToneGenerator_startTone(JNIEnv *env, jobject thiz, return false; } - return lpToneGen->startTone(toneType, durationMs); + return lpToneGen->startTone((ToneGenerator::tone_type) toneType, durationMs); } static void android_media_ToneGenerator_stopTone(JNIEnv *env, jobject thiz) { diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 990a617e723f..e00970a2dbde 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -739,6 +739,11 @@ static jint android_os_Binder_getCallingUid(JNIEnv* env, jobject clazz) return IPCThreadState::self()->getCallingUid(); } +static jint android_os_Binder_getOrigCallingUid(JNIEnv* env, jobject clazz) +{ + return IPCThreadState::self()->getOrigCallingUid(); +} + static jlong android_os_Binder_clearCallingIdentity(JNIEnv* env, jobject clazz) { return IPCThreadState::self()->clearCallingIdentity(); @@ -810,6 +815,7 @@ static const JNINativeMethod gBinderMethods[] = { /* name, signature, funcPtr */ { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid }, { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid }, + { "getOrigCallingUidNative", "()I", (void*)android_os_Binder_getOrigCallingUid }, { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity }, { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity }, { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy }, diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 6a533c02ede0..e19bb38dc53d 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -188,6 +188,14 @@ static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject clazz, renderer->finish(); } +static jint android_view_GLES20Canvas_getStencilSize(JNIEnv* env, jobject clazz) { + return OpenGLRenderer::getStencilSize(); +} + +// ---------------------------------------------------------------------------- +// Functor +// ---------------------------------------------------------------------------- + static bool android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, Functor *functor) { android::uirenderer::Rect dirty; @@ -618,6 +626,15 @@ static jint android_view_GLES20Canvas_getDisplayListSize(JNIEnv* env, return displayList->getSize(); } +static void android_view_GLES20Canvas_setDisplayListName(JNIEnv* env, + jobject clazz, DisplayList* displayList, jstring name) { + if (name != NULL) { + const char* textArray = env->GetStringUTFChars(name, NULL); + displayList->setName(textArray); + env->ReleaseStringUTFChars(name, textArray); + } +} + static OpenGLRenderer* android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env, jobject clazz) { return new DisplayListRenderer; @@ -808,6 +825,8 @@ static JNINativeMethod gMethods[] = { { "nPrepareDirty", "(IIIIIZ)V", (void*) android_view_GLES20Canvas_prepareDirty }, { "nFinish", "(I)V", (void*) android_view_GLES20Canvas_finish }, + { "nGetStencilSize", "()I", (void*) android_view_GLES20Canvas_getStencilSize }, + { "nCallDrawGLFunction", "(II)Z", (void*) android_view_GLES20Canvas_callDrawGLFunction }, @@ -880,10 +899,14 @@ static JNINativeMethod gMethods[] = { { "nGetDisplayList", "(II)I", (void*) android_view_GLES20Canvas_getDisplayList }, { "nDestroyDisplayList", "(I)V", (void*) android_view_GLES20Canvas_destroyDisplayList }, { "nGetDisplayListSize", "(I)I", (void*) android_view_GLES20Canvas_getDisplayListSize }, - { "nCreateDisplayListRenderer", "()I", (void*) android_view_GLES20Canvas_createDisplayListRenderer }, - { "nResetDisplayListRenderer", "(I)V", (void*) android_view_GLES20Canvas_resetDisplayListRenderer }, + { "nSetDisplayListName", "(ILjava/lang/String;)V", + (void*) android_view_GLES20Canvas_setDisplayListName }, { "nDrawDisplayList", "(IIIILandroid/graphics/Rect;)Z", (void*) android_view_GLES20Canvas_drawDisplayList }, + + { "nCreateDisplayListRenderer", "()I", (void*) android_view_GLES20Canvas_createDisplayListRenderer }, + { "nResetDisplayListRenderer", "(I)V", (void*) android_view_GLES20Canvas_resetDisplayListRenderer }, + { "nOutputDisplayList", "(II)V", (void*) android_view_GLES20Canvas_outputDisplayList }, { "nInterrupt", "(I)V", (void*) android_view_GLES20Canvas_interrupt }, { "nResume", "(I)V", (void*) android_view_GLES20Canvas_resume }, diff --git a/core/res/MakeJavaSymbols.sed b/core/res/MakeJavaSymbols.sed new file mode 100644 index 000000000000..d02fffae17e5 --- /dev/null +++ b/core/res/MakeJavaSymbols.sed @@ -0,0 +1,25 @@ +# Run this on the errors output by javac of missing resource symbols, +# to generate the set of <java-symbol> commands to have aapt generate +# the symbol for them. +# +# For example: make framework 2>&1 | sed -n -f MakeJavaSymbols.sed | sort -u + +s|.*R.id.\([a-zA-Z0-9_]*\).*| <java-symbol type="id" name="\1" />|gp +s|.*R.attr.\([a-zA-Z0-9_]*\).*| <java-symbol type="attr" name="\1" />|gp +s|.*R.bool.\([a-zA-Z0-9_]*\).*| <java-symbol type="bool" name="\1" />|gp +s|.*R.integer.\([a-zA-Z0-9_]*\).*| <java-symbol type="integer" name="\1" />|gp +s|.*R.color.\([a-zA-Z0-9_]*\).*| <java-symbol type="color" name="\1" />|gp +s|.*R.dimen.\([a-zA-Z0-9_]*\).*| <java-symbol type="dimen" name="\1" />|gp +s|.*R.fraction.\([a-zA-Z0-9_]*\).*| <java-symbol type="fraction" name="\1" />|gp +s|.*R.string.\([a-zA-Z0-9_]*\).*| <java-symbol type="string" name="\1" />|gp +s|.*R.plurals.\([a-zA-Z0-9_]*\).*| <java-symbol type="plurals" name="\1" />|gp +s|.*R.array.\([a-zA-Z0-9_]*\).*| <java-symbol type="array" name="\1" />|gp +s|.*R.drawable.\([a-zA-Z0-9_]*\).*| <java-symbol type="drawable" name="\1" />|gp +s|.*R.layout.\([a-zA-Z0-9_]*\).*| <java-symbol type="layout" name="\1" />|gp +s|.*R.anim.\([a-zA-Z0-9_]*\).*| <java-symbol type="anim" name="\1" />|gp +s|.*R.animator.\([a-zA-Z0-9_]*\).*| <java-symbol type="animator" name="\1" />|gp +s|.*R.interpolator.\([a-zA-Z0-9_]*\).*| <java-symbol type="interpolator" name="\1" />|gp +s|.*R.menu.\([a-zA-Z0-9_]*\).*| <java-symbol type="menu" name="\1" />|gp +s|.*R.xml.\([a-zA-Z0-9_]*\).*| <java-symbol type="xml" name="\1" />|gp +s|.*R.raw.\([a-zA-Z0-9_]*\).*| <java-symbol type="raw" name="\1" />|gp +s|.*R.style.\([a-zA-Z0-9_]*\).*| <java-symbol type="style" name="\1" />|gp diff --git a/core/res/lint.xml b/core/res/lint.xml new file mode 100644 index 000000000000..822cdd7c5c79 --- /dev/null +++ b/core/res/lint.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<lint> + <!-- temporary until we add frameworks support --> + <issue id="UnusedResources" severity="ignore" /> + + <issue id="PrivateResource" severity="ignore" /> +</lint>
\ No newline at end of file diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 030a6b3b9865..1175cbbe2033 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -881,7 +881,7 @@ <string name="replace" msgid="5781686059063148930">"Erstat..."</string> <string name="delete" msgid="6098684844021697789">"Slet"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopier webadresse"</string> - <string name="selectTextMode" msgid="1018691815143165326">"Marker tekst"</string> + <string name="selectTextMode" msgid="1018691815143165326">"Markér tekst"</string> <string name="textSelectionCABTitle" msgid="5236850394370820357">"Tekstmarkering"</string> <string name="addToDictionary" msgid="9090375111134433012">"føj til ordbog"</string> <string name="deleteText" msgid="7070985395199629156">"slet"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 1068b6675076..56af9aa7226d 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -785,7 +785,7 @@ </plurals> <plurals name="num_minutes_ago"> <item quantity="one" msgid="3306787433088810191">"iminithi elingu-1 edlule"</item> - <item quantity="other" msgid="2176942008915455116">"amaminithi angu-<xliff:g id="COUNT">%d</xliff:g> adlule.."</item> + <item quantity="other" msgid="2176942008915455116">"<xliff:g id="COUNT">%d</xliff:g> amaminithi adlule.."</item> </plurals> <plurals name="num_hours_ago"> <item quantity="one" msgid="9150797944610821849">"ihora elingu-1 elidlule"</item> @@ -822,7 +822,7 @@ </plurals> <plurals name="abbrev_num_minutes_ago"> <item quantity="one" msgid="6361490147113871545">"iminithi elingu-1 edlule"</item> - <item quantity="other" msgid="851164968597150710">"amaminithi angu-<xliff:g id="COUNT">%d</xliff:g> adlule"</item> + <item quantity="other" msgid="851164968597150710">"<xliff:g id="COUNT">%d</xliff:g> amaminithi adlule"</item> </plurals> <plurals name="abbrev_num_hours_ago"> <item quantity="one" msgid="4796212039724722116">"ihora elingu-1 elidlule"</item> @@ -1111,7 +1111,7 @@ <string name="no_matches" msgid="8129421908915840737">"Akukho okufanayo"</string> <string name="find_on_page" msgid="1946799233822820384">"Thola ekhasini"</string> <plurals name="matches_found"> - <item quantity="one" msgid="8167147081136579439">"okufanayo okungu-1"</item> + <item quantity="one" msgid="8167147081136579439">"1 okufanayo"</item> <item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> ku-<xliff:g id="TOTAL">%d</xliff:g>"</item> </plurals> <string name="action_mode_done" msgid="7217581640461922289">"Kwenziwe"</string> diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index f9e1f5bd5d5f..ce734fce8bb6 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -22,19 +22,9 @@ <!-- Do not translate. These are all of the drawable resources that should be preloaded by the zygote process before it starts forking application processes. --> <array name="preloaded_drawables"> - <item>@drawable/spinner_black_16</item> - <item>@drawable/spinner_black_20</item> - <item>@drawable/spinner_black_48</item> - <item>@drawable/spinner_black_76</item> - <item>@drawable/spinner_white_16</item> - <item>@drawable/spinner_white_48</item> - <item>@drawable/spinner_white_76</item> - <item>@drawable/toast_frame</item> <item>@drawable/toast_frame_holo</item> - <item>@drawable/btn_check_on_selected</item> <item>@drawable/btn_check_on_pressed_holo_light</item> <item>@drawable/btn_check_on_pressed_holo_dark</item> - <item>@drawable/btn_check_on_pressed</item> <item>@drawable/btn_check_on_holo_light</item> <item>@drawable/btn_check_on_holo_dark</item> <item>@drawable/btn_check_on_focused_holo_light</item> @@ -43,13 +33,8 @@ <item>@drawable/btn_check_on_disabled_holo_dark</item> <item>@drawable/btn_check_on_disabled_focused_holo_light</item> <item>@drawable/btn_check_on_disabled_focused_holo_dark</item> - <item>@drawable/btn_check_on_disable_focused</item> - <item>@drawable/btn_check_on_disable</item> - <item>@drawable/btn_check_on</item> - <item>@drawable/btn_check_off_selected</item> <item>@drawable/btn_check_off_pressed_holo_light</item> <item>@drawable/btn_check_off_pressed_holo_dark</item> - <item>@drawable/btn_check_off_pressed</item> <item>@drawable/btn_check_off_holo_light</item> <item>@drawable/btn_check_off_holo_dark</item> <item>@drawable/btn_check_off_focused_holo_light</item> @@ -58,16 +43,10 @@ <item>@drawable/btn_check_off_disabled_holo_dark</item> <item>@drawable/btn_check_off_disabled_focused_holo_light</item> <item>@drawable/btn_check_off_disabled_focused_holo_dark</item> - <item>@drawable/btn_check_off_disable_focused</item> - <item>@drawable/btn_check_off_disable</item> - <item>@drawable/btn_check_label_background</item> <item>@drawable/btn_check_holo_light</item> <item>@drawable/btn_check_holo_dark</item> - <item>@drawable/btn_check</item> - <item>@drawable/btn_radio_on_selected</item> <item>@drawable/btn_radio_on_pressed_holo_light</item> <item>@drawable/btn_radio_on_pressed_holo_dark</item> - <item>@drawable/btn_radio_on_pressed</item> <item>@drawable/btn_radio_on_holo_light</item> <item>@drawable/btn_radio_on_holo_dark</item> <item>@drawable/btn_radio_on_focused_holo_light</item> @@ -76,11 +55,8 @@ <item>@drawable/btn_radio_on_disabled_holo_dark</item> <item>@drawable/btn_radio_on_disabled_focused_holo_light</item> <item>@drawable/btn_radio_on_disabled_focused_holo_dark</item> - <item>@drawable/btn_radio_on</item> - <item>@drawable/btn_radio_off_selected</item> <item>@drawable/btn_radio_off_pressed_holo_light</item> <item>@drawable/btn_radio_off_pressed_holo_dark</item> - <item>@drawable/btn_radio_off_pressed</item> <item>@drawable/btn_radio_off_holo_light</item> <item>@drawable/btn_radio_off_holo_dark</item> <item>@drawable/btn_radio_off_focused_holo_light</item> @@ -89,23 +65,10 @@ <item>@drawable/btn_radio_off_disabled_holo_dark</item> <item>@drawable/btn_radio_off_disabled_focused_holo_light</item> <item>@drawable/btn_radio_off_disabled_focused_holo_dark</item> - <item>@drawable/btn_radio_label_background</item> - <item>@drawable/btn_radio</item> - <item>@drawable/btn_default_transparent_normal</item> - <item>@drawable/btn_default_small_selected</item> - <item>@drawable/btn_default_small_pressed</item> - <item>@drawable/btn_default_small_normal_disable_focused</item> - <item>@drawable/btn_default_small_normal_disable</item> - <item>@drawable/btn_default_small_normal</item> - <item>@drawable/btn_default_selected</item> <item>@drawable/btn_default_pressed_holo_light</item> <item>@drawable/btn_default_pressed_holo_dark</item> - <item>@drawable/btn_default_pressed</item> <item>@drawable/btn_default_normal_holo_light</item> <item>@drawable/btn_default_normal_holo_dark</item> - <item>@drawable/btn_default_normal_disable_focused</item> - <item>@drawable/btn_default_normal_disable</item> - <item>@drawable/btn_default_normal</item> <item>@drawable/btn_default_focused_holo_light</item> <item>@drawable/btn_default_focused_holo_dark</item> <item>@drawable/btn_default_disabled_holo_light</item> @@ -114,24 +77,6 @@ <item>@drawable/btn_default_disabled_focused_holo_dark</item> <item>@drawable/btn_default_holo_dark</item> <item>@drawable/btn_default_holo_light</item> - <item>@drawable/btn_default</item> - <item>@drawable/btn_default_small</item> - <item>@drawable/btn_dropdown_disabled</item> - <item>@drawable/btn_dropdown_disabled_focused</item> - <item>@drawable/btn_dropdown_normal</item> - <item>@drawable/btn_dropdown_pressed</item> - <item>@drawable/btn_dropdown_selected</item> - <item>@drawable/btn_star_label_background</item> - <item>@drawable/btn_star_big_off</item> - <item>@drawable/btn_star_big_on</item> - <item>@drawable/btn_star_big_on_disable</item> - <item>@drawable/btn_star_big_off_disable</item> - <item>@drawable/btn_star_big_on_pressed</item> - <item>@drawable/btn_star_big_off_pressed</item> - <item>@drawable/btn_star_big_on_selected</item> - <item>@drawable/btn_star_big_off_selected</item> - <item>@drawable/btn_star_big_on_disable_focused</item> - <item>@drawable/btn_star_big_off_disable_focused</item> <item>@drawable/btn_star_off_normal_holo_light</item> <item>@drawable/btn_star_on_normal_holo_light</item> <item>@drawable/btn_star_on_disabled_holo_light</item> @@ -154,7 +99,6 @@ <item>@drawable/btn_star_on_disabled_focused_holo_dark</item> <item>@drawable/btn_star_off_disabled_focused_holo_dark</item> <item>@drawable/btn_star_holo_dark</item> - <item>@drawable/btn_star</item> <item>@drawable/btn_toggle_on_pressed_holo_light</item> <item>@drawable/btn_toggle_on_pressed_holo_dark</item> <item>@drawable/btn_toggle_on_normal_holo_light</item> @@ -165,7 +109,6 @@ <item>@drawable/btn_toggle_on_disabled_holo_dark</item> <item>@drawable/btn_toggle_on_disabled_focused_holo_light</item> <item>@drawable/btn_toggle_on_disabled_focused_holo_dark</item> - <item>@drawable/btn_toggle_on</item> <item>@drawable/btn_toggle_off_pressed_holo_light</item> <item>@drawable/btn_toggle_off_pressed_holo_dark</item> <item>@drawable/btn_toggle_off_normal_holo_light</item> @@ -176,23 +119,10 @@ <item>@drawable/btn_toggle_off_disabled_holo_dark</item> <item>@drawable/btn_toggle_off_disabled_focused_holo_light</item> <item>@drawable/btn_toggle_off_disabled_focused_holo_dark</item> - <item>@drawable/btn_toggle_off</item> <item>@drawable/btn_toggle_holo_light</item> <item>@drawable/btn_toggle_holo_dark</item> - <item>@drawable/btn_toggle</item> - <item>@drawable/btn_toggle_bg</item> - <item>@drawable/btn_dropdown</item> - <item>@drawable/btn_dropdown</item> - <item>@drawable/light_header_dither</item> - <item>@drawable/divider_horizontal_textfield</item> - <item>@drawable/divider_horizontal_dark_opaque</item> - <item>@drawable/divider_horizontal_dark</item> - <item>@drawable/divider_horizontal_bright_opaque</item> - <item>@drawable/divider_horizontal_bright</item> - <item>@drawable/divider_vertical_dark</item> <item>@drawable/edit_text_holo_light</item> <item>@drawable/edit_text_holo_dark</item> - <item>@drawable/edit_text</item> <item>@drawable/text_cursor_holo_light</item> <item>@drawable/text_cursor_holo_dark</item> <item>@drawable/text_select_handle_left</item> @@ -200,70 +130,45 @@ <item>@drawable/text_edit_paste_window</item> <item>@drawable/expander_close_holo_dark</item> <item>@drawable/expander_close_holo_light</item> - <item>@drawable/expander_ic_maximized</item> - <item>@drawable/expander_ic_minimized</item> - <item>@drawable/expander_group</item> <item>@drawable/expander_group_holo_dark</item> <item>@drawable/expander_group_holo_light</item> - <item>@drawable/list_selector_background</item> - <item>@drawable/list_selector_background_light</item> - <item>@drawable/list_selector_background_longpress</item> - <item>@drawable/list_selector_background_longpress_light</item> - <item>@drawable/list_selector_background_pressed</item> - <item>@drawable/list_selector_background_pressed_light</item> - <item>@drawable/list_selector_background_selected</item> <item>@drawable/list_selector_holo_dark</item> <item>@drawable/list_selector_holo_light</item> <item>@drawable/list_section_divider_holo_light</item> <item>@drawable/list_section_divider_holo_dark</item> - <item>@drawable/menu_background</item> - <item>@drawable/menu_background_fill_parent_width</item> <item>@drawable/menu_hardkey_panel_holo_dark</item> <item>@drawable/menu_hardkey_panel_holo_light</item> <item>@drawable/menu_submenu_background</item> - <item>@drawable/menu_selector</item> <item>@drawable/menu_dropdown_panel_holo_light</item> <item>@drawable/menu_dropdown_panel_holo_dark</item> <item>@drawable/overscroll_edge</item> <item>@drawable/overscroll_glow</item> - <item>@drawable/panel_background</item> - <item>@drawable/popup_bottom_bright</item> - <item>@drawable/popup_bottom_dark</item> - <item>@drawable/popup_bottom_medium</item> - <item>@drawable/popup_center_bright</item> - <item>@drawable/popup_center_dark</item> - <item>@drawable/popup_center_medium</item> - <item>@drawable/popup_full_bright</item> - <item>@drawable/popup_full_dark</item> - <item>@drawable/popup_top_bright</item> - <item>@drawable/popup_top_dark</item> <item>@drawable/popup_inline_error_above_holo_dark</item> <item>@drawable/popup_inline_error_above_holo_light</item> <item>@drawable/popup_inline_error_holo_dark</item> <item>@drawable/popup_inline_error_holo_light</item> + <item>@drawable/spinner_16_outer_holo</item> + <item>@drawable/spinner_16_inner_holo</item> + <item>@drawable/spinner_48_outer_holo</item> + <item>@drawable/spinner_48_inner_holo</item> + <item>@drawable/spinner_76_outer_holo</item> + <item>@drawable/spinner_76_inner_holo</item> <item>@drawable/progress_bg_holo_dark</item> <item>@drawable/progress_bg_holo_light</item> - <item>@drawable/progress_horizontal</item> <item>@drawable/progress_horizontal_holo_dark</item> <item>@drawable/progress_horizontal_holo_light</item> - <item>@drawable/progress_indeterminate_horizontal</item> <item>@drawable/progress_indeterminate_horizontal_holo</item> - <item>@drawable/progress_large</item> <item>@drawable/progress_large_holo</item> - <item>@drawable/progress_large_white</item> - <item>@drawable/progress_medium</item> <item>@drawable/progress_medium_holo</item> - <item>@drawable/progress_medium_white</item> <item>@drawable/progress_primary_holo_dark</item> <item>@drawable/progress_primary_holo_light</item> <item>@drawable/progress_secondary_holo_dark</item> <item>@drawable/progress_secondary_holo_light</item> - <item>@drawable/progress_small</item> <item>@drawable/progress_small_holo</item> - <item>@drawable/progress_small_titlebar</item> - <item>@drawable/progress_small_white</item> <item>@drawable/scrubber_progress_horizontal_holo_dark</item> <item>@drawable/scrubber_progress_horizontal_holo_light</item> + <item>@drawable/background_holo_light</item> + <item>@drawable/background_holo_dark</item> <item>@drawable/screen_background_dark</item> <item>@drawable/screen_background_dark_transparent</item> <item>@drawable/screen_background_light</item> @@ -272,8 +177,6 @@ <item>@drawable/screen_background_selector_light</item> <item>@drawable/scrollbar_handle_holo_dark</item> <item>@drawable/scrollbar_handle_holo_light</item> - <item>@drawable/scrollbar_handle_horizontal</item> - <item>@drawable/scrollbar_handle_vertical</item> <item>@drawable/spinner_background_holo_dark</item> <item>@drawable/spinner_background_holo_light</item> <item>@drawable/spinner_ab_default_holo_dark</item> @@ -290,9 +193,6 @@ <item>@drawable/spinner_default_holo_light</item> <item>@drawable/spinner_disabled_holo_dark</item> <item>@drawable/spinner_disabled_holo_light</item> - <item>@drawable/spinner_dropdown_background</item> - <item>@drawable/spinner_dropdown_background_down</item> - <item>@drawable/spinner_dropdown_background_up</item> <item>@drawable/spinner_focused_holo_dark</item> <item>@drawable/spinner_focused_holo_light</item> <item>@drawable/spinner_pressed_holo_dark</item> @@ -337,7 +237,6 @@ <item>@drawable/dialog_middle_holo_light</item> <item>@drawable/dialog_top_holo_dark</item> <item>@drawable/dialog_top_holo_light</item> - <item>@drawable/ic_dialog_alert</item> <item>@drawable/ic_dialog_alert_holo_dark</item> <item>@drawable/ic_dialog_alert_holo_light</item> <item>@drawable/list_divider_holo_dark</item> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index b514bf5311cc..16b7ff36d92c 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2061,7 +2061,7 @@ </attr> <!-- Direction of the text. A heuristic is used to determine the resolved text direction of paragraphs. --> - <attr name="textDirection" format="integer"> + <attr name="textDirection" format="integer"> <!-- Default --> <enum name="inherit" value="0" /> <!-- Default for the root view. The first strong directional character determines the @@ -2072,16 +2072,12 @@ it is LTR if it contains any strong LTR characters. If there are neither, the paragraph direction is the view’s resolved layout direction. --> <enum name="anyRtl" value="2" /> - <!-- The paragraph direction is the same as the one held by a 60% majority of the - characters. If there is no majority then the paragraph direction is the resolved - layout direction of the View. --> - <enum name="charCount" value="3" /> <!-- The paragraph direction is left to right. --> - <enum name="ltr" value="4" /> + <enum name="ltr" value="3" /> <!-- The paragraph direction is right to left. --> - <enum name="rtl" value="5" /> + <enum name="rtl" value="4" /> <!-- The paragraph direction is coming from the system Locale. --> - <enum name="locale" value="6" /> + <enum name="locale" value="5" /> </attr> </declare-styleable> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 4d97ad20f4a5..e3c2bd8636ef 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -23,6 +23,1514 @@ SDK. Instead, put them here. --> <private-symbols package="com.android.internal" /> + <!-- Private symbols that we need to reference from framework code. See + frameworks/base/core/res/MakeJavaSymbols.sed for how to easily generate + this. + --> + <java-symbol type="id" name="account_name" /> + <java-symbol type="id" name="account_row_checkmark" /> + <java-symbol type="id" name="account_row_icon" /> + <java-symbol type="id" name="account_row_text" /> + <java-symbol type="id" name="account_type" /> + <java-symbol type="id" name="action_bar" /> + <java-symbol type="id" name="action_bar_container" /> + <java-symbol type="id" name="action_bar_title" /> + <java-symbol type="id" name="action_bar_subtitle" /> + <java-symbol type="id" name="action_context_bar" /> + <java-symbol type="id" name="action_menu_presenter" /> + <java-symbol type="id" name="action_mode_close_button" /> + <java-symbol type="id" name="activity_chooser_view_content" /> + <java-symbol type="id" name="addAccount" /> + <java-symbol type="id" name="albumart" /> + <java-symbol type="id" name="alertTitle" /> + <java-symbol type="id" name="allow_button" /> + <java-symbol type="id" name="alwaysUse" /> + <java-symbol type="id" name="amPm" /> + <java-symbol type="id" name="authtoken_type" /> + <java-symbol type="id" name="back_button" /> + <java-symbol type="id" name="btn_next" /> + <java-symbol type="id" name="btn_play" /> + <java-symbol type="id" name="btn_prev" /> + <java-symbol type="id" name="button_bar" /> + <java-symbol type="id" name="buttonPanel" /> + <java-symbol type="id" name="by_common" /> + <java-symbol type="id" name="by_org" /> + <java-symbol type="id" name="by_org_unit" /> + <java-symbol type="id" name="calendar_view" /> + <java-symbol type="id" name="cancel" /> + <java-symbol type="id" name="characterPicker" /> + <java-symbol type="id" name="clearDefaultHint" /> + <java-symbol type="id" name="contentPanel" /> + <java-symbol type="id" name="customPanel" /> + <java-symbol type="id" name="dangerous_perms_list" /> + <java-symbol type="id" name="datePicker" /> + <java-symbol type="id" name="day" /> + <java-symbol type="id" name="day_names" /> + <java-symbol type="id" name="decrement" /> + <java-symbol type="id" name="default_activity_button" /> + <java-symbol type="id" name="deny_button" /> + <java-symbol type="id" name="description" /> + <java-symbol type="id" name="divider" /> + <java-symbol type="id" name="edit_query" /> + <java-symbol type="id" name="edittext_container" /> + <java-symbol type="id" name="enter_pin_section" /> + <java-symbol type="id" name="expand_activities_button" /> + <java-symbol type="id" name="expand_button" /> + <java-symbol type="id" name="expand_button_divider" /> + <java-symbol type="id" name="expires_on" /> + <java-symbol type="id" name="find_next" /> + <java-symbol type="id" name="find_prev" /> + <java-symbol type="id" name="ffwd" /> + <java-symbol type="id" name="fillInIntent" /> + <java-symbol type="id" name="find" /> + <java-symbol type="id" name="fullscreenArea" /> + <java-symbol type="id" name="headers" /> + <java-symbol type="id" name="hour" /> + <java-symbol type="id" name="icon" /> + <java-symbol type="id" name="image" /> + <java-symbol type="id" name="imageButton" /> + <java-symbol type="id" name="increment" /> + <java-symbol type="id" name="internalEmpty" /> + <java-symbol type="id" name="info" /> + <java-symbol type="id" name="inputExtractAccessories" /> + <java-symbol type="id" name="inputExtractAction" /> + <java-symbol type="id" name="inputExtractEditButton" /> + <java-symbol type="id" name="issued_on" /> + <java-symbol type="id" name="left_icon" /> + <java-symbol type="id" name="leftSpacer" /> + <java-symbol type="id" name="line3" /> + <java-symbol type="id" name="list_footer" /> + <java-symbol type="id" name="list_item" /> + <java-symbol type="id" name="listContainer" /> + <java-symbol type="id" name="locale" /> + <java-symbol type="id" name="matches" /> + <java-symbol type="id" name="mediacontroller_progress" /> + <java-symbol type="id" name="minute" /> + <java-symbol type="id" name="mode_normal" /> + <java-symbol type="id" name="month" /> + <java-symbol type="id" name="month_name" /> + <java-symbol type="id" name="name" /> + <java-symbol type="id" name="next" /> + <java-symbol type="id" name="next_button" /> + <java-symbol type="id" name="new_app_action" /> + <java-symbol type="id" name="new_app_description" /> + <java-symbol type="id" name="new_app_icon" /> + <java-symbol type="id" name="no_permissions" /> + <java-symbol type="id" name="non_dangerous_perms_list" /> + <java-symbol type="id" name="numberpicker_input" /> + <java-symbol type="id" name="old_app_action" /> + <java-symbol type="id" name="old_app_description" /> + <java-symbol type="id" name="old_app_icon" /> + <java-symbol type="id" name="package_label" /> + <java-symbol type="id" name="packages_list" /> + <java-symbol type="id" name="pause" /> + <java-symbol type="id" name="perm_icon" /> + <java-symbol type="id" name="permission_group" /> + <java-symbol type="id" name="permission_list" /> + <java-symbol type="id" name="pickers" /> + <java-symbol type="id" name="prefs" /> + <java-symbol type="id" name="prefs_frame" /> + <java-symbol type="id" name="prev" /> + <java-symbol type="id" name="progress" /> + <java-symbol type="id" name="progress_circular" /> + <java-symbol type="id" name="progress_horizontal" /> + <java-symbol type="id" name="progress_number" /> + <java-symbol type="id" name="progress_percent" /> + <java-symbol type="id" name="progressContainer" /> + <java-symbol type="id" name="rew" /> + <java-symbol type="id" name="rightSpacer" /> + <java-symbol type="id" name="rowTypeId" /> + <java-symbol type="id" name="scrollView" /> + <java-symbol type="id" name="search_app_icon" /> + <java-symbol type="id" name="search_badge" /> + <java-symbol type="id" name="search_bar" /> + <java-symbol type="id" name="search_button" /> + <java-symbol type="id" name="search_close_btn" /> + <java-symbol type="id" name="search_edit_frame" /> + <java-symbol type="id" name="search_go_btn" /> + <java-symbol type="id" name="search_mag_icon" /> + <java-symbol type="id" name="search_plate" /> + <java-symbol type="id" name="search_src_text" /> + <java-symbol type="id" name="search_view" /> + <java-symbol type="id" name="search_voice_btn" /> + <java-symbol type="id" name="select_all" /> + <java-symbol type="id" name="serial_number" /> + <java-symbol type="id" name="seekbar" /> + <java-symbol type="id" name="sha1_fingerprint" /> + <java-symbol type="id" name="sha256_fingerprint" /> + <java-symbol type="id" name="share" /> + <java-symbol type="id" name="shortcut" /> + <java-symbol type="id" name="show_more" /> + <java-symbol type="id" name="show_more_icon" /> + <java-symbol type="id" name="show_more_text" /> + <java-symbol type="id" name="skip_button" /> + <java-symbol type="id" name="slider_group" /> + <java-symbol type="id" name="split_action_bar" /> + <java-symbol type="id" name="stream_icon" /> + <java-symbol type="id" name="submit_area" /> + <java-symbol type="id" name="switch_new" /> + <java-symbol type="id" name="switch_old" /> + <java-symbol type="id" name="switchWidget" /> + <java-symbol type="id" name="text" /> + <java-symbol type="id" name="textButton" /> + <java-symbol type="id" name="time" /> + <java-symbol type="id" name="time_current" /> + <java-symbol type="id" name="timeDisplayBackground" /> + <java-symbol type="id" name="timeDisplayForeground" /> + <java-symbol type="id" name="titleDivider" /> + <java-symbol type="id" name="titleDividerTop" /> + <java-symbol type="id" name="timePicker" /> + <java-symbol type="id" name="title_template" /> + <java-symbol type="id" name="to_common" /> + <java-symbol type="id" name="to_org" /> + <java-symbol type="id" name="to_org_unit" /> + <java-symbol type="id" name="topPanel" /> + <java-symbol type="id" name="up" /> + <java-symbol type="id" name="value" /> + <java-symbol type="id" name="visible_panel" /> + <java-symbol type="id" name="websearch" /> + <java-symbol type="id" name="wifi_p2p_wps_pin" /> + <java-symbol type="id" name="year" /> + <java-symbol type="id" name="zoomControls" /> + <java-symbol type="id" name="zoomIn" /> + <java-symbol type="id" name="zoomMagnify" /> + <java-symbol type="id" name="zoomOut" /> + + <java-symbol type="attr" name="actionModeShareDrawable" /> + <java-symbol type="attr" name="alertDialogCenterButtons" /> + <java-symbol type="attr" name="gestureOverlayViewStyle" /> + <java-symbol type="attr" name="keyboardViewStyle" /> + <java-symbol type="attr" name="numberPickerStyle" /> + <java-symbol type="attr" name="pointerStyle" /> + <java-symbol type="attr" name="preferenceFrameLayoutStyle" /> + <java-symbol type="attr" name="searchDialogTheme" /> + <java-symbol type="attr" name="searchViewSearchIcon" /> + <java-symbol type="attr" name="stackViewStyle" /> + <java-symbol type="attr" name="switchStyle" /> + <java-symbol type="attr" name="textAppearanceAutoCorrectionSuggestion" /> + <java-symbol type="attr" name="textAppearanceEasyCorrectSuggestion" /> + <java-symbol type="attr" name="textAppearanceMisspelledSuggestion" /> + <java-symbol type="attr" name="textColorSearchUrl" /> + <java-symbol type="attr" name="timePickerStyle" /> + + <java-symbol type="bool" name="action_bar_embed_tabs" /> + <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" /> + <java-symbol type="bool" name="config_allowActionMenuItemTextWithIcon" /> + <java-symbol type="bool" name="config_bluetooth_adapter_quick_switch" /> + <java-symbol type="bool" name="config_bluetooth_sco_off_call" /> + <java-symbol type="bool" name="config_duplicate_port_omadm_wappush" /> + <java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" /> + <java-symbol type="bool" name="config_enable_puk_unlock_screen" /> + <java-symbol type="bool" name="config_mms_content_disposition_support" /> + <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" /> + <java-symbol type="bool" name="config_sip_wifi_only" /> + <java-symbol type="bool" name="config_sms_capable" /> + <java-symbol type="bool" name="config_sms_utf8_support" /> + <java-symbol type="bool" name="config_swipeDisambiguation" /> + <java-symbol type="bool" name="config_telephony_use_own_number_for_voicemail" /> + <java-symbol type="bool" name="config_ui_enableFadingMarquee" /> + <java-symbol type="bool" name="config_use_strict_phone_number_comparation" /> + <java-symbol type="bool" name="config_voice_capable" /> + <java-symbol type="bool" name="preferences_prefer_dual_pane" /> + <java-symbol type="bool" name="skip_restoring_network_selection" /> + <java-symbol type="bool" name="split_action_bar_is_narrow" /> + + <java-symbol type="integer" name="config_cursorWindowSize" /> + <java-symbol type="integer" name="config_longPressOnPowerBehavior" /> + <java-symbol type="integer" name="config_max_pan_devices" /> + <java-symbol type="integer" name="config_ntpTimeout" /> + <java-symbol type="integer" name="config_wifi_framework_scan_interval" /> + <java-symbol type="integer" name="config_wifi_supplicant_scan_interval" /> + <java-symbol type="integer" name="db_connection_pool_size" /> + <java-symbol type="integer" name="max_action_buttons" /> + + <java-symbol type="color" name="tab_indicator_text_v4" /> + + <java-symbol type="dimen" name="config_prefDialogWidth" /> + <java-symbol type="dimen" name="config_viewConfigurationTouchSlop" /> + <java-symbol type="dimen" name="default_app_widget_padding_bottom" /> + <java-symbol type="dimen" name="default_app_widget_padding_left" /> + <java-symbol type="dimen" name="default_app_widget_padding_right" /> + <java-symbol type="dimen" name="default_app_widget_padding_top" /> + <java-symbol type="dimen" name="default_gap" /> + <java-symbol type="dimen" name="dropdownitem_icon_width" /> + <java-symbol type="dimen" name="dropdownitem_text_padding_left" /> + <java-symbol type="dimen" name="fastscroll_overlay_size" /> + <java-symbol type="dimen" name="fastscroll_thumb_height" /> + <java-symbol type="dimen" name="fastscroll_thumb_width" /> + <java-symbol type="dimen" name="fastscroll_thumb_width" /> + <java-symbol type="dimen" name="password_keyboard_spacebar_vertical_correction" /> + <java-symbol type="dimen" name="search_view_preferred_width" /> + <java-symbol type="dimen" name="textview_error_popup_default_width" /> + <java-symbol type="dimen" name="toast_y_offset" /> + <java-symbol type="dimen" name="volume_panel_top" /> + + <java-symbol type="string" name="addToDictionary" /> + <java-symbol type="string" name="action_bar_home_description" /> + <java-symbol type="string" name="action_bar_up_description" /> + <java-symbol type="string" name="delete" /> + <java-symbol type="string" name="deleteText" /> + <java-symbol type="string" name="ellipsis_two_dots" /> + <java-symbol type="string" name="ellipsis" /> + <java-symbol type="string" name="grant_permissions_header_text" /> + <java-symbol type="string" name="list_delimeter" /> + <java-symbol type="string" name="menu_delete_shortcut_label" /> + <java-symbol type="string" name="menu_enter_shortcut_label" /> + <java-symbol type="string" name="menu_space_shortcut_label" /> + <java-symbol type="string" name="notification_title" /> + <java-symbol type="string" name="permission_request_notification_with_subtitle" /> + <java-symbol type="string" name="prepend_shortcut_label" /> + <java-symbol type="string" name="replace" /> + <java-symbol type="string" name="textSelectionCABTitle" /> + <java-symbol type="string" name="BaMmi" /> + <java-symbol type="string" name="CLIRDefaultOffNextCallOff" /> + <java-symbol type="string" name="CLIRDefaultOffNextCallOn" /> + <java-symbol type="string" name="CLIRDefaultOnNextCallOff" /> + <java-symbol type="string" name="CLIRDefaultOnNextCallOn" /> + <java-symbol type="string" name="CLIRPermanent" /> + <java-symbol type="string" name="CfMmi" /> + <java-symbol type="string" name="ClipMmi" /> + <java-symbol type="string" name="ClirMmi" /> + <java-symbol type="string" name="CwMmi" /> + <java-symbol type="string" name="Midnight" /> + <java-symbol type="string" name="Noon" /> + <java-symbol type="string" name="PinMmi" /> + <java-symbol type="string" name="PwdMmi" /> + <java-symbol type="string" name="RestrictedChangedTitle" /> + <java-symbol type="string" name="RestrictedOnAllVoice" /> + <java-symbol type="string" name="RestrictedOnData" /> + <java-symbol type="string" name="RestrictedOnEmergency" /> + <java-symbol type="string" name="RestrictedOnNormal" /> + <java-symbol type="string" name="SetupCallDefault" /> + <java-symbol type="string" name="abbrev_month" /> + <java-symbol type="string" name="abbrev_month_day" /> + <java-symbol type="string" name="abbrev_month_day_year" /> + <java-symbol type="string" name="abbrev_month_year" /> + <java-symbol type="string" name="accept" /> + <java-symbol type="string" name="activity_chooser_view_see_all" /> + <java-symbol type="string" name="activitychooserview_choose_application" /> + <java-symbol type="string" name="alternate_eri_file" /> + <java-symbol type="string" name="alwaysUse" /> + <java-symbol type="string" name="am" /> + <java-symbol type="string" name="autofill_address_line_1_label_re" /> + <java-symbol type="string" name="autofill_address_line_1_re" /> + <java-symbol type="string" name="autofill_address_line_2_re" /> + <java-symbol type="string" name="autofill_address_line_3_re" /> + <java-symbol type="string" name="autofill_address_name_separator" /> + <java-symbol type="string" name="autofill_address_summary_format" /> + <java-symbol type="string" name="autofill_address_summary_name_format" /> + <java-symbol type="string" name="autofill_address_summary_separator" /> + <java-symbol type="string" name="autofill_address_type_same_as_re" /> + <java-symbol type="string" name="autofill_address_type_use_my_re" /> + <java-symbol type="string" name="autofill_area" /> + <java-symbol type="string" name="autofill_area_code_notext_re" /> + <java-symbol type="string" name="autofill_area_code_re" /> + <java-symbol type="string" name="autofill_attention_ignored_re" /> + <java-symbol type="string" name="autofill_billing_designator_re" /> + <java-symbol type="string" name="autofill_card_cvc_re" /> + <java-symbol type="string" name="autofill_card_ignored_re" /> + <java-symbol type="string" name="autofill_card_number_re" /> + <java-symbol type="string" name="autofill_city_re" /> + <java-symbol type="string" name="autofill_company_re" /> + <java-symbol type="string" name="autofill_country_code_re" /> + <java-symbol type="string" name="autofill_country_re" /> + <java-symbol type="string" name="autofill_county" /> + <java-symbol type="string" name="autofill_department" /> + <java-symbol type="string" name="autofill_district" /> + <java-symbol type="string" name="autofill_email_re" /> + <java-symbol type="string" name="autofill_emirate" /> + <java-symbol type="string" name="autofill_expiration_date_re" /> + <java-symbol type="string" name="autofill_expiration_month_re" /> + <java-symbol type="string" name="autofill_fax_re" /> + <java-symbol type="string" name="autofill_first_name_re" /> + <java-symbol type="string" name="autofill_island" /> + <java-symbol type="string" name="autofill_last_name_re" /> + <java-symbol type="string" name="autofill_middle_initial_re" /> + <java-symbol type="string" name="autofill_middle_name_re" /> + <java-symbol type="string" name="autofill_name_on_card_contextual_re" /> + <java-symbol type="string" name="autofill_name_on_card_re" /> + <java-symbol type="string" name="autofill_name_re" /> + <java-symbol type="string" name="autofill_name_specific_re" /> + <java-symbol type="string" name="autofill_parish" /> + <java-symbol type="string" name="autofill_phone_extension_re" /> + <java-symbol type="string" name="autofill_phone_prefix_re" /> + <java-symbol type="string" name="autofill_phone_prefix_separator_re" /> + <java-symbol type="string" name="autofill_phone_re" /> + <java-symbol type="string" name="autofill_phone_suffix_re" /> + <java-symbol type="string" name="autofill_phone_suffix_separator_re" /> + <java-symbol type="string" name="autofill_postal_code" /> + <java-symbol type="string" name="autofill_prefecture" /> + <java-symbol type="string" name="autofill_province" /> + <java-symbol type="string" name="autofill_region_ignored_re" /> + <java-symbol type="string" name="autofill_shipping_designator_re" /> + <java-symbol type="string" name="autofill_state" /> + <java-symbol type="string" name="autofill_state_re" /> + <java-symbol type="string" name="autofill_this_form" /> + <java-symbol type="string" name="autofill_username_re" /> + <java-symbol type="string" name="autofill_zip_4_re" /> + <java-symbol type="string" name="autofill_zip_code" /> + <java-symbol type="string" name="autofill_zip_code_re" /> + <java-symbol type="string" name="badPin" /> + <java-symbol type="string" name="badPuk" /> + <java-symbol type="string" name="byteShort" /> + <java-symbol type="string" name="cfTemplateForwarded" /> + <java-symbol type="string" name="cfTemplateForwardedTime" /> + <java-symbol type="string" name="cfTemplateNotForwarded" /> + <java-symbol type="string" name="cfTemplateRegistered" /> + <java-symbol type="string" name="cfTemplateRegisteredTime" /> + <java-symbol type="string" name="checkbox_checked" /> + <java-symbol type="string" name="checkbox_not_checked" /> + <java-symbol type="string" name="chooseActivity" /> + <java-symbol type="string" name="config_default_dns_server" /> + <java-symbol type="string" name="config_ethernet_iface_regex" /> + <java-symbol type="string" name="config_ntpServer" /> + <java-symbol type="string" name="config_tether_apndata" /> + <java-symbol type="string" name="config_useragentprofile_url" /> + <java-symbol type="string" name="config_wifi_p2p_device_type" /> + <java-symbol type="string" name="contentServiceSync" /> + <java-symbol type="string" name="contentServiceSyncNotificationTitle" /> + <java-symbol type="string" name="contentServiceTooManyDeletesNotificationDesc" /> + <java-symbol type="string" name="date1_date2" /> + <java-symbol type="string" name="date1_time1_date2_time2" /> + <java-symbol type="string" name="date_and_time" /> + <java-symbol type="string" name="date_picker_decrement_day_button" /> + <java-symbol type="string" name="date_picker_decrement_month_button" /> + <java-symbol type="string" name="date_picker_decrement_year_button" /> + <java-symbol type="string" name="date_picker_dialog_title" /> + <java-symbol type="string" name="date_picker_increment_day_button" /> + <java-symbol type="string" name="date_picker_increment_month_button" /> + <java-symbol type="string" name="date_picker_increment_year_button" /> + <java-symbol type="string" name="date_time" /> + <java-symbol type="string" name="date_time_set" /> + <java-symbol type="string" name="day_of_week_long_friday" /> + <java-symbol type="string" name="day_of_week_long_monday" /> + <java-symbol type="string" name="day_of_week_long_saturday" /> + <java-symbol type="string" name="day_of_week_long_sunday" /> + <java-symbol type="string" name="day_of_week_long_thursday" /> + <java-symbol type="string" name="day_of_week_long_tuesday" /> + <java-symbol type="string" name="day_of_week_long_wednesday" /> + <java-symbol type="string" name="day_of_week_medium_friday" /> + <java-symbol type="string" name="day_of_week_medium_monday" /> + <java-symbol type="string" name="day_of_week_medium_saturday" /> + <java-symbol type="string" name="day_of_week_medium_sunday" /> + <java-symbol type="string" name="day_of_week_medium_thursday" /> + <java-symbol type="string" name="day_of_week_medium_tuesday" /> + <java-symbol type="string" name="day_of_week_medium_wednesday" /> + <java-symbol type="string" name="day_of_week_short_friday" /> + <java-symbol type="string" name="day_of_week_short_monday" /> + <java-symbol type="string" name="day_of_week_short_saturday" /> + <java-symbol type="string" name="day_of_week_short_sunday" /> + <java-symbol type="string" name="day_of_week_short_thursday" /> + <java-symbol type="string" name="day_of_week_short_tuesday" /> + <java-symbol type="string" name="day_of_week_short_wednesday" /> + <java-symbol type="string" name="day_of_week_shortest_friday" /> + <java-symbol type="string" name="day_of_week_shortest_monday" /> + <java-symbol type="string" name="day_of_week_shortest_saturday" /> + <java-symbol type="string" name="day_of_week_shortest_sunday" /> + <java-symbol type="string" name="day_of_week_shortest_thursday" /> + <java-symbol type="string" name="day_of_week_shortest_tuesday" /> + <java-symbol type="string" name="day_of_week_shortest_wednesday" /> + <java-symbol type="string" name="decline" /> + <java-symbol type="string" name="default_permission_group" /> + <java-symbol type="string" name="default_text_encoding" /> + <java-symbol type="string" name="description_target_unlock_tablet" /> + <java-symbol type="string" name="double_tap_toast" /> + <java-symbol type="string" name="elapsed_time_short_format_h_mm_ss" /> + <java-symbol type="string" name="elapsed_time_short_format_mm_ss" /> + <java-symbol type="string" name="emailTypeCustom" /> + <java-symbol type="string" name="emailTypeHome" /> + <java-symbol type="string" name="emailTypeMobile" /> + <java-symbol type="string" name="emailTypeOther" /> + <java-symbol type="string" name="emailTypeWork" /> + <java-symbol type="string" name="emergency_call_dialog_number_for_display" /> + <java-symbol type="string" name="emergency_calls_only" /> + <java-symbol type="string" name="eventTypeAnniversary" /> + <java-symbol type="string" name="eventTypeBirthday" /> + <java-symbol type="string" name="eventTypeCustom" /> + <java-symbol type="string" name="eventTypeOther" /> + <java-symbol type="string" name="extmedia_format_button_format" /> + <java-symbol type="string" name="extmedia_format_message" /> + <java-symbol type="string" name="extmedia_format_title" /> + <java-symbol type="string" name="fileSizeSuffix" /> + <java-symbol type="string" name="force_close" /> + <java-symbol type="string" name="format_error" /> + <java-symbol type="string" name="gadget_host_error_inflating" /> + <java-symbol type="string" name="gigabyteShort" /> + <java-symbol type="string" name="gpsNotifMessage" /> + <java-symbol type="string" name="gpsNotifTicker" /> + <java-symbol type="string" name="gpsNotifTitle" /> + <java-symbol type="string" name="gpsVerifNo" /> + <java-symbol type="string" name="gpsVerifYes" /> + <java-symbol type="string" name="gsm_alphabet_default_charset" /> + <java-symbol type="string" name="hour_ampm" /> + <java-symbol type="string" name="hour_cap_ampm" /> + <java-symbol type="string" name="hour_minute_24" /> + <java-symbol type="string" name="hour_minute_ampm" /> + <java-symbol type="string" name="hour_minute_cap_ampm" /> + <java-symbol type="string" name="httpError" /> + <java-symbol type="string" name="httpErrorAuth" /> + <java-symbol type="string" name="httpErrorConnect" /> + <java-symbol type="string" name="httpErrorFailedSslHandshake" /> + <java-symbol type="string" name="httpErrorFile" /> + <java-symbol type="string" name="httpErrorFileNotFound" /> + <java-symbol type="string" name="httpErrorIO" /> + <java-symbol type="string" name="httpErrorLookup" /> + <java-symbol type="string" name="httpErrorOk" /> + <java-symbol type="string" name="httpErrorProxyAuth" /> + <java-symbol type="string" name="httpErrorRedirectLoop" /> + <java-symbol type="string" name="httpErrorTimeout" /> + <java-symbol type="string" name="httpErrorTooManyRequests" /> + <java-symbol type="string" name="httpErrorUnsupportedAuthScheme" /> + <java-symbol type="string" name="imProtocolAim" /> + <java-symbol type="string" name="imProtocolCustom" /> + <java-symbol type="string" name="imProtocolGoogleTalk" /> + <java-symbol type="string" name="imProtocolIcq" /> + <java-symbol type="string" name="imProtocolJabber" /> + <java-symbol type="string" name="imProtocolMsn" /> + <java-symbol type="string" name="imProtocolNetMeeting" /> + <java-symbol type="string" name="imProtocolQq" /> + <java-symbol type="string" name="imProtocolSkype" /> + <java-symbol type="string" name="imProtocolYahoo" /> + <java-symbol type="string" name="imTypeCustom" /> + <java-symbol type="string" name="imTypeHome" /> + <java-symbol type="string" name="imTypeOther" /> + <java-symbol type="string" name="imTypeWork" /> + <java-symbol type="string" name="ime_action_default" /> + <java-symbol type="string" name="ime_action_done" /> + <java-symbol type="string" name="ime_action_go" /> + <java-symbol type="string" name="ime_action_next" /> + <java-symbol type="string" name="ime_action_previous" /> + <java-symbol type="string" name="ime_action_search" /> + <java-symbol type="string" name="ime_action_send" /> + <java-symbol type="string" name="invalidPin" /> + <java-symbol type="string" name="js_dialog_before_unload" /> + <java-symbol type="string" name="js_dialog_title" /> + <java-symbol type="string" name="js_dialog_title_default" /> + <java-symbol type="string" name="keyboard_headset_required_to_hear_password" /> + <java-symbol type="string" name="keyboard_password_character_no_headset" /> + <java-symbol type="string" name="keyboardview_keycode_alt" /> + <java-symbol type="string" name="keyboardview_keycode_cancel" /> + <java-symbol type="string" name="keyboardview_keycode_delete" /> + <java-symbol type="string" name="keyboardview_keycode_done" /> + <java-symbol type="string" name="keyboardview_keycode_enter" /> + <java-symbol type="string" name="keyboardview_keycode_mode_change" /> + <java-symbol type="string" name="keyboardview_keycode_shift" /> + <java-symbol type="string" name="kilobyteShort" /> + <java-symbol type="string" name="last_month" /> + <java-symbol type="string" name="launchBrowserDefault" /> + <java-symbol type="string" name="lockscreen_access_pattern_cell_added" /> + <java-symbol type="string" name="lockscreen_access_pattern_cleared" /> + <java-symbol type="string" name="lockscreen_access_pattern_detected" /> + <java-symbol type="string" name="lockscreen_access_pattern_start" /> + <java-symbol type="string" name="lockscreen_emergency_call" /> + <java-symbol type="string" name="lockscreen_return_to_call" /> + <java-symbol type="string" name="lockscreen_transport_pause_description" /> + <java-symbol type="string" name="lockscreen_transport_play_description" /> + <java-symbol type="string" name="lockscreen_transport_stop_description" /> + <java-symbol type="string" name="low_memory" /> + <java-symbol type="string" name="media_bad_removal" /> + <java-symbol type="string" name="media_checking" /> + <java-symbol type="string" name="media_removed" /> + <java-symbol type="string" name="media_shared" /> + <java-symbol type="string" name="media_unknown_state" /> + <java-symbol type="string" name="megabyteShort" /> + <java-symbol type="string" name="midnight" /> + <java-symbol type="string" name="mismatchPin" /> + <java-symbol type="string" name="mmiComplete" /> + <java-symbol type="string" name="mmiError" /> + <java-symbol type="string" name="mmiFdnError" /> + <java-symbol type="string" name="month" /> + <java-symbol type="string" name="month_day" /> + <java-symbol type="string" name="month_day_year" /> + <java-symbol type="string" name="month_long_april" /> + <java-symbol type="string" name="month_long_august" /> + <java-symbol type="string" name="month_long_december" /> + <java-symbol type="string" name="month_long_february" /> + <java-symbol type="string" name="month_long_january" /> + <java-symbol type="string" name="month_long_july" /> + <java-symbol type="string" name="month_long_june" /> + <java-symbol type="string" name="month_long_march" /> + <java-symbol type="string" name="month_long_may" /> + <java-symbol type="string" name="month_long_november" /> + <java-symbol type="string" name="month_long_october" /> + <java-symbol type="string" name="month_long_september" /> + <java-symbol type="string" name="month_long_standalone_april" /> + <java-symbol type="string" name="month_long_standalone_august" /> + <java-symbol type="string" name="month_long_standalone_december" /> + <java-symbol type="string" name="month_long_standalone_february" /> + <java-symbol type="string" name="month_long_standalone_january" /> + <java-symbol type="string" name="month_long_standalone_july" /> + <java-symbol type="string" name="month_long_standalone_june" /> + <java-symbol type="string" name="month_long_standalone_march" /> + <java-symbol type="string" name="month_long_standalone_may" /> + <java-symbol type="string" name="month_long_standalone_november" /> + <java-symbol type="string" name="month_long_standalone_october" /> + <java-symbol type="string" name="month_long_standalone_september" /> + <java-symbol type="string" name="month_medium_april" /> + <java-symbol type="string" name="month_medium_august" /> + <java-symbol type="string" name="month_medium_december" /> + <java-symbol type="string" name="month_medium_february" /> + <java-symbol type="string" name="month_medium_january" /> + <java-symbol type="string" name="month_medium_july" /> + <java-symbol type="string" name="month_medium_june" /> + <java-symbol type="string" name="month_medium_march" /> + <java-symbol type="string" name="month_medium_may" /> + <java-symbol type="string" name="month_medium_november" /> + <java-symbol type="string" name="month_medium_october" /> + <java-symbol type="string" name="month_medium_september" /> + <java-symbol type="string" name="month_shortest_april" /> + <java-symbol type="string" name="month_shortest_august" /> + <java-symbol type="string" name="month_shortest_december" /> + <java-symbol type="string" name="month_shortest_february" /> + <java-symbol type="string" name="month_shortest_january" /> + <java-symbol type="string" name="month_shortest_july" /> + <java-symbol type="string" name="month_shortest_june" /> + <java-symbol type="string" name="month_shortest_march" /> + <java-symbol type="string" name="month_shortest_may" /> + <java-symbol type="string" name="month_shortest_november" /> + <java-symbol type="string" name="month_shortest_october" /> + <java-symbol type="string" name="month_shortest_september" /> + <java-symbol type="string" name="month_year" /> + <java-symbol type="string" name="more_item_label" /> + <java-symbol type="string" name="needPuk" /> + <java-symbol type="string" name="needPuk2" /> + <java-symbol type="string" name="new_app_action" /> + <java-symbol type="string" name="new_app_description" /> + <java-symbol type="string" name="noApplications" /> + <java-symbol type="string" name="no_file_chosen" /> + <java-symbol type="string" name="no_matches" /> + <java-symbol type="string" name="noon" /> + <java-symbol type="string" name="number_picker_increment_scroll_action" /> + <java-symbol type="string" name="number_picker_increment_scroll_mode" /> + <java-symbol type="string" name="numeric_date" /> + <java-symbol type="string" name="numeric_date_format" /> + <java-symbol type="string" name="numeric_date_template" /> + <java-symbol type="string" name="numeric_md1_md2" /> + <java-symbol type="string" name="numeric_md1_time1_md2_time2" /> + <java-symbol type="string" name="numeric_mdy1_mdy2" /> + <java-symbol type="string" name="numeric_mdy1_time1_mdy2_time2" /> + <java-symbol type="string" name="numeric_wday1_md1_time1_wday2_md2_time2" /> + <java-symbol type="string" name="numeric_wday1_md1_wday2_md2" /> + <java-symbol type="string" name="numeric_wday1_mdy1_time1_wday2_mdy2_time2" /> + <java-symbol type="string" name="numeric_wday1_mdy1_wday2_mdy2" /> + <java-symbol type="string" name="old_app_action" /> + <java-symbol type="string" name="old_app_description" /> + <java-symbol type="string" name="older" /> + <java-symbol type="string" name="open_permission_deny" /> + <java-symbol type="string" name="orgTypeCustom" /> + <java-symbol type="string" name="orgTypeOther" /> + <java-symbol type="string" name="orgTypeWork" /> + <java-symbol type="string" name="passwordIncorrect" /> + <java-symbol type="string" name="permissions_format" /> + <java-symbol type="string" name="perms_hide" /> + <java-symbol type="string" name="perms_show_all" /> + <java-symbol type="string" name="petabyteShort" /> + <java-symbol type="string" name="phoneTypeAssistant" /> + <java-symbol type="string" name="phoneTypeCallback" /> + <java-symbol type="string" name="phoneTypeCar" /> + <java-symbol type="string" name="phoneTypeCompanyMain" /> + <java-symbol type="string" name="phoneTypeCustom" /> + <java-symbol type="string" name="phoneTypeFaxHome" /> + <java-symbol type="string" name="phoneTypeFaxWork" /> + <java-symbol type="string" name="phoneTypeHome" /> + <java-symbol type="string" name="phoneTypeIsdn" /> + <java-symbol type="string" name="phoneTypeMain" /> + <java-symbol type="string" name="phoneTypeMms" /> + <java-symbol type="string" name="phoneTypeMobile" /> + <java-symbol type="string" name="phoneTypeOther" /> + <java-symbol type="string" name="phoneTypeOtherFax" /> + <java-symbol type="string" name="phoneTypePager" /> + <java-symbol type="string" name="phoneTypeRadio" /> + <java-symbol type="string" name="phoneTypeTelex" /> + <java-symbol type="string" name="phoneTypeTtyTdd" /> + <java-symbol type="string" name="phoneTypeWork" /> + <java-symbol type="string" name="phoneTypeWorkMobile" /> + <java-symbol type="string" name="phoneTypeWorkPager" /> + <java-symbol type="string" name="pm" /> + <java-symbol type="string" name="policydesc_disableCamera" /> + <java-symbol type="string" name="policydesc_encryptedStorage" /> + <java-symbol type="string" name="policydesc_expirePassword" /> + <java-symbol type="string" name="policydesc_forceLock" /> + <java-symbol type="string" name="policydesc_limitPassword" /> + <java-symbol type="string" name="policydesc_resetPassword" /> + <java-symbol type="string" name="policydesc_setGlobalProxy" /> + <java-symbol type="string" name="policydesc_watchLogin" /> + <java-symbol type="string" name="policydesc_wipeData" /> + <java-symbol type="string" name="policylab_disableCamera" /> + <java-symbol type="string" name="policylab_encryptedStorage" /> + <java-symbol type="string" name="policylab_expirePassword" /> + <java-symbol type="string" name="policylab_forceLock" /> + <java-symbol type="string" name="policylab_limitPassword" /> + <java-symbol type="string" name="policylab_resetPassword" /> + <java-symbol type="string" name="policylab_setGlobalProxy" /> + <java-symbol type="string" name="policylab_watchLogin" /> + <java-symbol type="string" name="policylab_wipeData" /> + <java-symbol type="string" name="postalTypeCustom" /> + <java-symbol type="string" name="postalTypeHome" /> + <java-symbol type="string" name="postalTypeOther" /> + <java-symbol type="string" name="postalTypeWork" /> + <java-symbol type="string" name="power_off" /> + <java-symbol type="string" name="preposition_for_date" /> + <java-symbol type="string" name="preposition_for_time" /> + <java-symbol type="string" name="progress_erasing" /> + <java-symbol type="string" name="progress_unmounting" /> + <java-symbol type="string" name="radiobutton_not_selected" /> + <java-symbol type="string" name="radiobutton_selected" /> + <java-symbol type="string" name="relationTypeAssistant" /> + <java-symbol type="string" name="relationTypeBrother" /> + <java-symbol type="string" name="relationTypeChild" /> + <java-symbol type="string" name="relationTypeDomesticPartner" /> + <java-symbol type="string" name="relationTypeFather" /> + <java-symbol type="string" name="relationTypeFriend" /> + <java-symbol type="string" name="relationTypeManager" /> + <java-symbol type="string" name="relationTypeMother" /> + <java-symbol type="string" name="relationTypeParent" /> + <java-symbol type="string" name="relationTypePartner" /> + <java-symbol type="string" name="relationTypeReferredBy" /> + <java-symbol type="string" name="relationTypeRelative" /> + <java-symbol type="string" name="relationTypeSister" /> + <java-symbol type="string" name="relationTypeSpouse" /> + <java-symbol type="string" name="relative_time" /> + <java-symbol type="string" name="reset" /> + <java-symbol type="string" name="ringtone_default" /> + <java-symbol type="string" name="ringtone_default_with_actual" /> + <java-symbol type="string" name="ringtone_picker_title" /> + <java-symbol type="string" name="ringtone_silent" /> + <java-symbol type="string" name="ringtone_unknown" /> + <java-symbol type="string" name="roamingText0" /> + <java-symbol type="string" name="roamingText1" /> + <java-symbol type="string" name="roamingText10" /> + <java-symbol type="string" name="roamingText11" /> + <java-symbol type="string" name="roamingText12" /> + <java-symbol type="string" name="roamingText2" /> + <java-symbol type="string" name="roamingText3" /> + <java-symbol type="string" name="roamingText4" /> + <java-symbol type="string" name="roamingText5" /> + <java-symbol type="string" name="roamingText6" /> + <java-symbol type="string" name="roamingText7" /> + <java-symbol type="string" name="roamingText8" /> + <java-symbol type="string" name="roamingText9" /> + <java-symbol type="string" name="roamingTextSearching" /> + <java-symbol type="string" name="same_month_md1_md2" /> + <java-symbol type="string" name="same_month_md1_time1_md2_time2" /> + <java-symbol type="string" name="same_month_mdy1_mdy2" /> + <java-symbol type="string" name="same_month_mdy1_time1_mdy2_time2" /> + <java-symbol type="string" name="same_month_wday1_md1_time1_wday2_md2_time2" /> + <java-symbol type="string" name="same_month_wday1_md1_wday2_md2" /> + <java-symbol type="string" name="same_month_wday1_mdy1_time1_wday2_mdy2_time2" /> + <java-symbol type="string" name="same_month_wday1_mdy1_wday2_mdy2" /> + <java-symbol type="string" name="same_year_md1_md2" /> + <java-symbol type="string" name="same_year_md1_time1_md2_time2" /> + <java-symbol type="string" name="same_year_mdy1_mdy2" /> + <java-symbol type="string" name="same_year_mdy1_time1_mdy2_time2" /> + <java-symbol type="string" name="same_year_wday1_md1_time1_wday2_md2_time2" /> + <java-symbol type="string" name="same_year_wday1_md1_wday2_md2" /> + <java-symbol type="string" name="same_year_wday1_mdy1_time1_wday2_mdy2_time2" /> + <java-symbol type="string" name="same_year_wday1_mdy1_wday2_mdy2" /> + <java-symbol type="string" name="save_password_label" /> + <java-symbol type="string" name="save_password_message" /> + <java-symbol type="string" name="save_password_never" /> + <java-symbol type="string" name="save_password_notnow" /> + <java-symbol type="string" name="save_password_remember" /> + <java-symbol type="string" name="sendText" /> + <java-symbol type="string" name="sending" /> + <java-symbol type="string" name="serviceClassData" /> + <java-symbol type="string" name="serviceClassDataAsync" /> + <java-symbol type="string" name="serviceClassDataSync" /> + <java-symbol type="string" name="serviceClassFAX" /> + <java-symbol type="string" name="serviceClassPAD" /> + <java-symbol type="string" name="serviceClassPacket" /> + <java-symbol type="string" name="serviceClassSMS" /> + <java-symbol type="string" name="serviceClassVoice" /> + <java-symbol type="string" name="serviceDisabled" /> + <java-symbol type="string" name="serviceEnabled" /> + <java-symbol type="string" name="serviceEnabledFor" /> + <java-symbol type="string" name="serviceErased" /> + <java-symbol type="string" name="serviceNotProvisioned" /> + <java-symbol type="string" name="serviceRegistered" /> + <java-symbol type="string" name="setup_autofill" /> + <java-symbol type="string" name="shareactionprovider_share_with" /> + <java-symbol type="string" name="shareactionprovider_share_with_application" /> + <java-symbol type="string" name="short_format_month" /> + <java-symbol type="string" name="shutdown_confirm" /> + <java-symbol type="string" name="shutdown_confirm_question" /> + <java-symbol type="string" name="shutdown_progress" /> + <java-symbol type="string" name="sim_added_message" /> + <java-symbol type="string" name="sim_added_title" /> + <java-symbol type="string" name="sim_removed_message" /> + <java-symbol type="string" name="sim_removed_title" /> + <java-symbol type="string" name="sim_restart_button" /> + <java-symbol type="string" name="sipAddressTypeCustom" /> + <java-symbol type="string" name="sipAddressTypeHome" /> + <java-symbol type="string" name="sipAddressTypeOther" /> + <java-symbol type="string" name="sipAddressTypeWork" /> + <java-symbol type="string" name="sms_control_default_app_name" /> + <java-symbol type="string" name="sms_control_message" /> + <java-symbol type="string" name="sms_control_no" /> + <java-symbol type="string" name="sms_control_title" /> + <java-symbol type="string" name="sms_control_yes" /> + <java-symbol type="string" name="submit" /> + <java-symbol type="string" name="switch_off" /> + <java-symbol type="string" name="switch_on" /> + <java-symbol type="string" name="sync_binding_label" /> + <java-symbol type="string" name="sync_do_nothing" /> + <java-symbol type="string" name="sync_really_delete" /> + <java-symbol type="string" name="sync_too_many_deletes_desc" /> + <java-symbol type="string" name="sync_undo_deletes" /> + <java-symbol type="string" name="terabyteShort" /> + <java-symbol type="string" name="text_copied" /> + <java-symbol type="string" name="time1_time2" /> + <java-symbol type="string" name="time_date" /> + <java-symbol type="string" name="time_of_day" /> + <java-symbol type="string" name="time_picker_decrement_hour_button" /> + <java-symbol type="string" name="time_picker_decrement_minute_button" /> + <java-symbol type="string" name="time_picker_decrement_set_am_button" /> + <java-symbol type="string" name="time_picker_dialog_title" /> + <java-symbol type="string" name="time_picker_increment_hour_button" /> + <java-symbol type="string" name="time_picker_increment_minute_button" /> + <java-symbol type="string" name="time_picker_increment_set_pm_button" /> + <java-symbol type="string" name="time_picker_separator" /> + <java-symbol type="string" name="time_wday" /> + <java-symbol type="string" name="time_wday_date" /> + <java-symbol type="string" name="today" /> + <java-symbol type="string" name="togglebutton_not_pressed" /> + <java-symbol type="string" name="togglebutton_pressed" /> + <java-symbol type="string" name="tomorrow" /> + <java-symbol type="string" name="twelve_hour_time_format" /> + <java-symbol type="string" name="twenty_four_hour_time_format" /> + <java-symbol type="string" name="upload_file" /> + <java-symbol type="string" name="volume_alarm" /> + <java-symbol type="string" name="volume_icon_description_bluetooth" /> + <java-symbol type="string" name="volume_icon_description_incall" /> + <java-symbol type="string" name="volume_icon_description_media" /> + <java-symbol type="string" name="volume_icon_description_notification" /> + <java-symbol type="string" name="volume_icon_description_ringer" /> + <java-symbol type="string" name="wait" /> + <java-symbol type="string" name="wday1_date1_time1_wday2_date2_time2" /> + <java-symbol type="string" name="wday1_date1_wday2_date2" /> + <java-symbol type="string" name="wday_date" /> + <java-symbol type="string" name="web_user_agent" /> + <java-symbol type="string" name="web_user_agent_target_content" /> + <java-symbol type="string" name="webpage_unresponsive" /> + <java-symbol type="string" name="whichApplication" /> + <java-symbol type="string" name="wifi_available_sign_in" /> + <java-symbol type="string" name="wifi_available_sign_in_detailed" /> + <java-symbol type="string" name="wifi_p2p_dialog_title" /> + <java-symbol type="string" name="wifi_p2p_enabled_notification_message" /> + <java-symbol type="string" name="wifi_p2p_enabled_notification_title" /> + <java-symbol type="string" name="wifi_p2p_failed_message" /> + <java-symbol type="string" name="wifi_p2p_from_message" /> + <java-symbol type="string" name="wifi_p2p_invitation_sent_title" /> + <java-symbol type="string" name="wifi_p2p_invitation_to_connect_title" /> + <java-symbol type="string" name="wifi_p2p_show_pin_message" /> + <java-symbol type="string" name="wifi_p2p_to_message" /> + <java-symbol type="string" name="wifi_p2p_turnon_message" /> + <java-symbol type="string" name="wifi_tether_configure_ssid_default" /> + <java-symbol type="string" name="wifi_watchdog_network_disabled" /> + <java-symbol type="string" name="wifi_watchdog_network_disabled_detailed" /> + <java-symbol type="string" name="yesterday" /> + + <java-symbol type="plurals" name="abbrev_in_num_days" /> + <java-symbol type="plurals" name="abbrev_in_num_hours" /> + <java-symbol type="plurals" name="abbrev_in_num_minutes" /> + <java-symbol type="plurals" name="abbrev_in_num_seconds" /> + <java-symbol type="plurals" name="abbrev_num_days_ago" /> + <java-symbol type="plurals" name="abbrev_num_hours_ago" /> + <java-symbol type="plurals" name="abbrev_num_minutes_ago" /> + <java-symbol type="plurals" name="abbrev_num_seconds_ago" /> + <java-symbol type="plurals" name="in_num_days" /> + <java-symbol type="plurals" name="in_num_hours" /> + <java-symbol type="plurals" name="in_num_minutes" /> + <java-symbol type="plurals" name="in_num_seconds" /> + <java-symbol type="plurals" name="last_num_days" /> + <java-symbol type="plurals" name="matches_found" /> + <java-symbol type="plurals" name="num_days_ago" /> + <java-symbol type="plurals" name="num_hours_ago" /> + <java-symbol type="plurals" name="num_minutes_ago" /> + <java-symbol type="plurals" name="num_seconds_ago" /> + + <java-symbol type="array" name="carrier_properties" /> + <java-symbol type="array" name="config_data_usage_network_types" /> + <java-symbol type="array" name="config_sms_enabled_locking_shift_tables" /> + <java-symbol type="array" name="config_sms_enabled_single_shift_tables" /> + <java-symbol type="array" name="config_twoDigitNumberPattern" /> + <java-symbol type="array" name="networkAttributes" /> + <java-symbol type="array" name="preloaded_color_state_lists" /> + <java-symbol type="array" name="preloaded_drawables" /> + <java-symbol type="array" name="special_locale_codes" /> + <java-symbol type="array" name="special_locale_names" /> + + <java-symbol type="drawable" name="default_wallpaper" /> + <java-symbol type="drawable" name="ic_suggestions_add" /> + <java-symbol type="drawable" name="ic_suggestions_delete" /> + <java-symbol type="drawable" name="indicator_input_error" /> + <java-symbol type="drawable" name="overscroll_edge" /> + <java-symbol type="drawable" name="overscroll_glow" /> + <java-symbol type="drawable" name="popup_bottom_dark" /> + <java-symbol type="drawable" name="popup_bottom_bright" /> + <java-symbol type="drawable" name="popup_bottom_medium" /> + <java-symbol type="drawable" name="popup_center_dark" /> + <java-symbol type="drawable" name="popup_center_bright" /> + <java-symbol type="drawable" name="popup_full_dark" /> + <java-symbol type="drawable" name="popup_full_bright" /> + <java-symbol type="drawable" name="popup_top_dark" /> + <java-symbol type="drawable" name="popup_top_bright" /> + <java-symbol type="drawable" name="search_spinner" /> + <java-symbol type="drawable" name="sym_app_on_sd_unavailable_icon" /> + <java-symbol type="drawable" name="text_edit_side_paste_window" /> + <java-symbol type="drawable" name="text_edit_paste_window" /> + <java-symbol type="drawable" name="btn_check_off" /> + <java-symbol type="drawable" name="btn_code_lock_default_holo" /> + <java-symbol type="drawable" name="btn_code_lock_touched_holo" /> + <java-symbol type="drawable" name="clock_dial" /> + <java-symbol type="drawable" name="clock_hand_hour" /> + <java-symbol type="drawable" name="clock_hand_minute" /> + <java-symbol type="drawable" name="emo_im_angel" /> + <java-symbol type="drawable" name="emo_im_cool" /> + <java-symbol type="drawable" name="emo_im_crying" /> + <java-symbol type="drawable" name="emo_im_embarrassed" /> + <java-symbol type="drawable" name="emo_im_foot_in_mouth" /> + <java-symbol type="drawable" name="emo_im_happy" /> + <java-symbol type="drawable" name="emo_im_kissing" /> + <java-symbol type="drawable" name="emo_im_laughing" /> + <java-symbol type="drawable" name="emo_im_lips_are_sealed" /> + <java-symbol type="drawable" name="emo_im_money_mouth" /> + <java-symbol type="drawable" name="emo_im_sad" /> + <java-symbol type="drawable" name="emo_im_surprised" /> + <java-symbol type="drawable" name="emo_im_tongue_sticking_out" /> + <java-symbol type="drawable" name="emo_im_undecided" /> + <java-symbol type="drawable" name="emo_im_winking" /> + <java-symbol type="drawable" name="emo_im_wtf" /> + <java-symbol type="drawable" name="emo_im_yelling" /> + <java-symbol type="drawable" name="expander_close_holo_dark" /> + <java-symbol type="drawable" name="expander_open_holo_dark" /> + <java-symbol type="drawable" name="ic_audio_alarm" /> + <java-symbol type="drawable" name="ic_audio_alarm_mute" /> + <java-symbol type="drawable" name="ic_audio_bt" /> + <java-symbol type="drawable" name="ic_audio_bt_mute" /> + <java-symbol type="drawable" name="ic_audio_notification" /> + <java-symbol type="drawable" name="ic_audio_notification_mute" /> + <java-symbol type="drawable" name="ic_audio_phone" /> + <java-symbol type="drawable" name="ic_audio_ring_notif" /> + <java-symbol type="drawable" name="ic_audio_ring_notif_mute" /> + <java-symbol type="drawable" name="ic_audio_ring_notif_vibrate" /> + <java-symbol type="drawable" name="ic_audio_vol" /> + <java-symbol type="drawable" name="ic_audio_vol_mute" /> + <java-symbol type="drawable" name="ic_bullet_key_permission" /> + <java-symbol type="drawable" name="ic_contact_picture" /> + <java-symbol type="drawable" name="ic_dialog_usb" /> + <java-symbol type="drawable" name="ic_emergency" /> + <java-symbol type="drawable" name="ic_media_stop" /> + <java-symbol type="drawable" name="ic_text_dot" /> + <java-symbol type="drawable" name="indicator_code_lock_drag_direction_green_up" /> + <java-symbol type="drawable" name="indicator_code_lock_drag_direction_red_up" /> + <java-symbol type="drawable" name="indicator_code_lock_point_area_default_holo" /> + <java-symbol type="drawable" name="indicator_code_lock_point_area_green_holo" /> + <java-symbol type="drawable" name="indicator_code_lock_point_area_red_holo" /> + <java-symbol type="drawable" name="jog_dial_arrow_long_left_green" /> + <java-symbol type="drawable" name="jog_dial_arrow_long_right_red" /> + <java-symbol type="drawable" name="jog_dial_arrow_short_left_and_right" /> + <java-symbol type="drawable" name="jog_dial_bg" /> + <java-symbol type="drawable" name="jog_dial_dimple" /> + <java-symbol type="drawable" name="jog_dial_dimple_dim" /> + <java-symbol type="drawable" name="jog_tab_bar_left_generic" /> + <java-symbol type="drawable" name="jog_tab_bar_right_generic" /> + <java-symbol type="drawable" name="jog_tab_left_generic" /> + <java-symbol type="drawable" name="jog_tab_right_generic" /> + <java-symbol type="drawable" name="jog_tab_target_gray" /> + <java-symbol type="drawable" name="picture_emergency" /> + <java-symbol type="drawable" name="platlogo" /> + <java-symbol type="drawable" name="stat_notify_sync_error" /> + <java-symbol type="drawable" name="stat_notify_wifi_in_range" /> + <java-symbol type="drawable" name="stat_sys_gps_on" /> + <java-symbol type="drawable" name="stat_sys_tether_wifi" /> + <java-symbol type="drawable" name="status_bar_background" /> + <java-symbol type="drawable" name="sym_keyboard_shift" /> + <java-symbol type="drawable" name="sym_keyboard_shift_locked" /> + <java-symbol type="drawable" name="tab_bottom_left" /> + <java-symbol type="drawable" name="tab_bottom_left_v4" /> + <java-symbol type="drawable" name="tab_bottom_right" /> + <java-symbol type="drawable" name="tab_bottom_right_v4" /> + <java-symbol type="drawable" name="tab_indicator_v4" /> + <java-symbol type="drawable" name="text_select_handle_left" /> + <java-symbol type="drawable" name="text_select_handle_right" /> + <java-symbol type="drawable" name="unknown_image" /> + <java-symbol type="drawable" name="unlock_default" /> + <java-symbol type="drawable" name="unlock_halo" /> + <java-symbol type="drawable" name="unlock_ring" /> + <java-symbol type="drawable" name="unlock_wave" /> + + <java-symbol type="layout" name="action_bar_home" /> + <java-symbol type="layout" name="action_bar_title_item" /> + <java-symbol type="layout" name="action_menu_item_layout" /> + <java-symbol type="layout" name="action_menu_layout" /> + <java-symbol type="layout" name="action_mode_close_item" /> + <java-symbol type="layout" name="alert_dialog" /> + <java-symbol type="layout" name="choose_account" /> + <java-symbol type="layout" name="choose_account_row" /> + <java-symbol type="layout" name="choose_account_type" /> + <java-symbol type="layout" name="choose_selected_account_row" /> + <java-symbol type="layout" name="choose_type_and_account" /> + <java-symbol type="layout" name="grant_credentials_permission" /> + <java-symbol type="layout" name="number_picker" /> + <java-symbol type="layout" name="permissions_package_list_item" /> + <java-symbol type="layout" name="popup_menu_item_layout" /> + <java-symbol type="layout" name="remote_views_adapter_default_loading_view" /> + <java-symbol type="layout" name="search_bar" /> + <java-symbol type="layout" name="search_dropdown_item_icons_2line" /> + <java-symbol type="layout" name="search_view" /> + <java-symbol type="layout" name="select_dialog" /> + <java-symbol type="layout" name="simple_dropdown_hint" /> + <java-symbol type="layout" name="status_bar_latest_event_content" /> + <java-symbol type="layout" name="status_bar_latest_event_content_large_icon" /> + <java-symbol type="layout" name="status_bar_latest_event_ticker" /> + <java-symbol type="layout" name="status_bar_latest_event_ticker_large_icon" /> + <java-symbol type="layout" name="text_edit_action_popup_text" /> + <java-symbol type="layout" name="text_drag_thumbnail" /> + <java-symbol type="layout" name="typing_filter" /> + <java-symbol type="layout" name="activity_chooser_view" /> + <java-symbol type="layout" name="activity_chooser_view_list_item" /> + <java-symbol type="layout" name="activity_list" /> + <java-symbol type="layout" name="activity_list_item_2" /> + <java-symbol type="layout" name="alert_dialog_progress" /> + <java-symbol type="layout" name="always_use_checkbox" /> + <java-symbol type="layout" name="app_permission_item" /> + <java-symbol type="layout" name="app_perms_summary" /> + <java-symbol type="layout" name="calendar_view" /> + <java-symbol type="layout" name="character_picker" /> + <java-symbol type="layout" name="character_picker_button" /> + <java-symbol type="layout" name="date_picker" /> + <java-symbol type="layout" name="date_picker_dialog" /> + <java-symbol type="layout" name="expanded_menu_layout" /> + <java-symbol type="layout" name="fragment_bread_crumb_item" /> + <java-symbol type="layout" name="fragment_bread_crumbs" /> + <java-symbol type="layout" name="heavy_weight_switcher" /> + <java-symbol type="layout" name="icon_menu_item_layout" /> + <java-symbol type="layout" name="icon_menu_layout" /> + <java-symbol type="layout" name="input_method" /> + <java-symbol type="layout" name="input_method_extract_view" /> + <java-symbol type="layout" name="js_prompt" /> + <java-symbol type="layout" name="list_content_simple" /> + <java-symbol type="layout" name="list_menu_item_checkbox" /> + <java-symbol type="layout" name="list_menu_item_icon" /> + <java-symbol type="layout" name="list_menu_item_layout" /> + <java-symbol type="layout" name="list_menu_item_radio" /> + <java-symbol type="layout" name="locale_picker_item" /> + <java-symbol type="layout" name="media_controller" /> + <java-symbol type="layout" name="preference" /> + <java-symbol type="layout" name="preference_header_item" /> + <java-symbol type="layout" name="preference_list_content" /> + <java-symbol type="layout" name="preference_list_content_single" /> + <java-symbol type="layout" name="preference_list_fragment" /> + <java-symbol type="layout" name="preference_widget_seekbar" /> + <java-symbol type="layout" name="progress_dialog" /> + <java-symbol type="layout" name="resolve_list_item" /> + <java-symbol type="layout" name="seekbar_dialog" /> + <java-symbol type="layout" name="select_dialog_singlechoice_holo" /> + <java-symbol type="layout" name="ssl_certificate" /> + <java-symbol type="layout" name="tab_content" /> + <java-symbol type="layout" name="tab_indicator_holo" /> + <java-symbol type="layout" name="textview_hint" /> + <java-symbol type="layout" name="time_picker" /> + <java-symbol type="layout" name="time_picker_dialog" /> + <java-symbol type="layout" name="transient_notification" /> + <java-symbol type="layout" name="volume_adjust" /> + <java-symbol type="layout" name="volume_adjust_item" /> + <java-symbol type="layout" name="web_text_view_dropdown" /> + <java-symbol type="layout" name="webview_find" /> + <java-symbol type="layout" name="webview_select_singlechoice" /> + <java-symbol type="layout" name="wifi_p2p_dialog" /> + <java-symbol type="layout" name="wifi_p2p_dialog_row" /> + <java-symbol type="layout" name="zoom_container" /> + <java-symbol type="layout" name="zoom_controls" /> + <java-symbol type="layout" name="zoom_magnify" /> + + <java-symbol type="anim" name="slide_in_child_bottom" /> + <java-symbol type="anim" name="slide_in_right" /> + <java-symbol type="anim" name="slide_out_left" /> + + <java-symbol type="menu" name="webview_copy" /> + <java-symbol type="menu" name="webview_find" /> + + <java-symbol type="xml" name="password_kbd_qwerty" /> + <java-symbol type="xml" name="autotext" /> + <java-symbol type="xml" name="eri" /> + <java-symbol type="xml" name="password_kbd_numeric" /> + <java-symbol type="xml" name="password_kbd_qwerty_shifted" /> + <java-symbol type="xml" name="password_kbd_symbols" /> + <java-symbol type="xml" name="password_kbd_symbols_shift" /> + <java-symbol type="xml" name="power_profile" /> + <java-symbol type="xml" name="time_zones_by_country" /> + + <java-symbol type="raw" name="incognito_mode_start_page" /> + <java-symbol type="raw" name="loaderror" /> + <java-symbol type="raw" name="nodomain" /> + + <java-symbol type="style" name="Animation.DropDownUp" /> + <java-symbol type="style" name="Animation.DropDownDown" /> + <java-symbol type="style" name="Animation.PopupWindow" /> + <java-symbol type="style" name="Animation.TypingFilter" /> + <java-symbol type="style" name="Animation.TypingFilterRestore" /> + <java-symbol type="style" name="Theme.DeviceDefault.Dialog.Alert" /> + <java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.Alert" /> + <java-symbol type="style" name="Theme.Dialog.Alert" /> + <java-symbol type="style" name="Theme.Holo.Dialog.Alert" /> + <java-symbol type="style" name="Theme.Holo.Light.Dialog.Alert" /> + <java-symbol type="style" name="ActiveWallpaperSettings" /> + <java-symbol type="style" name="Animation.InputMethodFancy" /> + <java-symbol type="style" name="Animation.Wallpaper" /> + <java-symbol type="style" name="Animation.ZoomButtons" /> + <java-symbol type="style" name="PreviewWallpaperSettings" /> + <java-symbol type="style" name="TextAppearance.SlidingTabActive" /> + <java-symbol type="style" name="TextAppearance.SlidingTabNormal" /> + <java-symbol type="style" name="Theme.DeviceDefault.Dialog.NoFrame" /> + <java-symbol type="style" name="Theme.IconMenu" /> + <java-symbol type="style" name="Theme.Panel.Volume" /> + + <!-- From android.policy --> + <java-symbol type="anim" name="app_starting_exit" /> + <java-symbol type="anim" name="lock_screen_behind_enter" /> + <java-symbol type="array" name="config_keyboardTapVibePattern" /> + <java-symbol type="array" name="config_longPressVibePattern" /> + <java-symbol type="array" name="config_safeModeDisabledVibePattern" /> + <java-symbol type="array" name="config_safeModeEnabledVibePattern" /> + <java-symbol type="array" name="config_virtualKeyVibePattern" /> + <java-symbol type="array" name="lockscreen_targets_when_silent" /> + <java-symbol type="array" name="lockscreen_targets_when_soundon" /> + <java-symbol type="array" name="lockscreen_targets_with_camera" /> + <java-symbol type="attr" name="actionModePopupWindowStyle" /> + <java-symbol type="attr" name="dialogCustomTitleDecorLayout" /> + <java-symbol type="attr" name="dialogTitleDecorLayout" /> + <java-symbol type="attr" name="dialogTitleIconsDecorLayout" /> + <java-symbol type="bool" name="config_allowAllRotations" /> + <java-symbol type="bool" name="config_bypass_keyguard_if_slider_open" /> + <java-symbol type="bool" name="config_carDockEnablesAccelerometer" /> + <java-symbol type="bool" name="config_deskDockEnablesAccelerometer" /> + <java-symbol type="bool" name="config_disableMenuKeyInLockScreen" /> + <java-symbol type="bool" name="config_enableLockBeforeUnlockScreen" /> + <java-symbol type="bool" name="config_enableLockScreenRotation" /> + <java-symbol type="bool" name="config_reverseDefaultRotation" /> + <java-symbol type="bool" name="config_showNavigationBar" /> + <java-symbol type="bool" name="target_honeycomb_needs_options_menu" /> + <java-symbol type="dimen" name="navigation_bar_height" /> + <java-symbol type="dimen" name="navigation_bar_width" /> + <java-symbol type="dimen" name="status_bar_height" /> + <java-symbol type="dimen" name="system_bar_height" /> + <java-symbol type="drawable" name="ic_jog_dial_sound_off" /> + <java-symbol type="drawable" name="ic_jog_dial_sound_on" /> + <java-symbol type="drawable" name="ic_jog_dial_unlock" /> + <java-symbol type="drawable" name="ic_jog_dial_vibrate_on" /> + <java-symbol type="drawable" name="ic_lock_airplane_mode" /> + <java-symbol type="drawable" name="ic_lock_airplane_mode_off" /> + <java-symbol type="drawable" name="ic_menu_cc" /> + <java-symbol type="drawable" name="jog_tab_bar_left_unlock" /> + <java-symbol type="drawable" name="jog_tab_bar_right_sound_off" /> + <java-symbol type="drawable" name="jog_tab_bar_right_sound_on" /> + <java-symbol type="drawable" name="jog_tab_left_unlock" /> + <java-symbol type="drawable" name="jog_tab_right_sound_off" /> + <java-symbol type="drawable" name="jog_tab_right_sound_on" /> + <java-symbol type="drawable" name="jog_tab_target_green" /> + <java-symbol type="drawable" name="jog_tab_target_yellow" /> + <java-symbol type="drawable" name="menu_background" /> + <java-symbol type="drawable" name="stat_sys_secure" /> + <java-symbol type="id" name="action_mode_bar_stub" /> + <java-symbol type="id" name="alarm_status" /> + <java-symbol type="id" name="backspace" /> + <java-symbol type="id" name="button0" /> + <java-symbol type="id" name="button4" /> + <java-symbol type="id" name="button5" /> + <java-symbol type="id" name="button6" /> + <java-symbol type="id" name="button7" /> + <java-symbol type="id" name="carrier" /> + <java-symbol type="id" name="date" /> + <java-symbol type="id" name="eight" /> + <java-symbol type="id" name="emergencyCallButton" /> + <java-symbol type="id" name="faceLockAreaView" /> + <java-symbol type="id" name="five" /> + <java-symbol type="id" name="forgotPatternButton" /> + <java-symbol type="id" name="four" /> + <java-symbol type="id" name="headerText" /> + <java-symbol type="id" name="icon_menu_presenter" /> + <java-symbol type="id" name="instructions" /> + <java-symbol type="id" name="keyboard" /> + <java-symbol type="id" name="list_menu_presenter" /> + <java-symbol type="id" name="lockPattern" /> + <java-symbol type="id" name="lock_screen" /> + <java-symbol type="id" name="login" /> + <java-symbol type="id" name="nine" /> + <java-symbol type="id" name="no_applications_message" /> + <java-symbol type="id" name="ok" /> + <java-symbol type="id" name="one" /> + <java-symbol type="id" name="option1" /> + <java-symbol type="id" name="option2" /> + <java-symbol type="id" name="option3" /> + <java-symbol type="id" name="password" /> + <java-symbol type="id" name="passwordEntry" /> + <java-symbol type="id" name="pinDel" /> + <java-symbol type="id" name="pinDisplay" /> + <java-symbol type="id" name="propertyOf" /> + <java-symbol type="id" name="pukDel" /> + <java-symbol type="id" name="pukDisplay" /> + <java-symbol type="id" name="right_icon" /> + <java-symbol type="id" name="seven" /> + <java-symbol type="id" name="six" /> + <java-symbol type="id" name="status" /> + <java-symbol type="id" name="status1" /> + <java-symbol type="id" name="switch_ime_button" /> + <java-symbol type="id" name="three" /> + <java-symbol type="id" name="title_container" /> + <java-symbol type="id" name="topHeader" /> + <java-symbol type="id" name="transport" /> + <java-symbol type="id" name="transport_bg_protect" /> + <java-symbol type="id" name="two" /> + <java-symbol type="id" name="unlock_widget" /> + <java-symbol type="id" name="zero" /> + <java-symbol type="integer" name="config_carDockRotation" /> + <java-symbol type="integer" name="config_defaultUiModeType" /> + <java-symbol type="integer" name="config_deskDockRotation" /> + <java-symbol type="integer" name="config_lidKeyboardAccessibility" /> + <java-symbol type="integer" name="config_lidNavigationAccessibility" /> + <java-symbol type="integer" name="config_lidOpenRotation" /> + <java-symbol type="integer" name="config_longPressOnHomeBehavior" /> + <java-symbol type="layout" name="global_actions_item" /> + <java-symbol type="layout" name="global_actions_silent_mode" /> + <java-symbol type="layout" name="keyguard_screen_glogin_unlock" /> + <java-symbol type="layout" name="keyguard_screen_password_landscape" /> + <java-symbol type="layout" name="keyguard_screen_password_portrait" /> + <java-symbol type="layout" name="keyguard_screen_sim_pin_landscape" /> + <java-symbol type="layout" name="keyguard_screen_sim_pin_portrait" /> + <java-symbol type="layout" name="keyguard_screen_sim_puk_landscape" /> + <java-symbol type="layout" name="keyguard_screen_sim_puk_portrait" /> + <java-symbol type="layout" name="keyguard_screen_tab_unlock" /> + <java-symbol type="layout" name="keyguard_screen_tab_unlock_land" /> + <java-symbol type="layout" name="keyguard_screen_unlock_landscape" /> + <java-symbol type="layout" name="keyguard_screen_unlock_portrait" /> + <java-symbol type="layout" name="recent_apps_dialog" /> + <java-symbol type="layout" name="screen_action_bar" /> + <java-symbol type="layout" name="screen_action_bar_overlay" /> + <java-symbol type="layout" name="screen_custom_title" /> + <java-symbol type="layout" name="screen_progress" /> + <java-symbol type="layout" name="screen_simple" /> + <java-symbol type="layout" name="screen_simple_overlay_action_mode" /> + <java-symbol type="layout" name="screen_title" /> + <java-symbol type="layout" name="screen_title_icons" /> + <java-symbol type="string" name="abbrev_wday_month_day_no_year" /> + <java-symbol type="string" name="android_upgrading_title" /> + <java-symbol type="string" name="config_defaultDreamComponent" /> + <java-symbol type="string" name="faceunlock_multiple_failures" /> + <java-symbol type="string" name="global_action_power_off" /> + <java-symbol type="string" name="global_actions_airplane_mode_off_status" /> + <java-symbol type="string" name="global_actions_airplane_mode_on_status" /> + <java-symbol type="string" name="global_actions_toggle_airplane_mode" /> + <java-symbol type="string" name="invalidPuk" /> + <java-symbol type="string" name="keyguard_password_enter_pin_code" /> + <java-symbol type="string" name="keyguard_password_enter_puk_code" /> + <java-symbol type="string" name="keyguard_password_wrong_pin_code" /> + <java-symbol type="string" name="lockscreen_carrier_default" /> + <java-symbol type="string" name="lockscreen_charged" /> + <java-symbol type="string" name="lockscreen_failed_attempts_almost_at_wipe" /> + <java-symbol type="string" name="lockscreen_failed_attempts_almost_glogin" /> + <java-symbol type="string" name="lockscreen_failed_attempts_now_wiping" /> + <java-symbol type="string" name="lockscreen_forgot_pattern_button_text" /> + <java-symbol type="string" name="lockscreen_glogin_checking_password" /> + <java-symbol type="string" name="lockscreen_glogin_forgot_pattern" /> + <java-symbol type="string" name="lockscreen_glogin_invalid_input" /> + <java-symbol type="string" name="lockscreen_glogin_too_many_attempts" /> + <java-symbol type="string" name="lockscreen_instructions_when_pattern_disabled" /> + <java-symbol type="string" name="lockscreen_low_battery" /> + <java-symbol type="string" name="lockscreen_missing_sim_instructions" /> + <java-symbol type="string" name="lockscreen_missing_sim_instructions_long" /> + <java-symbol type="string" name="lockscreen_missing_sim_message_short" /> + <java-symbol type="string" name="lockscreen_network_locked_message" /> + <java-symbol type="string" name="lockscreen_password_wrong" /> + <java-symbol type="string" name="lockscreen_pattern_instructions" /> + <java-symbol type="string" name="lockscreen_pattern_wrong" /> + <java-symbol type="string" name="lockscreen_permanent_disabled_sim_instructions" /> + <java-symbol type="string" name="lockscreen_plugged_in" /> + <java-symbol type="string" name="lockscreen_sim_locked_message" /> + <java-symbol type="string" name="lockscreen_sim_puk_locked_message" /> + <java-symbol type="string" name="lockscreen_sim_unlock_progress_dialog_message" /> + <java-symbol type="string" name="lockscreen_sound_off_label" /> + <java-symbol type="string" name="lockscreen_sound_on_label" /> + <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" /> + <java-symbol type="string" name="lockscreen_too_many_failed_attempts_dialog_message" /> + <java-symbol type="string" name="lockscreen_too_many_failed_password_attempts_dialog_message" /> + <java-symbol type="string" name="lockscreen_too_many_failed_pin_attempts_dialog_message" /> + <java-symbol type="string" name="lockscreen_unlock_label" /> + <java-symbol type="string" name="status_bar_device_locked" /> + <java-symbol type="style" name="Animation.LockScreen" /> + <java-symbol type="style" name="Theme.Dialog.RecentApplications" /> + <java-symbol type="style" name="Theme.ExpandedMenu" /> + + <!-- From services --> + <java-symbol type="anim" name="screen_rotate_0_enter" /> + <java-symbol type="anim" name="screen_rotate_0_exit" /> + <java-symbol type="anim" name="screen_rotate_180_enter" /> + <java-symbol type="anim" name="screen_rotate_180_exit" /> + <java-symbol type="anim" name="screen_rotate_finish_enter" /> + <java-symbol type="anim" name="screen_rotate_finish_exit" /> + <java-symbol type="anim" name="screen_rotate_minus_90_enter" /> + <java-symbol type="anim" name="screen_rotate_minus_90_exit" /> + <java-symbol type="anim" name="screen_rotate_plus_90_enter" /> + <java-symbol type="anim" name="screen_rotate_plus_90_exit" /> + <java-symbol type="anim" name="screen_rotate_start_enter" /> + <java-symbol type="anim" name="screen_rotate_start_exit" /> + <java-symbol type="anim" name="window_move_from_decor" /> + <java-symbol type="array" name="config_autoBrightnessButtonBacklightValues" /> + <java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" /> + <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" /> + <java-symbol type="array" name="config_autoBrightnessLevels" /> + <java-symbol type="array" name="config_protectedNetworks" /> + <java-symbol type="array" name="config_statusBarIcons" /> + <java-symbol type="array" name="config_tether_bluetooth_regexs" /> + <java-symbol type="array" name="config_tether_dhcp_range" /> + <java-symbol type="array" name="config_tether_upstream_types" /> + <java-symbol type="array" name="config_tether_usb_regexs" /> + <java-symbol type="array" name="config_tether_wifi_regexs" /> + <java-symbol type="array" name="config_usbHostBlacklist" /> + <java-symbol type="array" name="radioAttributes" /> + <java-symbol type="bool" name="config_animateScreenLights" /> + <java-symbol type="bool" name="config_automatic_brightness_available" /> + <java-symbol type="bool" name="config_sf_limitedAlpha" /> + <java-symbol type="bool" name="config_unplugTurnsOnScreen" /> + <java-symbol type="bool" name="config_wifi_background_scan_support" /> + <java-symbol type="bool" name="config_wifi_dual_band_support" /> + <java-symbol type="bool" name="config_wimaxEnabled" /> + <java-symbol type="bool" name="show_ongoing_ime_switcher" /> + <java-symbol type="color" name="config_defaultNotificationColor" /> + <java-symbol type="drawable" name="ic_notification_ime_default" /> + <java-symbol type="drawable" name="stat_notify_car_mode" /> + <java-symbol type="drawable" name="stat_notify_disabled" /> + <java-symbol type="drawable" name="stat_notify_disk_full" /> + <java-symbol type="drawable" name="stat_sys_adb" /> + <java-symbol type="drawable" name="stat_sys_battery" /> + <java-symbol type="drawable" name="stat_sys_battery_charge" /> + <java-symbol type="drawable" name="stat_sys_battery_unknown" /> + <java-symbol type="drawable" name="stat_sys_data_usb" /> + <java-symbol type="drawable" name="stat_sys_tether_bluetooth" /> + <java-symbol type="drawable" name="stat_sys_tether_general" /> + <java-symbol type="drawable" name="stat_sys_tether_usb" /> + <java-symbol type="drawable" name="stat_sys_throttled" /> + <java-symbol type="drawable" name="vpn_connected" /> + <java-symbol type="id" name="ask_checkbox" /> + <java-symbol type="id" name="compat_checkbox" /> + <java-symbol type="id" name="original_app_icon" /> + <java-symbol type="id" name="original_message" /> + <java-symbol type="id" name="radio" /> + <java-symbol type="id" name="reask_hint" /> + <java-symbol type="id" name="replace_app_icon" /> + <java-symbol type="id" name="replace_message" /> + <java-symbol type="fraction" name="config_dimBehindFadeDuration" /> + <java-symbol type="integer" name="config_carDockKeepsScreenOn" /> + <java-symbol type="integer" name="config_criticalBatteryWarningLevel" /> + <java-symbol type="integer" name="config_datause_notification_type" /> + <java-symbol type="integer" name="config_datause_polling_period_sec" /> + <java-symbol type="integer" name="config_datause_threshold_bytes" /> + <java-symbol type="integer" name="config_datause_throttle_kbitsps" /> + <java-symbol type="integer" name="config_defaultNotificationLedOff" /> + <java-symbol type="integer" name="config_defaultNotificationLedOn" /> + <java-symbol type="integer" name="config_deskDockKeepsScreenOn" /> + <java-symbol type="integer" name="config_lightSensorWarmupTime" /> + <java-symbol type="integer" name="config_lowBatteryCloseWarningLevel" /> + <java-symbol type="integer" name="config_lowBatteryWarningLevel" /> + <java-symbol type="integer" name="config_networkPolicyDefaultWarning" /> + <java-symbol type="integer" name="config_networkTransitionTimeout" /> + <java-symbol type="integer" name="config_notificationsBatteryFullARGB" /> + <java-symbol type="integer" name="config_notificationsBatteryLedOff" /> + <java-symbol type="integer" name="config_notificationsBatteryLedOn" /> + <java-symbol type="integer" name="config_notificationsBatteryLowARGB" /> + <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> + <java-symbol type="integer" name="config_radioScanningTimeout" /> + <java-symbol type="integer" name="config_screenBrightnessDim" /> + <java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" /> + <java-symbol type="layout" name="am_compat_mode_dialog" /> + <java-symbol type="layout" name="launch_warning" /> + <java-symbol type="layout" name="safe_mode" /> + <java-symbol type="layout" name="simple_list_item_2_single_choice" /> + <java-symbol type="plurals" name="wifi_available" /> + <java-symbol type="plurals" name="wifi_available_detailed" /> + <java-symbol type="string" name="accessibility_binding_label" /> + <java-symbol type="string" name="adb_active_notification_message" /> + <java-symbol type="string" name="adb_active_notification_title" /> + <java-symbol type="string" name="aerr_application" /> + <java-symbol type="string" name="aerr_process" /> + <java-symbol type="string" name="aerr_title" /> + <java-symbol type="string" name="android_upgrading_apk" /> + <java-symbol type="string" name="android_upgrading_complete" /> + <java-symbol type="string" name="android_upgrading_starting_apps" /> + <java-symbol type="string" name="anr_activity_application" /> + <java-symbol type="string" name="anr_activity_process" /> + <java-symbol type="string" name="anr_application_process" /> + <java-symbol type="string" name="anr_process" /> + <java-symbol type="string" name="anr_title" /> + <java-symbol type="string" name="car_mode_disable_notification_message" /> + <java-symbol type="string" name="car_mode_disable_notification_title" /> + <java-symbol type="string" name="chooser_wallpaper" /> + <java-symbol type="string" name="config_datause_iface" /> + <java-symbol type="string" name="config_geocodeProvider" /> + <java-symbol type="string" name="config_networkLocationProvider" /> + <java-symbol type="string" name="config_wimaxManagerClassname" /> + <java-symbol type="string" name="config_wimaxNativeLibLocation" /> + <java-symbol type="string" name="config_wimaxServiceClassname" /> + <java-symbol type="string" name="config_wimaxServiceJarLocation" /> + <java-symbol type="string" name="config_wimaxStateTrackerClassname" /> + <java-symbol type="string" name="configure_input_methods" /> + <java-symbol type="string" name="data_usage_3g_limit_snoozed_title" /> + <java-symbol type="string" name="data_usage_3g_limit_title" /> + <java-symbol type="string" name="data_usage_4g_limit_snoozed_title" /> + <java-symbol type="string" name="data_usage_4g_limit_title" /> + <java-symbol type="string" name="data_usage_limit_body" /> + <java-symbol type="string" name="data_usage_limit_snoozed_body" /> + <java-symbol type="string" name="data_usage_mobile_limit_snoozed_title" /> + <java-symbol type="string" name="data_usage_mobile_limit_title" /> + <java-symbol type="string" name="data_usage_restricted_body" /> + <java-symbol type="string" name="data_usage_restricted_title" /> + <java-symbol type="string" name="data_usage_warning_body" /> + <java-symbol type="string" name="data_usage_warning_title" /> + <java-symbol type="string" name="data_usage_wifi_limit_snoozed_title" /> + <java-symbol type="string" name="data_usage_wifi_limit_title" /> + <java-symbol type="string" name="default_wallpaper_component" /> + <java-symbol type="string" name="dlg_ok" /> + <java-symbol type="string" name="factorytest_failed" /> + <java-symbol type="string" name="factorytest_no_action" /> + <java-symbol type="string" name="factorytest_not_system" /> + <java-symbol type="string" name="factorytest_reboot" /> + <java-symbol type="string" name="heavy_weight_notification" /> + <java-symbol type="string" name="heavy_weight_notification_detail" /> + <java-symbol type="string" name="input_method_binding_label" /> + <java-symbol type="string" name="launch_warning_original" /> + <java-symbol type="string" name="launch_warning_replace" /> + <java-symbol type="string" name="launch_warning_title" /> + <java-symbol type="string" name="low_internal_storage_view_text" /> + <java-symbol type="string" name="low_internal_storage_view_title" /> + <java-symbol type="string" name="report" /> + <java-symbol type="string" name="select_input_method" /> + <java-symbol type="string" name="smv_application" /> + <java-symbol type="string" name="smv_process" /> + <java-symbol type="string" name="tethered_notification_message" /> + <java-symbol type="string" name="tethered_notification_title" /> + <java-symbol type="string" name="throttle_warning_notification_message" /> + <java-symbol type="string" name="throttle_warning_notification_title" /> + <java-symbol type="string" name="throttled_notification_message" /> + <java-symbol type="string" name="throttled_notification_title" /> + <java-symbol type="string" name="usb_accessory_notification_title" /> + <java-symbol type="string" name="usb_cd_installer_notification_title" /> + <java-symbol type="string" name="usb_mtp_notification_title" /> + <java-symbol type="string" name="usb_notification_message" /> + <java-symbol type="string" name="usb_ptp_notification_title" /> + <java-symbol type="string" name="vpn_text" /> + <java-symbol type="string" name="vpn_text_long" /> + <java-symbol type="string" name="vpn_title" /> + <java-symbol type="string" name="vpn_title_long" /> + <java-symbol type="string" name="wallpaper_binding_label" /> + <java-symbol type="style" name="Theme.Dialog.AppError" /> + <java-symbol type="style" name="Theme.Toast" /> + <java-symbol type="xml" name="storage_list" /> + + <!-- From SystemUI --> + <java-symbol type="anim" name="push_down_in" /> + <java-symbol type="anim" name="push_down_out" /> + <java-symbol type="anim" name="push_up_in" /> + <java-symbol type="anim" name="push_up_out" /> + <java-symbol type="dimen" name="status_bar_icon_size" /> + <java-symbol type="dimen" name="system_bar_icon_size" /> + <java-symbol type="drawable" name="list_selector_pressed_holo_dark" /> + <java-symbol type="drawable" name="scrubber_control_disabled_holo" /> + <java-symbol type="drawable" name="scrubber_control_selector_holo" /> + <java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" /> + <java-symbol type="drawable" name="usb_android" /> + <java-symbol type="drawable" name="usb_android_connected" /> + <java-symbol type="id" name="banner" /> + <java-symbol type="id" name="mount_button" /> + <java-symbol type="id" name="unmount_button" /> + <java-symbol type="layout" name="usb_storage_activity" /> + <java-symbol type="string" name="chooseUsbActivity" /> + <java-symbol type="string" name="dlg_confirm_kill_storage_users_text" /> + <java-symbol type="string" name="dlg_confirm_kill_storage_users_title" /> + <java-symbol type="string" name="dlg_error_title" /> + <java-symbol type="string" name="ext_media_badremoval_notification_message" /> + <java-symbol type="string" name="ext_media_badremoval_notification_title" /> + <java-symbol type="string" name="ext_media_checking_notification_message" /> + <java-symbol type="string" name="ext_media_checking_notification_title" /> + <java-symbol type="string" name="ext_media_nofs_notification_message" /> + <java-symbol type="string" name="ext_media_nofs_notification_title" /> + <java-symbol type="string" name="ext_media_nomedia_notification_message" /> + <java-symbol type="string" name="ext_media_nomedia_notification_title" /> + <java-symbol type="string" name="ext_media_safe_unmount_notification_message" /> + <java-symbol type="string" name="ext_media_safe_unmount_notification_title" /> + <java-symbol type="string" name="ext_media_unmountable_notification_message" /> + <java-symbol type="string" name="ext_media_unmountable_notification_title" /> + <java-symbol type="string" name="usb_storage_error_message" /> + <java-symbol type="string" name="usb_storage_message" /> + <java-symbol type="string" name="usb_storage_notification_message" /> + <java-symbol type="string" name="usb_storage_notification_title" /> + <java-symbol type="string" name="usb_storage_stop_message" /> + <java-symbol type="string" name="usb_storage_stop_notification_message" /> + <java-symbol type="string" name="usb_storage_stop_notification_title" /> + <java-symbol type="string" name="usb_storage_stop_title" /> + <java-symbol type="string" name="usb_storage_title" /> + + <!-- ImfTest --> + <java-symbol type="layout" name="auto_complete_list" /> + + <!-- From SettingsProvider --> + <java-symbol type="raw" name="fallbackring" /> + + <!-- From Settings --> + <java-symbol type="array" name="config_mobile_hotspot_provision_app" /> + <java-symbol type="bool" name="config_intrusiveNotificationLed" /> + <java-symbol type="dimen" name="preference_fragment_padding_bottom" /> + <java-symbol type="dimen" name="preference_fragment_padding_side" /> + <java-symbol type="drawable" name="expander_ic_maximized" /> + <java-symbol type="drawable" name="expander_ic_minimized" /> + <java-symbol type="drawable" name="ic_menu_archive" /> + <java-symbol type="drawable" name="ic_menu_goto" /> + <java-symbol type="drawable" name="title_bar_medium" /> + <java-symbol type="id" name="body" /> + <java-symbol type="string" name="fast_scroll_alphabet" /> + <java-symbol type="string" name="ssl_certificate" /> + + <!-- From Phone --> + <java-symbol type="bool" name="config_built_in_sip_phone" /> + + <!-- From TelephonyProvider --> + <java-symbol type="xml" name="apns" /> + + <!-- From ContactsProvider --> + <java-symbol type="array" name="common_nicknames" /> + <java-symbol type="drawable" name="call_contact" /> + <java-symbol type="drawable" name="create_contact" /> + <java-symbol type="string" name="common_name_prefixes" /> + <java-symbol type="string" name="common_last_name_prefixes" /> + <java-symbol type="string" name="common_name_suffixes" /> + <java-symbol type="string" name="common_name_conjunctions" /> + <java-symbol type="string" name="dial_number_using" /> + <java-symbol type="string" name="create_contact_using" /> + + <!-- From DownloadProvider --> + <java-symbol type="integer" name="config_MaxConcurrentDownloadsAllowed" /> + <java-symbol type="integer" name="config_downloadDataDirSize" /> + <java-symbol type="integer" name="config_downloadDataDirLowSpaceThreshold" /> + + <!-- From Contacts --> + <java-symbol type="drawable" name="quickcontact_badge_overlay_dark" /> + + <!-- From Browser --> + <java-symbol type="drawable" name="ic_menu_moreoverflow_normal_holo_dark" /> + <java-symbol type="id" name="placeholder" /> + <java-symbol type="string" name="ssl_certificate_is_valid" /> + + <!-- From Mms --> + <java-symbol type="drawable" name="ic_menu_play_clip" /> + + <!-- From Stk --> + <java-symbol type="bool" name="config_sf_slowBlur" /> + <java-symbol type="drawable" name="ic_volume" /> + <java-symbol type="drawable" name="stat_notify_sim_toolkit" /> + + <!-- From PinyinIME(!!!) --> + <java-symbol type="string" name="inputMethod" /> + <!-- AndroidManifest.xml attributes. --> <eat-comment /> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 3b6d6f1a66f0..571c4ad9e447 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -112,7 +112,7 @@ please see styles_device_defaults.xml. </style> <!-- Standard animations for a translucent window or activity. This - style is <em>not<em> used by default for the translucent theme + style is <em>not</em> used by default for the translucent theme (since translucent activities are a special case that have no clear UI paradigm), but you can make your own specialized theme with this animation style if you would like to have the standard diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index fe5388bdcd83..7046fc551002 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -29,15 +29,16 @@ please see themes_device_defaults.xml. =============================================================== --> <resources> - <!-- The default system theme. This is the theme used for activities - that have not explicitly set their own theme. - + <!-- The default theme for apps on API level 10 and lower. This is the theme used for + activities that have not explicitly set their own theme. <p>You can count on this being a dark background with light text on top, but should try to make no other assumptions about its appearance. In particular, the text inside of widgets using this theme may be completely different, with the widget container being a light color and the text on top of it a dark color. + <p>If you're developing for API level 11 and higher, you should instead use {@link + #Theme_Holo} or {@link #Theme_DeviceDefault}.</p> --> <style name="Theme"> @@ -370,13 +371,12 @@ please see themes_device_defaults.xml. <item name="pointerStyle">@android:style/Pointer</item> </style> - <!-- Variant of the default (dark) theme with no title bar --> + <!-- Variant of {@link #Theme} with no title bar --> <style name="Theme.NoTitleBar"> <item name="android:windowNoTitle">true</item> </style> - <!-- Variant of the default (dark) theme that has no title bar and - fills the entire screen --> + <!-- Variant of {@link #Theme} that has no title bar and no status bar --> <style name="Theme.NoTitleBar.Fullscreen"> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> @@ -385,7 +385,8 @@ please see themes_device_defaults.xml. <!-- Theme for a light background with dark text on top. Set your activity to this theme if you would like such an appearance. As with the default theme, you should try to assume little more than that the - background will be a light color. --> + background will be a light color. + <p>This is designed for API level 10 and lower.</p>--> <style name="Theme.Light"> <item name="windowBackground">@android:drawable/screen_background_selector_light</item> <item name="colorBackground">@android:color/background_light</item> @@ -457,19 +458,19 @@ please see themes_device_defaults.xml. <item name="detailsElementBackground">@android:drawable/panel_bg_holo_light</item> </style> - <!-- Variant of the light theme with no title bar --> + <!-- Variant of {@link #Theme_Light} with no title bar --> <style name="Theme.Light.NoTitleBar"> <item name="android:windowNoTitle">true</item> </style> - <!-- Variant of the light theme that has no title bar and - fills the entire screen --> + <!-- Variant of {@link #Theme_Light} that has no title bar and + no status bar --> <style name="Theme.Light.NoTitleBar.Fullscreen"> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> </style> - <!-- Special variation on the default theme that ensures the background is + <!-- Variant on {@link #Theme} that ensures the background is completely black. This is useful for things like image viewers and media players. If you want the normal (dark background) theme do <em>not</em> use this, use {@link #Theme}. --> @@ -478,40 +479,40 @@ please see themes_device_defaults.xml. <item name="android:colorBackground">@android:color/black</item> </style> - <!-- Variant of the black theme with no title bar --> + <!-- Variant of {@link #Theme_Black} with no title bar --> <style name="Theme.Black.NoTitleBar"> <item name="android:windowNoTitle">true</item> </style> - <!-- Variant of the black theme that has no title bar and - fills the entire screen --> + <!-- Variant of {@link #Theme_Black} that has no title bar and + no status bar --> <style name="Theme.Black.NoTitleBar.Fullscreen"> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> </style> - <!-- Default theme for windows that want to have the user's selected - wallpaper appear behind them. --> + <!-- Theme for windows that want to have the user's selected + wallpaper appear behind them (for API level 10 and lower). --> <style name="Theme.Wallpaper"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> <item name="android:windowShowWallpaper">true</item> </style> - <!-- Variant of the translucent theme with no title bar --> + <!-- Variant of {@link #Theme_Wallpaper} that has no title bar --> <style name="Theme.Wallpaper.NoTitleBar"> <item name="android:windowNoTitle">true</item> </style> - <!-- Variant of the translucent theme that has no title bar and - fills the entire screen --> + <!-- Variant of {@link #Theme_Wallpaper} that + has no title bar or status bar. --> <style name="Theme.Wallpaper.NoTitleBar.Fullscreen"> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> </style> - <!-- Theme for a wallpaper's setting activity that is designed to be on - top of a dark background. --> + <!-- Theme for a wallpaper's setting activity, which is designed to be a transparent + background with a dark shade, so the previous Activity is visible in the background. --> <style name="Theme.WallpaperSettings"> <item name="android:windowBackground">@android:drawable/screen_background_dark_transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> @@ -519,8 +520,8 @@ please see themes_device_defaults.xml. <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item> </style> - <!-- Theme for a wallpaper's setting activity that is designed to be on - top of a light background. --> + <!-- Theme for a wallpaper's setting activity, which is designed to be a transparent + background with a light shade, so the previous Activity is visible in the background. --> <style name="Theme.Light.WallpaperSettings"> <item name="android:windowBackground">@android:drawable/screen_background_light_transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> @@ -538,8 +539,8 @@ please see themes_device_defaults.xml. <style name="PreviewWallpaperSettings"> </style> - <!-- Default theme for translucent activities, that is windows that allow you - to see through them to the windows behind. This sets up the translucent + <!-- Theme for translucent activities (on API level 10 and lower). That is, windows + that allow you to see through them to the windows behind. This sets up the translucent flag and appropriate animations for your windows. --> <style name="Theme.Translucent"> <item name="android:windowBackground">@android:color/transparent</item> @@ -551,14 +552,14 @@ please see themes_device_defaults.xml. <item name="android:windowAnimationStyle">@android:style/Animation</item> </style> - <!-- Variant of the translucent theme with no title bar --> + <!-- Variant of {@link #Theme_Translucent} with no title bar --> <style name="Theme.Translucent.NoTitleBar"> <item name="android:windowNoTitle">true</item> <item name="android:windowContentOverlay">@null</item> </style> - <!-- Variant of the translucent theme that has no title bar and - fills the entire screen --> + <!-- Variant of {@link #Theme_Translucent} that has no title bar and + no status bar --> <style name="Theme.Translucent.NoTitleBar.Fullscreen"> <item name="android:windowFullscreen">true</item> </style> @@ -574,7 +575,8 @@ please see themes_device_defaults.xml. <item name="android:windowNoDisplay">true</item> </style> - <!-- Default theme for dialog windows and activities, which is used by the + <!-- Default theme for dialog windows and activities (on API level 10 and lower), + which is used by the {@link android.app.Dialog} class. This changes the window to be floating (not fill the entire screen), and puts a frame around its contents. You can set this theme on an activity if you would like to @@ -622,7 +624,7 @@ please see themes_device_defaults.xml. <item name="listPreferredItemPaddingRight">10dip</item> </style> - <!-- Variation of Theme.Dialog that does not include a frame (or background). + <!-- Variant of {@link Theme_Dialog} that does not include a frame (or background). The view hierarchy of the dialog is responsible for drawing all of its pixels. --> <style name="Theme.Dialog.NoFrame"> @@ -636,7 +638,7 @@ please see themes_device_defaults.xml. <item name="android:windowCloseOnTouchOutside">false</item> </style> - <!-- Default theme for alert dialog windows, which is used by the + <!-- Default theme for alert dialog windows (on API level 10 and lower), which is used by the {@link android.app.AlertDialog} class. This is basically a dialog but sets the background to empty so it can do two-tone backgrounds. --> <style name="Theme.Dialog.Alert"> @@ -648,8 +650,8 @@ please see themes_device_defaults.xml. <item name="textAppearanceListItemSmall">@android:style/TextAppearance.Large.Inverse</item> </style> - <!-- Default dark theme for panel windows. This removes all extraneous - window decorations, so you basically have an empty rectangle in which + <!-- Default dark theme for panel windows (on API level 10 and lower). This removes all + extraneous window decorations, so you basically have an empty rectangle in which to place your content. It makes the window floating, with a transparent background, and turns off dimming behind the window. --> <style name="Theme.Panel"> @@ -664,8 +666,8 @@ please see themes_device_defaults.xml. <item name="android:windowNoTitle">true</item> </style> - <!-- Default light theme for panel windows. This removes all extraneous - window decorations, so you basically have an empty rectangle in which + <!-- Default light theme for panel windows (on API level 10 and lower). This removes all + extraneous window decorations, so you basically have an empty rectangle in which to place your content. It makes the window floating, with a transparent background, and turns off dimming behind the window. --> <style name="Theme.Light.Panel"> @@ -712,7 +714,7 @@ please see themes_device_defaults.xml. <item name="android:windowNoTitle">true</item> </style> - <!-- Default theme for input methods, which is used by the + <!-- Default theme for input methods (on API level 10 and lower), which is used by the {@link android.inputmethodservice.InputMethodService} class. this inherits from Theme.Panel, but sets up IME appropriate animations and a few custom attributes. --> @@ -723,7 +725,7 @@ please see themes_device_defaults.xml. <item name="android:imeExtractExitAnimation">@android:anim/input_method_extract_exit</item> </style> - <!-- Default theme for modern holo style input methods, which is used by the + <!-- Default theme for holo style input methods, which is used by the {@link android.inputmethodservice.InputMethodService} class. this inherits from Theme.Panel, but sets up IME appropriate animations and a few custom attributes. --> @@ -842,14 +844,23 @@ please see themes_device_defaults.xml. <item name="android:windowActionModeOverlay">true</item> </style> - <!-- New Honeycomb holographic theme. Dark version. The widgets in the - holographic theme are translucent on their brackground, so applications - must ensure that any background they use with this theme is itself - dark; otherwise, it will be difficult to see the widgets. The new - UI style also includes a full action bar by default. - - Styles used by the Holo theme are named using the convention Type.Holo.Etc. - (For example, Widget.Holo.Button, TextAppearance.Holo.Widget.PopupMenu.Large.) + <!-- Honeycomb holographic theme (dark version). + <p>This is the default system theme for apps that target API level 11 - 13. Starting + with API level 14, the default system theme is supplied by {@link #Theme_DeviceDefault}, + which might apply a different style on different devices. If you want to ensure that your + app consistenly uses the Holo theme at all times, you must explicitly declare it in your + manifest. For example, {@code <application android:theme="@android:style/Theme.Holo">}. + For more information, read <a + href="http://android-developers.blogspot.com/2012/01/holo-everywhere.html">Holo + Everywhere</a>.</p> + <p>The widgets in the holographic theme are translucent on their brackground, so + applications must ensure that any background they use with this theme is itself + dark; otherwise, it will be difficult to see the widgets. This UI style also includes a + full action bar by default.</p> + + <p>Styles used by the Holo theme are named using the convention Type.Holo.Etc + (for example, {@code Widget.Holo.Button} and {@code + TextAppearance.Holo.Widget.PopupMenu.Large}). Specific resources used by Holo are named using the convention @type/foo_bar_baz_holo with trailing _dark or _light specifiers if they are not shared between both light and dark versions of the theme. --> @@ -951,15 +962,12 @@ please see themes_device_defaults.xml. <item name="listDividerAlertDialog">@android:drawable/list_divider_holo_dark</item> <item name="expandableListPreferredItemPaddingLeft">40dip</item> - <item name="expandableListPreferredChildPaddingLeft"> - ?android:attr/expandableListPreferredItemPaddingLeft</item> + <item name="expandableListPreferredChildPaddingLeft">?android:attr/expandableListPreferredItemPaddingLeft</item> <item name="expandableListPreferredItemIndicatorLeft">3dip</item> <item name="expandableListPreferredItemIndicatorRight">0dip</item> - <item name="expandableListPreferredChildIndicatorLeft"> - ?android:attr/expandableListPreferredItemIndicatorLeft</item> - <item name="expandableListPreferredChildIndicatorRight"> - ?android:attr/expandableListPreferredItemIndicatorRight</item> + <item name="expandableListPreferredChildIndicatorLeft">?android:attr/expandableListPreferredItemIndicatorLeft</item> + <item name="expandableListPreferredChildIndicatorRight">?android:attr/expandableListPreferredItemIndicatorRight</item> <!-- Gallery attributes --> <item name="galleryItemBackground">@android:drawable/gallery_item_background</item> @@ -1156,10 +1164,10 @@ please see themes_device_defaults.xml. </style> - <!-- New Honeycomb holographic theme. Light version. The widgets in the + <!-- Honeycomb holographic theme (light version). The widgets in the holographic theme are translucent on their brackground, so applications must ensure that any background they use with this theme is itself - light; otherwise, it will be difficult to see the widgets. The new + light; otherwise, it will be difficult to see the widgets. This UI style also includes a full action bar by default. --> <style name="Theme.Holo.Light" parent="Theme.Light"> <item name="colorForeground">@android:color/bright_foreground_holo_light</item> @@ -1257,15 +1265,12 @@ please see themes_device_defaults.xml. <item name="activatedBackgroundIndicator">@android:drawable/activated_background_holo_light</item> <item name="expandableListPreferredItemPaddingLeft">40dip</item> - <item name="expandableListPreferredChildPaddingLeft"> - ?android:attr/expandableListPreferredItemPaddingLeft</item> + <item name="expandableListPreferredChildPaddingLeft">?android:attr/expandableListPreferredItemPaddingLeft</item> <item name="expandableListPreferredItemIndicatorLeft">3dip</item> <item name="expandableListPreferredItemIndicatorRight">0dip</item> - <item name="expandableListPreferredChildIndicatorLeft"> - ?android:attr/expandableListPreferredItemIndicatorLeft</item> - <item name="expandableListPreferredChildIndicatorRight"> - ?android:attr/expandableListPreferredItemIndicatorRight</item> + <item name="expandableListPreferredChildIndicatorLeft">?android:attr/expandableListPreferredItemIndicatorLeft</item> + <item name="expandableListPreferredChildIndicatorRight">?android:attr/expandableListPreferredItemIndicatorRight</item> <item name="listDividerAlertDialog">@android:drawable/list_divider_holo_light</item> @@ -1522,6 +1527,7 @@ please see themes_device_defaults.xml. </style> <!-- Dialog themes for Holo --> + <eat-comment /> <!-- Holo theme for dialog windows and activities, which is used by the {@link android.app.Dialog} class. This changes the window to be @@ -1554,27 +1560,27 @@ please see themes_device_defaults.xml. <item name="listPreferredItemPaddingRight">16dip</item> </style> - <!-- Variation of Theme.Holo.Dialog that has a nice minumum width for + <!-- Variant of Theme.Holo.Dialog that has a nice minimum width for a regular dialog. --> <style name="Theme.Holo.Dialog.MinWidth"> <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item> <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item> </style> - <!-- Variation of Theme.Holo.Dialog that does not include a title bar. --> + <!-- Variant of Theme.Holo.Dialog that does not include a title bar. --> <style name="Theme.Holo.Dialog.NoActionBar"> <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> </style> - <!-- Variation of Theme.Holo.Dialog.NoActionVar that has a nice minumum width for + <!-- Variant of Theme.Holo.Dialog.NoActionBar that has a nice minimum width for a regular dialog. --> <style name="Theme.Holo.Dialog.NoActionBar.MinWidth"> <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item> <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item> </style> - <!-- Variation of Theme.Holo.Dialog that does not include a frame (or background). + <!-- Variant of Theme.Holo.Dialog that does not include a frame (or background). The view hierarchy of the dialog is responsible for drawing all of its pixels. --> <style name="Theme.Holo.Dialog.NoFrame"> @@ -1646,20 +1652,20 @@ please see themes_device_defaults.xml. <item name="listPreferredItemPaddingRight">16dip</item> </style> - <!-- Variation of Theme.Holo.Light.Dialog that has a nice minumum width for + <!-- Variant of Theme.Holo.Light.Dialog that has a nice minimum width for a regular dialog. --> <style name="Theme.Holo.Light.Dialog.MinWidth"> <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item> <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item> </style> - <!-- Variation of Theme.Holo.Light.Dialog that does not include a title bar. --> + <!-- Variant of Theme.Holo.Light.Dialog that does not include a title bar. --> <style name="Theme.Holo.Light.Dialog.NoActionBar"> <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> </style> - <!-- Variation of Theme.Holo.Light.Dialog.NoActionBar that has a nice minumum width for + <!-- Variant of Theme.Holo.Light.Dialog.NoActionBar that has a nice minimum width for a regular dialog. --> <style name="Theme.Holo.Light.Dialog.NoActionBar.MinWidth"> <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item> @@ -1700,7 +1706,8 @@ please see themes_device_defaults.xml. <item name="android:windowShowWallpaper">true</item> </style> - <!-- Variant of the holographic (dark) theme with no title bar --> + <!--Default holographic (dark) for windows that want to have the user's selected + wallpaper appear behind them and without an action bar. --> <style name="Theme.Holo.Wallpaper.NoTitleBar"> <item name="android:windowNoTitle">true</item> </style> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 813598641184..abe4aad71e4e 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -31,6 +31,24 @@ easier. =============================================================== --> <resources> + + <!-- The default theme for apps that target API level 14 and higher. + <p>The DeviceDefault themes are aliases for a specific device’s native look and feel. The + DeviceDefault theme family and widget style family offer ways for you to target your app + to a device’s native theme with all device customizations intact.</p> + <p>For example, when you set your app's {@code targetSdkVersion} to 14 or higher, this + theme is applied to your application by default. As such, your app might appear with the + {@link #Theme_Holo Holo} styles on one device, but with a different set of styles on + another device. This is great if you want your app to fit with the device's native look and + feel. If, however, you prefer to keep your UI style the same across all devices, you should + apply a specific theme such as {@link #Theme_Holo Holo} or one of your own design. For more + information, read <a + href="http://android-developers.blogspot.com/2012/01/holo-everywhere.html">Holo + Everywhere</a>.</p> + <p>Styles used by the DeviceDefault theme are named using the convention + Type.DeviceDefault.Etc (for example, {@code Widget.DeviceDefault.Button} and + {@code TextAppearance.DeviceDefault.Widget.PopupMenu.Large}).</p> + --> <style name="Theme.DeviceDefault" parent="Theme.Holo" > <!-- Text styles --> <item name="textAppearance">@android:style/TextAppearance.DeviceDefault</item> @@ -176,12 +194,16 @@ easier. <!-- DatePicker style --> <item name="datePickerStyle">@style/Widget.DeviceDefault.DatePicker</item> </style> + + <!-- Variant of {@link #Theme_DeviceDefault} with no action bar --> <style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Holo.NoActionBar" > </style> + <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar --> <style name="Theme.DeviceDefault.NoActionBar.Fullscreen" parent="Theme.Holo.NoActionBar.Fullscreen" > </style> + <!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style --> <style name="Theme.DeviceDefault.Light" parent="Theme.Holo.Light" > <!-- Text styles --> <item name="textAppearance">@android:style/TextAppearance.DeviceDefault.Light</item> @@ -322,12 +344,17 @@ easier. <!-- DatePicker style --> <item name="datePickerStyle">@style/Widget.DeviceDefault.Light.DatePicker</item> </style> + <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar --> <style name="Theme.DeviceDefault.Light.NoActionBar" parent="Theme.Holo.Light.NoActionBar" > </style> + <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar --> <style name="Theme.DeviceDefault.Light.NoActionBar.Fullscreen" parent="Theme.Holo.Light.NoActionBar.Fullscreen" > </style> + <!-- DeviceDefault theme for dialog windows and activities. This changes the window to be + floating (not fill the entire screen), and puts a frame around its contents. You can set this + theme on an activity if you would like to make an activity that looks like a Dialog. --> <style name="Theme.DeviceDefault.Dialog" parent="Theme.Holo.Dialog" > <item name="android:windowTitleStyle">@android:style/DialogWindowTitle.DeviceDefault</item> <item name="android:windowAnimationStyle">@android:style/Animation.DeviceDefault.Dialog</item> @@ -338,15 +365,23 @@ easier. <item name="textAppearance">@android:style/TextAppearance.DeviceDefault</item> <item name="textAppearanceInverse">@android:style/TextAppearance.DeviceDefault.Inverse</item> </style> + <!-- Variant of {@link #Theme_DeviceDefault_Dialog} that has a nice minimum width for a + regular dialog. --> <style name="Theme.DeviceDefault.Dialog.MinWidth" parent="Theme.Holo.Dialog.MinWidth" > </style> + <!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar --> <style name="Theme.DeviceDefault.Dialog.NoActionBar" parent="Theme.Holo.Dialog.NoActionBar" > </style> + <!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width + for a regular dialog. --> <style name="Theme.DeviceDefault.Dialog.NoActionBar.MinWidth" parent="Theme.Holo.Dialog.NoActionBar.MinWidth" > </style> + <!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be + floating (not fill the entire screen), and puts a frame around its contents. You can set this + theme on an activity if you would like to make an activity that looks like a Dialog.--> <style name="Theme.DeviceDefault.Light.Dialog" parent="Theme.Holo.Light.Dialog" > <item name="android:windowTitleStyle">@android:style/DialogWindowTitle.DeviceDefault.Light</item> <item name="android:windowAnimationStyle">@android:style/Animation.DeviceDefault.Dialog</item> @@ -357,42 +392,71 @@ easier. <item name="textAppearance">@android:style/TextAppearance.DeviceDefault.Light</item> <item name="textAppearanceInverse">@android:style/TextAppearance.DeviceDefault.Light.Inverse</item> </style> + <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} that has a nice minimum width for a + regular dialog. --> <style name="Theme.DeviceDefault.Light.Dialog.MinWidth" parent="Theme.Holo.Light.Dialog.MinWidth" > </style> + <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar --> <style name="Theme.DeviceDefault.Light.Dialog.NoActionBar" parent="Theme.Holo.Light.Dialog.NoActionBar" > </style> + <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum + width for a regular dialog. --> <style name="Theme.DeviceDefault.Light.Dialog.NoActionBar.MinWidth" parent="Theme.Holo.Light.Dialog.NoActionBar.MinWidth" > </style> + <!-- DeviceDefault theme for a window that will be displayed either full-screen on smaller + screens (small, normal) or as a dialog on larger screens (large, xlarge). --> <style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Holo.DialogWhenLarge" > </style> + <!-- DeviceDefault theme for a window without an action bar that will be displayed either + full-screen on smaller screens (small, normal) or as a dialog on larger screens (large, + xlarge). --> <style name="Theme.DeviceDefault.DialogWhenLarge.NoActionBar" parent="Theme.Holo.DialogWhenLarge.NoActionBar" > </style> + <!-- DeviceDefault light theme for a window that will be displayed either full-screen on smaller + screens (small, normal) or as a dialog on larger screens (large, xlarge). --> <style name="Theme.DeviceDefault.Light.DialogWhenLarge" parent="Theme.Holo.Light.DialogWhenLarge" > </style> + <!-- DeviceDefault light theme for a window without an action bar that will be displayed either + full-screen on smaller screens (small, normal) or as a dialog on larger screens (large, + xlarge). --> <style name="Theme.DeviceDefault.Light.DialogWhenLarge.NoActionBar" parent="Theme.Holo.Light.DialogWhenLarge.NoActionBar" > </style> + <!-- DeviceDefault theme for panel windows. This removes all extraneous window + decorations, so you basically have an empty rectangle in which to place your content. It makes + the window floating, with a transparent background, and turns off dimming behind the window. --> <style name="Theme.DeviceDefault.Panel" parent="Theme.Holo.Panel" > </style> + <!-- DeviceDefault light theme for panel windows. This removes all extraneous window + decorations, so you basically have an empty rectangle in which to place your content. It makes + the window floating, with a transparent background, and turns off dimming behind the window. --> <style name="Theme.DeviceDefault.Light.Panel" parent="Theme.Holo.Light.Panel" > </style> + <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear + behind them. --> <style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Holo.Wallpaper" > </style> + <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear + behind them and without an action bar. --> <style name="Theme.DeviceDefault.Wallpaper.NoTitleBar" parent="Theme.Holo.Wallpaper.NoTitleBar" > </style> + <!-- DeviceDefault style for input methods, which is used by the + {@link android.inputmethodservice.InputMethodService} class.--> <style name="Theme.DeviceDefault.InputMethod" parent="Theme.Holo.InputMethod" > </style> + <!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an + inverse color profile. --> <style name="Theme.DeviceDefault.Light.DarkActionBar" parent="Theme.Holo.Light.DarkActionBar" > <item name="android:actionBarStyle">@android:style/Widget.DeviceDefault.Light.ActionBar.Solid.Inverse</item> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java index 3ffa085ae0b7..7233e7fdfa67 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java @@ -46,7 +46,7 @@ public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunn @Override public TestSuite getAllTests() { TestSuite suite = new InstrumentationTestSuite(this); - if (!UtilHelper.isWifiOnly()) { + if (!UtilHelper.isWifiOnly(getContext())) { suite.addTestSuite(WifiApStress.class); suite.addTestSuite(WifiStressTest.class); } else { @@ -64,7 +64,7 @@ public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunn @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - if (!UtilHelper.isWifiOnly()) { + if (!UtilHelper.isWifiOnly(getContext())) { String valueStr = (String) icicle.get("softap_iterations"); if (valueStr != null) { int iteration = Integer.parseInt(valueStr); diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java index 20aae47077a3..9819c54aad6d 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java @@ -40,7 +40,7 @@ public class ConnectivityManagerTestRunner extends InstrumentationTestRunner { @Override public TestSuite getAllTests() { TestSuite suite = new InstrumentationTestSuite(this); - if (!UtilHelper.isWifiOnly()) { + if (!UtilHelper.isWifiOnly(getContext())) { suite.addTestSuite(ConnectivityManagerMobileTest.class); } else { // create a new test suite diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java index 1b966bfb42e3..b9fe6edf7e2a 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java @@ -16,12 +16,31 @@ package com.android.connectivitymanagertest; -import android.os.SystemProperties; +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.Log; public class UtilHelper { - public static boolean isWifiOnly() { - return "wifi-only".equals(SystemProperties.get("ro.carrier")); - } + private static Boolean mIsWifiOnly = null; + private static final Object sLock = new Object(); + /** + * Return true if device is a wifi only device. + */ + public static boolean isWifiOnly(Context context) { + synchronized (sLock) { + // cache the result from pkgMgr statically. It will never change, since its a + // device configuration setting + if (mIsWifiOnly == null) { + PackageManager pkgMgr = context.getPackageManager(); + mIsWifiOnly = Boolean.valueOf(!pkgMgr + .hasSystemFeature(PackageManager.FEATURE_TELEPHONY) + && pkgMgr.hasSystemFeature(PackageManager.FEATURE_WIFI)); + String deviceType = mIsWifiOnly ? "wifi-only" : "telephony"; + Log.d("ConnectivityManagerTest", String.format("detected a %s device", deviceType)); + } + } + return mIsWifiOnly; + } } diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java index b1f4bf122a4f..52326d577496 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java @@ -16,37 +16,31 @@ package com.android.connectivitymanagertest.functional; -import com.android.connectivitymanagertest.ConnectivityManagerTestActivity; -import com.android.connectivitymanagertest.UtilHelper; - -import android.content.Intent; import android.content.Context; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.app.Instrumentation; -import android.os.Handler; -import android.os.Message; -import android.provider.Settings; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; -import android.net.NetworkInfo.DetailedState; import android.net.wifi.WifiManager; - -import android.test.suitebuilder.annotation.LargeTest; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.provider.Settings; import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + +import com.android.connectivitymanagertest.ConnectivityManagerTestActivity; import com.android.connectivitymanagertest.ConnectivityManagerTestRunner; import com.android.connectivitymanagertest.NetworkState; -import android.util.Log; +import com.android.connectivitymanagertest.UtilHelper; -public class ConnectivityManagerMobileTest - extends ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> { +public class ConnectivityManagerMobileTest extends + ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> { private static final String LOG_TAG = "ConnectivityManagerMobileTest"; - private static final String PKG_NAME = "com.android.connectivitymanagertest"; private String TEST_ACCESS_POINT; private ConnectivityManagerTestActivity cmActivity; private WakeLock wl; + private boolean mIsWifiOnlyDevice; public ConnectivityManagerMobileTest() { super(ConnectivityManagerTestActivity.class); @@ -69,7 +63,8 @@ public class ConnectivityManagerMobileTest log("airplane is not disabled, disable it."); cmActivity.setAirplaneMode(getInstrumentation().getContext(), false); } - if (!UtilHelper.isWifiOnly()) { + mIsWifiOnlyDevice = UtilHelper.isWifiOnly(mRunner.getTargetContext()); + if (!mIsWifiOnlyDevice) { if (!cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)) { // Note: When the test fails in setUp(), tearDown is not called. In that case, @@ -166,7 +161,7 @@ public class ConnectivityManagerMobileTest public void testConnectToWifi() { assertNotNull("SSID is null", TEST_ACCESS_POINT); NetworkInfo networkInfo; - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { //Prepare for connectivity verification networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE, @@ -185,7 +180,7 @@ public class ConnectivityManagerMobileTest log("wifi state is enabled"); assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); } @@ -197,7 +192,7 @@ public class ConnectivityManagerMobileTest cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI)); assertTrue(false); } - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) { log("Mobile state transition validation failed."); log("reason: " + @@ -232,13 +227,13 @@ public class ConnectivityManagerMobileTest ConnectivityManagerTestActivity.LONG_TIMEOUT)); assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); } NetworkInfo networkInfo; - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { //Prepare for connectivity state verification networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE, @@ -258,7 +253,7 @@ public class ConnectivityManagerMobileTest // Wait for Wifi to be connected and mobile to be disconnected assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); } @@ -288,7 +283,7 @@ public class ConnectivityManagerMobileTest sleep(ConnectivityManagerTestActivity.SHORT_TIMEOUT); NetworkInfo networkInfo; - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE, networkInfo.getState(), @@ -304,7 +299,7 @@ public class ConnectivityManagerMobileTest assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); } @@ -316,7 +311,7 @@ public class ConnectivityManagerMobileTest cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI)); assertTrue(false); } - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) { log("Mobile state transition validation failed."); log("reason: " + @@ -393,7 +388,7 @@ public class ConnectivityManagerMobileTest cmActivity.setAirplaneMode(getInstrumentation().getContext(), true); NetworkInfo networkInfo; - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); @@ -419,7 +414,7 @@ public class ConnectivityManagerMobileTest cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI)); assertTrue("State validation failed", false); } - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) { log("state validation for Mobile failed"); log("reason: " + @@ -471,7 +466,7 @@ public class ConnectivityManagerMobileTest assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT)); } diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java index 2069789c37c6..feb63cd8dfab 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java @@ -16,10 +16,6 @@ package com.android.connectivitymanagertest.stress; -import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner; -import com.android.connectivitymanagertest.ConnectivityManagerTestActivity; -import com.android.connectivitymanagertest.UtilHelper; - import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo.State; @@ -31,15 +27,15 @@ import android.net.wifi.WifiConfiguration.ProxySettings; import android.net.wifi.WifiManager; import android.os.Environment; import android.os.PowerManager; -import android.os.IPowerManager; -import android.os.SystemClock; -import android.os.ServiceManager; import android.provider.Settings; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; - import android.util.Log; +import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner; +import com.android.connectivitymanagertest.ConnectivityManagerTestActivity; +import com.android.connectivitymanagertest.UtilHelper; + import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -79,6 +75,7 @@ public class WifiStressTest private String mPassword; private ConnectivityManagerStressTestRunner mRunner; private BufferedWriter mOutputWriter = null; + private boolean mIsWifiOnlyDevice; public WifiStressTest() { super(ConnectivityManagerTestActivity.class); @@ -100,6 +97,7 @@ public class WifiStressTest mOutputWriter = new BufferedWriter(new FileWriter(new File( Environment.getExternalStorageDirectory(), OUTPUT_FILE), true)); mAct.turnScreenOn(); + mIsWifiOnlyDevice = UtilHelper.isWifiOnly(mRunner.getTargetContext()); if (!mAct.mWifiManager.isWifiEnabled()) { log("Enable wi-fi before stress tests."); if (!mAct.enableWifi()) { @@ -271,7 +269,7 @@ public class WifiStressTest assertTrue("Wait for Wi-Fi to idle timeout", mAct.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED, 6 * ConnectivityManagerTestActivity.SHORT_TIMEOUT)); - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { // use long timeout as the pppd startup may take several retries. assertTrue("Wait for cellular connection timeout", mAct.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED, @@ -282,7 +280,7 @@ public class WifiStressTest assertEquals("Wi-Fi is reconnected", State.DISCONNECTED, mAct.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState()); - if (!UtilHelper.isWifiOnly()) { + if (!mIsWifiOnlyDevice) { assertEquals("Cellular connection is down", State.CONNECTED, mAct.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState()); assertTrue("Mobile is connected, but no data connection.", mAct.pingTest(null)); diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java index a5e5ab0e77b9..dfcbba950b99 100644 --- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java @@ -146,10 +146,10 @@ public class ConnectionUtil { Log.v("ConnectivityReceiver", "onReceive() called with " + intent); return; } - if (intent.hasExtra(ConnectivityManager.EXTRA_NETWORK_INFO)) { - mNetworkInfo = (NetworkInfo) - intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); - } + + final ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + mNetworkInfo = connManager.getActiveNetworkInfo(); if (intent.hasExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO)) { mOtherNetworkInfo = (NetworkInfo) diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java index b4a05819020b..a9f144bdca49 100644 --- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java +++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java @@ -14,12 +14,12 @@ package android.accessibilityservice; -import com.android.frameworks.coretests.R; - import android.app.Activity; import android.os.Bundle; import android.view.View; +import com.android.frameworks.coretests.R; + /** * Activity for testing the accessibility APIs for "interrogation" of * the screen content. These APIs allow exploring the screen and diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java index 259a09448e2f..2fb42379fe5d 100644 --- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java @@ -14,26 +14,21 @@ package android.accessibilityservice; -import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS; -import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION; -import android.content.Context; import android.graphics.Rect; -import android.os.ServiceManager; import android.os.SystemClock; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; -import android.view.View; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityInteractionClient; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.IAccessibilityManager; import com.android.frameworks.coretests.R; +import com.android.internal.util.Predicate; import java.util.ArrayList; import java.util.LinkedList; @@ -48,21 +43,15 @@ import java.util.Queue; */ public class InterrogationActivityTest extends ActivityInstrumentationTestCase2<InterrogationActivity> { - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static String LOG_TAG = "InterrogationActivityTest"; - // Timeout before give up wait for the system to process an accessibility setting change. - private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000; - // Timeout for the accessibility state of an Activity to be fully initialized. - private static final int TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS = 100; + private static final int TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS = 5000; // Handle to a connection to the AccessibilityManagerService - private static int sConnectionId = View.NO_ID; - - // The last received accessibility event - private volatile AccessibilityEvent mLastAccessibilityEvent; + private UiTestAutomationBridge mUiTestAutomationBridge; public InterrogationActivityTest() { super(InterrogationActivity.class); @@ -70,16 +59,39 @@ public class InterrogationActivityTest @Override public void setUp() throws Exception { - ensureConnection(); - bringUpActivityWithInitalizedAccessbility(); + super.setUp(); + mUiTestAutomationBridge = new UiTestAutomationBridge(); + mUiTestAutomationBridge.connect(); + mUiTestAutomationBridge.executeCommandAndWaitForAccessibilityEvent(new Runnable() { + // wait for the first accessibility event + @Override + public void run() { + // bring up the activity + getActivity(); + } + }, + new Predicate<AccessibilityEvent>() { + @Override + public boolean apply(AccessibilityEvent event) { + return (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + && event.getPackageName().equals(getActivity().getPackageName())); + } + }, + TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS); + } + + @Override + public void tearDown() throws Exception { + mUiTestAutomationBridge.disconnect(); + super.tearDown(); } @LargeTest public void testFindAccessibilityNodeInfoByViewId() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + AccessibilityNodeInfo button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertNotNull(button); assertEquals(0, button.getChildCount()); @@ -125,8 +137,8 @@ public class InterrogationActivityTest final long startTimeMillis = SystemClock.uptimeMillis(); try { // find a view by text - List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId, "butto"); + List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge + .findAccessibilityNodeInfosByTextInActiveWindow("butto"); assertEquals(9, buttons.size()); } finally { if (DEBUG) { @@ -141,12 +153,9 @@ public class InterrogationActivityTest public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - bringUpActivityWithInitalizedAccessbility(); - // find a view by text - List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfosByTextInActiveWindow(sConnectionId, - "contentDescription"); + List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge + .findAccessibilityNodeInfosByTextInActiveWindow("contentDescription"); assertEquals(1, buttons.size()); } finally { if (DEBUG) { @@ -177,8 +186,8 @@ public class InterrogationActivityTest classNameAndTextList.add("android.widget.ButtonButton8"); classNameAndTextList.add("android.widget.ButtonButton9"); - AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.root); + AccessibilityNodeInfo root = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root); assertNotNull("We must find the existing root.", root); Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); @@ -216,16 +225,16 @@ public class InterrogationActivityTest final long startTimeMillis = SystemClock.uptimeMillis(); try { // find a view and make sure it is not focused - AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + AccessibilityNodeInfo button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); // focus the view assertTrue(button.performAction(ACTION_FOCUS)); // find the view again and make sure it is focused - button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isFocused()); } finally { if (DEBUG) { @@ -240,24 +249,24 @@ public class InterrogationActivityTest final long startTimeMillis = SystemClock.uptimeMillis(); try { // find a view and make sure it is not focused - AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + AccessibilityNodeInfo button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); // focus the view assertTrue(button.performAction(ACTION_FOCUS)); // find the view again and make sure it is focused - button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isFocused()); // unfocus the view assertTrue(button.performAction(ACTION_CLEAR_FOCUS)); // find the view again and make sure it is not focused - button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); } finally { if (DEBUG) { @@ -273,16 +282,16 @@ public class InterrogationActivityTest final long startTimeMillis = SystemClock.uptimeMillis(); try { // find a view and make sure it is not selected - AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + AccessibilityNodeInfo button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); // select the view assertTrue(button.performAction(ACTION_SELECT)); // find the view again and make sure it is selected - button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isSelected()); } finally { if (DEBUG) { @@ -297,24 +306,24 @@ public class InterrogationActivityTest final long startTimeMillis = SystemClock.uptimeMillis(); try { // find a view and make sure it is not selected - AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + AccessibilityNodeInfo button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); // select the view assertTrue(button.performAction(ACTION_SELECT)); // find the view again and make sure it is selected - button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isSelected()); // unselect the view assertTrue(button.performAction(ACTION_CLEAR_SELECTION)); // find the view again and make sure it is not selected - button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); } finally { if (DEBUG) { @@ -330,23 +339,33 @@ public class InterrogationActivityTest final long startTimeMillis = SystemClock.uptimeMillis(); try { // find a view and make sure it is not focused - AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); - assertFalse(button.isSelected()); - - // focus the view - assertTrue(button.performAction(ACTION_FOCUS)); + final AccessibilityNodeInfo button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); + assertFalse(button.isFocused()); - synchronized (this) { - try { - wait(TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS); - } catch (InterruptedException ie) { - /* ignore */ + AccessibilityEvent event = mUiTestAutomationBridge + .executeCommandAndWaitForAccessibilityEvent(new Runnable() { + @Override + public void run() { + // focus the view + assertTrue(button.performAction(ACTION_FOCUS)); } - } + }, + new Predicate<AccessibilityEvent>() { + @Override + public boolean apply(AccessibilityEvent event) { + return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED + && event.getPackageName().equals(getActivity().getPackageName()) + && event.getText().get(0).equals(button.getText())); + } + }, + TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS); + + // check the last event + assertNotNull(event); // check that last event source - AccessibilityNodeInfo source = mLastAccessibilityEvent.getSource(); + AccessibilityNodeInfo source = event.getSource(); assertNotNull(source); // bounds @@ -389,8 +408,9 @@ public class InterrogationActivityTest final long startTimeMillis = SystemClock.uptimeMillis(); try { // find a view and make sure it is not focused - AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); + AccessibilityNodeInfo button = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); + assertNotNull(button); AccessibilityNodeInfo parent = button.getParent(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { @@ -411,69 +431,33 @@ public class InterrogationActivityTest } } - private void bringUpActivityWithInitalizedAccessbility() { - mLastAccessibilityEvent = null; - // bring up the activity - getActivity(); - + @LargeTest + public void testGetRootAccessibilityNodeInfoInActiveWindow() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); - while (true) { - if (mLastAccessibilityEvent != null) { - final int eventType = mLastAccessibilityEvent.getEventType(); - if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - return; - } - } - final long remainingTimeMillis = TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS - - (SystemClock.uptimeMillis() - startTimeMillis); - if (remainingTimeMillis <= 0) { - return; - } - synchronized (this) { - try { - wait(remainingTimeMillis); - } catch (InterruptedException e) { - /* ignore */ + try { + // get the root via the designated API + AccessibilityNodeInfo fetched = mUiTestAutomationBridge + .getRootAccessibilityNodeInfoInActiveWindow(); + assertNotNull(fetched); + + // get the root via traversal + AccessibilityNodeInfo expected = mUiTestAutomationBridge + .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root); + while (true) { + AccessibilityNodeInfo parent = expected.getParent(); + if (parent == null) { + break; } + expected = parent; } - } - } + assertNotNull(expected); - private void ensureConnection() throws Exception { - if (sConnectionId == View.NO_ID) { - IEventListener listener = new IEventListener.Stub() { - public void setConnection(IAccessibilityServiceConnection connection, - int connectionId) { - sConnectionId = connectionId; - if (connection != null) { - AccessibilityInteractionClient.getInstance().addConnection(connectionId, - connection); - } else { - AccessibilityInteractionClient.getInstance().removeConnection(connectionId); - } - synchronized (this) { - notifyAll(); - } - } - - public void onInterrupt() {} - - public void onAccessibilityEvent(AccessibilityEvent event) { - mLastAccessibilityEvent = AccessibilityEvent.obtain(event); - synchronized (this) { - notifyAll(); - } - } - }; - - AccessibilityManager accessibilityManager = - AccessibilityManager.getInstance(getInstrumentation().getContext()); - - synchronized (this) { - IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( - ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); - manager.registerEventListener(listener); - wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING); + assertEquals("The node with id \"root\" should be the root.", expected, fetched); + } finally { + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testGetRootAccessibilityNodeInfoInActiveWindow: " + + elapsedTimeMillis + "ms"); } } } diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java index 4b1f9fddc6e4..d527c0d61f3c 100644 --- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java +++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java @@ -303,7 +303,8 @@ public class BroadcastTest extends ActivityTestsBase { public void testSetSticky() throws Exception { Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null); intent.putExtra("test", LaunchpadActivity.DATA_1); - ActivityManagerNative.getDefault().unbroadcastIntent(null, intent); + ActivityManagerNative.getDefault().unbroadcastIntent(null, intent, + Binder.getOrigCallingUser()); ActivityManagerNative.broadcastStickyIntent(intent, null); addIntermediate("finished-broadcast"); @@ -320,7 +321,8 @@ public class BroadcastTest extends ActivityTestsBase { ActivityManagerNative.broadcastStickyIntent(intent, null); ActivityManagerNative.getDefault().unbroadcastIntent( - null, new Intent(LaunchpadActivity.BROADCAST_STICKY1, null)); + null, new Intent(LaunchpadActivity.BROADCAST_STICKY1, null), + Binder.getOrigCallingUser()); addIntermediate("finished-unbroadcast"); IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1); diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java index 1df763a48540..b1811225a28f 100644 --- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java +++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java @@ -24,6 +24,8 @@ import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLong; import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLong; import static android.net.NetworkStatsHistory.Entry.UNKNOWN; +import static android.net.TrafficStats.GB_IN_BYTES; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; @@ -50,10 +52,6 @@ public class NetworkStatsHistoryTest extends AndroidTestCase { private static final long TEST_START = 1194220800000L; - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; - private NetworkStatsHistory stats; @Override diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocus.java b/core/tests/coretests/src/android/widget/focus/RequestFocus.java index af9ee170abe4..21d762a55061 100644 --- a/core/tests/coretests/src/android/widget/focus/RequestFocus.java +++ b/core/tests/coretests/src/android/widget/focus/RequestFocus.java @@ -21,9 +21,7 @@ import com.android.frameworks.coretests.R; import android.app.Activity; import android.os.Bundle; import android.os.Handler; -import android.widget.LinearLayout; import android.widget.Button; -import android.view.View; /** * Exercises cases where elements of the UI are requestFocus()ed. diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java index a78b0c932c4b..baf587e39c1b 100644 --- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java +++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java @@ -16,21 +16,27 @@ package android.widget.focus; -import android.widget.focus.RequestFocus; -import com.android.frameworks.coretests.R; - import android.os.Handler; -import android.test.ActivityInstrumentationTestCase; +import android.test.ActivityInstrumentationTestCase2; +import android.test.UiThreadTest; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; -import android.widget.Button; import android.util.AndroidRuntimeException; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; +import android.widget.Button; + +import com.android.frameworks.coretests.R; + +import java.util.ArrayList; +import java.util.List; /** * {@link RequestFocusTest} is set up to exercise cases where the views that * have focus become invisible or GONE. */ -public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFocus> { +public class RequestFocusTest extends ActivityInstrumentationTestCase2<RequestFocus> { private Button mTopLeftButton; private Button mBottomLeftButton; @@ -39,7 +45,7 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc private Handler mHandler; public RequestFocusTest() { - super("com.android.frameworks.coretests", RequestFocus.class); + super(RequestFocus.class); } @Override @@ -94,4 +100,145 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc e.getClass().getName()); } } + + /** + * This tests checks the case in which the first focusable View clears focus. + * In such a case the framework tries to give the focus to another View starting + * from the top. Hence, the framework will try to give focus to the view that + * wants to clear its focus. From a client perspective, the view does not loose + * focus after the call, therefore no callback for focus change should be invoked. + * + * @throws Exception If an error occurs. + */ + @UiThreadTest + public void testOnFocusChangeNotCalledIfFocusDoesNotMove() throws Exception { + // Get the first focusable. + Button button = mTopLeftButton; + + // Make sure that the button is the first focusable and focus it. + button.getRootView().requestFocus(View.FOCUS_DOWN); + assertTrue(button.hasFocus()); + + // Attach on focus change listener that should not be called. + button.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + throw new IllegalStateException("Unexpeced call to" + + "OnFocusChangeListener#onFocusChange"); + } + }); + + // Attach on global focus change listener that should not be called. + button.getViewTreeObserver().addOnGlobalFocusChangeListener( + new OnGlobalFocusChangeListener() { + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + throw new IllegalStateException("Unexpeced call to" + + "OnFocusChangeListener#onFocusChange"); + } + }); + + // Try to clear focus. + button.clearFocus(); + } + + /** + * This tests check whether the on focus change callbacks are invoked in + * the proper order when a View loses focus and the framework gives it to + * the fist focusable one. + * + * @throws Exception + */ + @UiThreadTest + public void testOnFocusChangeCallbackOrder() throws Exception { + // Get the first focusable. + Button clearingFocusButton = mTopRightButton; + Button gainingFocusButton = mTopLeftButton; + + // Make sure that the clearing focus is not the first focusable. + View focusCandidate = clearingFocusButton.getRootView().getParent().focusSearch(null, + View.FOCUS_FORWARD); + assertNotSame("The clearing focus button is not the first focusable.", + clearingFocusButton, focusCandidate); + assertSame("The gaining focus button is the first focusable.", + gainingFocusButton, focusCandidate); + + // Focus the clearing focus button. + clearingFocusButton.requestFocus(); + assertTrue(clearingFocusButton.hasFocus()); + + // Register the invocation order checker. + CallbackOrderChecker checker = new CallbackOrderChecker(clearingFocusButton, + gainingFocusButton); + clearingFocusButton.setOnFocusChangeListener(checker); + gainingFocusButton.setOnFocusChangeListener(checker); + clearingFocusButton.getViewTreeObserver().addOnGlobalFocusChangeListener(checker); + + // Try to clear focus. + clearingFocusButton.clearFocus(); + + // Check that no callback was invoked since focus did not move. + checker.verify(); + } + + /** + * This class check whether the focus change callback are invoked in order. + */ + private class CallbackOrderChecker implements OnFocusChangeListener, + OnGlobalFocusChangeListener { + + private class CallbackInvocation { + final String mMethodName; + final Object[] mArguments; + + CallbackInvocation(String methodName, Object[] arguments) { + mMethodName = methodName; + mArguments = arguments; + } + } + + private final View mClearingFocusView; + private final View mGainingFocusView; + + private final List<CallbackInvocation> mInvocations = new ArrayList<CallbackInvocation>(); + + public CallbackOrderChecker(View clearingFocusView, View gainingFocusView) { + mClearingFocusView = clearingFocusView; + mGainingFocusView = gainingFocusView; + } + + @Override + public void onFocusChange(View view, boolean hasFocus) { + CallbackInvocation invocation = new CallbackInvocation( + "OnFocusChangeListener#onFocusChange", new Object[] {view, hasFocus}); + mInvocations.add(invocation); + } + + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + CallbackInvocation invocation = new CallbackInvocation( + "OnFocusChangeListener#onFocusChange", new Object[] {oldFocus, newFocus}); + mInvocations.add(invocation); + } + + public void verify() { + assertSame("All focus change callback should be invoked.", 3, mInvocations.size()); + assertInvioked("Callback for View clearing focus explected.", 0, + "OnFocusChangeListener#onFocusChange", + new Object[] {mClearingFocusView, false}); + assertInvioked("Callback for View global focus change explected.", 1, + "OnFocusChangeListener#onFocusChange", new Object[] {mClearingFocusView, + mGainingFocusView}); + assertInvioked("Callback for View gaining focus explected.", 2, + "OnFocusChangeListener#onFocusChange", new Object[] {mGainingFocusView, true}); + } + + private void assertInvioked(String message, int order, String methodName, + Object[] arguments) { + CallbackInvocation invocation = mInvocations.get(order); + assertEquals(message, methodName, invocation.mMethodName); + assertEquals(message, arguments[0], invocation.mArguments[0]); + assertEquals(message, arguments[1], invocation.mArguments[1]); + } + } } diff --git a/data/fonts/DroidSansArabic.ttf b/data/fonts/DroidSansArabic.ttf Binary files differnew file mode 100644 index 000000000000..bdefaacb04df --- /dev/null +++ b/data/fonts/DroidSansArabic.ttf diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml index 47cdae7e145f..51b07e44534a 100644 --- a/data/fonts/fallback_fonts.xml +++ b/data/fonts/fallback_fonts.xml @@ -25,7 +25,7 @@ <familyset> <family> <fileset> - <file>DroidNaskh-Regular.ttf</file> + <file>DroidSansArabic.ttf</file> </fileset> </family> <family> diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk index 9dca329a69d4..6a9ed539bfc9 100644 --- a/data/fonts/fonts.mk +++ b/data/fonts/fonts.mk @@ -21,6 +21,7 @@ PRODUCT_COPY_FILES := \ frameworks/base/data/fonts/Roboto-Bold.ttf:system/fonts/Roboto-Bold.ttf \ frameworks/base/data/fonts/Roboto-Italic.ttf:system/fonts/Roboto-Italic.ttf \ frameworks/base/data/fonts/Roboto-BoldItalic.ttf:system/fonts/Roboto-BoldItalic.ttf \ + frameworks/base/data/fonts/DroidSansArabic.ttf:system/fonts/DroidSansArabic.ttf \ frameworks/base/data/fonts/DroidNaskh-Regular.ttf:system/fonts/DroidNaskh-Regular.ttf \ frameworks/base/data/fonts/DroidSansHebrew-Regular.ttf:system/fonts/DroidSansHebrew-Regular.ttf \ frameworks/base/data/fonts/DroidSansHebrew-Bold.ttf:system/fonts/DroidSansHebrew-Bold.ttf \ diff --git a/docs/html/guide/appendix/market-filters.jd b/docs/html/guide/appendix/market-filters.jd index 07b9370d6740..d9b2155f8e46 100644 --- a/docs/html/guide/appendix/market-filters.jd +++ b/docs/html/guide/appendix/market-filters.jd @@ -165,10 +165,10 @@ default.</li> <p><strong>Example 1</strong><br /> The manifest declares <code><uses-sdk android:minSdkVersion="3"></code> - and does not does not include a <code><supports-screens></code> element. - <strong>Result</strong>: Android Market will not show the app to a user of a - small-screen device, but will show it to users of normal and large-screen - devices, users, unless other filters apply. </p> + and does not include a <code><supports-screens></code> element. + <strong>Result</strong>: Android Market does not show the app to a user of a + small-screen device, but does show it to users of normal and large-screen + devices, unless other filters also exclude those devices. </p> <p><strong>Example 2<br /> </strong>The manifest declares <code><uses-sdk android:minSdkVersion="3" android:targetSdkVersion="4"></code> and does not include a diff --git a/docs/html/guide/developing/device.jd b/docs/html/guide/developing/device.jd index e46d07c86df2..d390ec1474d1 100644 --- a/docs/html/guide/developing/device.jd +++ b/docs/html/guide/developing/device.jd @@ -254,6 +254,10 @@ above.</p> <td><code>04DD</code></td> </tr> <tr> + <td>Sony</td> + <td><code>054C</code></td> + </tr> + <tr> <td>Sony Ericsson</td> <td><code>0FCE</code></td> </tr> diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index 4e5badd695ef..4a9a6848632a 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -8,15 +8,16 @@ <ul> <li> - <h2><span class="en">Android Basics</span> - <span class="de" style="display:none">Einführung in Android</span> - <span class="es" style="display:none">Información básica sobre Android</span> - <span class="fr" style="display:none">Présentation d'Android</span> - <span class="it" style="display:none">Nozioni di base su Android</span> - <span class="ja" style="display:none">Android の基本</span> - <span class="zh-CN" style="display:none">Android 基础知识</span> - <span class="zh-TW" style="display:none">Android 簡介</span> - </h2> + <span class="heading"> + <span class="en">Android Basics</span> + <span class="de" style="display:none">Einführung in Android</span> + <span class="es" style="display:none">Información básica sobre Android</span> + <span class="fr" style="display:none">Présentation d'Android</span> + <span class="it" style="display:none">Nozioni di base su Android</span> + <span class="ja" style="display:none">Android の基本</span> + <span class="zh-CN" style="display:none">Android 基础知识</span> + <span class="zh-TW" style="display:none">Android 簡介</span> + </span> <ul> <li><a href="<?cs var:toroot ?>guide/basics/what-is-android.html"> <span class="en">What Is Android?</span> @@ -46,7 +47,7 @@ </li> <li> - <h2> + <span class="heading"> <span class="en">Framework Topics</span> <span class="de" style="display:none">Framework-Themen</span> <span class="es" style="display:none">Temas sobre el framework</span> @@ -55,7 +56,7 @@ <span class="ja" style="display:none">フレームワーク トピック</span> <span class="zh-CN" style="display:none">框架主题</span> <span class="zh-TW" style="display:none">架構主題</span> - </h2> + </span> <ul> <li class="toggle-list"> <div><a href="<?cs var:toroot ?>guide/topics/fundamentals/activities.html"> @@ -130,7 +131,7 @@ </a></li> <li><a href="<?cs var:toroot ?>guide/topics/ui/menus.html"> <span class="en">Menus</span> - </a></li> + </a> <span class="new">updated</span></li> <li><a href="<?cs var:toroot ?>guide/topics/ui/actionbar.html"> <span class="en">Action Bar</span> </a></li> @@ -443,9 +444,9 @@ </li> <li> - <h2> + <span class="heading"> <span class="en">Android Market Topics</span> - </h2> + </span> <ul> <li><a href="<?cs var:toroot ?>guide/publishing/publishing.html"> <span class="en">Publishing on Android Market</span> @@ -489,15 +490,16 @@ <li> - <h2><span class="en">Developing</span> - <span class="de" style="display:none">Entwicklung</span> - <span class="es" style="display:none">Desarrollo</span> - <span class="fr" style="display:none">Développement</span> - <span class="it" style="display:none">Sviluppo</span> - <span class="ja" style="display:none">開発</span> - <span class="zh-CN" style="display:none">开发</span> - <span class="zh-TW" style="display:none">開發</span> - </h2> + <span class="heading"> + <span class="en">Developing</span> + <span class="de" style="display:none">Entwicklung</span> + <span class="es" style="display:none">Desarrollo</span> + <span class="fr" style="display:none">Développement</span> + <span class="it" style="display:none">Sviluppo</span> + <span class="ja" style="display:none">開発</span> + <span class="zh-CN" style="display:none">开发</span> + <span class="zh-TW" style="display:none">開發</span> + </span> <ul> <!--<li><a href="">Developing for Android</a></li> signing, upgrading, selecting a package name, select device profile, touch, trackball, dpad available, etc. --> @@ -683,15 +685,16 @@ </li> <li> - <h2><span class="en">Publishing</span> - <span class="de" style="display:none">Veröffentlichung</span> - <span class="es" style="display:none">Publicación</span> - <span class="fr" style="display:none">Publication</span> - <span class="it" style="display:none">Pubblicazione</span> - <span class="ja" style="display:none">公開</span> - <span class="zh-CN" style="display:none">发布</span> - <span class="zh-TW" style="display:none">發佈</span> - </h2> + <span class="heading"> + <span class="en">Publishing</span> + <span class="de" style="display:none">Veröffentlichung</span> + <span class="es" style="display:none">Publicación</span> + <span class="fr" style="display:none">Publication</span> + <span class="it" style="display:none">Pubblicazione</span> + <span class="ja" style="display:none">公開</span> + <span class="zh-CN" style="display:none">发布</span> + <span class="zh-TW" style="display:none">發佈</span> + </span> <ul> <li><a href="<?cs var:toroot ?>guide/publishing/publishing_overview.html"> <span class="en">Publishing Overview</span> @@ -733,15 +736,16 @@ applications</span> </li> <li> - <h2><span class="en">Best Practices</span> - <span class="de" style="display:none">Bewährte Verfahren</span> - <span class="es" style="display:none">Prácticas recomendadas</span> - <span class="fr" style="display:none">Meilleures pratiques</span> - <span class="it" style="display:none">Best practice</span> - <span class="ja" style="display:none">ベスト プラクティス</span> - <span class="zh-CN" style="display:none">最佳实践</span> - <span class="zh-TW" style="display:none">最佳實務</span> - </h2> + <span class="heading"> + <span class="en">Best Practices</span> + <span class="de" style="display:none">Bewährte Verfahren</span> + <span class="es" style="display:none">Prácticas recomendadas</span> + <span class="fr" style="display:none">Meilleures pratiques</span> + <span class="it" style="display:none">Best practice</span> + <span class="ja" style="display:none">ベスト プラクティス</span> + <span class="zh-CN" style="display:none">最佳实践</span> + <span class="zh-TW" style="display:none">最佳實務</span> + </span> <ul> <li><a href="<?cs var:toroot ?>guide/practices/compatibility.html"> <span class="en">Compatibility</span> @@ -837,8 +841,9 @@ applications</span> </li> <li> - <h2><span class="en">Web Applications</span> - </h2> + <span class="heading"> + <span class="en">Web Applications</span> + </span> <ul> <li><a href="<?cs var:toroot ?>guide/webapps/index.html"> <span class="en">Web Apps Overview</span> @@ -859,15 +864,16 @@ applications</span> </li> <li> - <h2><span class="en">Appendix</span> - <span class="de" style="display:none">Anhang</span> - <span class="es" style="display:none">Apéndice</span> - <span class="fr" style="display:none">Annexes</span> - <span class="it" style="display:none">Appendice</span> - <span class="ja" style="display:none">付録</span> - <span class="zh-CN" style="display:none">附录</span> - <span class="zh-TW" style="display:none">附錄</span> - </h2> + <span class="heading"> + <span class="en">Appendix</span> + <span class="de" style="display:none">Anhang</span> + <span class="es" style="display:none">Apéndice</span> + <span class="fr" style="display:none">Annexes</span> + <span class="it" style="display:none">Appendice</span> + <span class="ja" style="display:none">付録</span> + <span class="zh-CN" style="display:none">附录</span> + <span class="zh-TW" style="display:none">附錄</span> + </span> <ul> <li><a href="<?cs var:toroot ?>guide/appendix/api-levels.html"> <span class="en">Android API Levels</span> diff --git a/docs/html/guide/practices/tablets-and-handsets.jd b/docs/html/guide/practices/tablets-and-handsets.jd index 3f4aaa95a8ab..8e07a08f5553 100644 --- a/docs/html/guide/practices/tablets-and-handsets.jd +++ b/docs/html/guide/practices/tablets-and-handsets.jd @@ -99,7 +99,8 @@ side.</p> <p>You can enable items from the options menu to appear directly in the action bar as "action items". You can also add navigation features to the action bar, such as tabs or a drop-down list, -and use the application icon to supplement the system's BACK behavior with the option to navigate to +and use the application icon to supplement the system's <em>Back</em> button behavior with the option to +navigate to your application's "home" activity or "up" the application's structural hierarchy.</p> <p>This guide provides some tips for using the action bar in ways that support both tablets and @@ -458,7 +459,8 @@ attribute.</p> developer guide, you can use the application icon in the action bar to facilitate user navigation when appropriate—either as a method to get back to the "home" activity (similar to clicking the logo on a web site) or as a way to navigate up the application's structural hierarchy. Although -it might seem similar to the standard BACK navigation in some cases, the up navigation option +it might seem similar to the standard <em>Back</em> navigation in some cases, the up navigation +option provides a more predictable navigation method for situations in which the user may have entered from an external location, such as a notification, app widget, or a different application.</p> diff --git a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd index 5faa7ece8502..9be72eea9935 100644 --- a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd +++ b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd @@ -42,7 +42,8 @@ parent.link=index.html <li><a href=#activities_added_to_task_tip>Allow activities to add to current task</a></li> <li><a href=#notifications_get_back_tip>Notifications and App Widgets should provide consistent back behavior</li> <li><a href=#use_notification_tip>Use the notification system</a></li> - <li><a href=#taking_over_back_key>Don't take over BACK key unless you absolutely need to</a></li> + <li><a href=#taking_over_back_key>Don't take over <em>Back</em> button unless you absolutely +need to</a></li> </ol> </li> </ol> @@ -241,8 +242,8 @@ independent of the other Android system keeps a linear navigation history of activities the user has visited. This is the <em>activity stack</em>, also known as the back stack. In general, when a user starts a new activity, it is added - to the activity stack, so that pressing BACK displays the previous - activity on the stack. However, the user cannot use the BACK key to go + to the activity stack, so that pressing <em>Back</em> displays the previous + activity on the stack. However, the user cannot use the <em>Back</em> button to go back further than the last visit to Home. The adding of an activity to the current stack happens whether or not that activity begins a new <a href=#tasks title=task>task</a> (as long as that task was started @@ -256,10 +257,11 @@ independent of the other Activities are the only things that can be added to the activity stack — views, windows, menus, and dialogs cannot. That is, when designing the navigation, if you have screen A and you want the user - to be able go to a subsequent screen B and then use the BACK key to go + to be able go to a subsequent screen B and then use the <em>Back</em> button to go back to screen A, then the screen A needs to be implemented as an activity. The one exception to this rule is if your application - <a href="#taking_over_back_key">takes control of the BACK key</a> and manages the navigation + <a href="#taking_over_back_key">takes control of the <em>Back</em> button</a> and manages the +navigation itself. </p> @@ -287,7 +289,7 @@ itself. launcher, Home screen shortcut or "Recent tasks" switcher (a long press on Home on some devices). The user can return to a task by choosing the icon for its root activity the same way they started the - task. Once inside a task, the BACK key goes to previous activities in + task. Once inside a task, the <em>Back</em> button goes to previous activities in that task. The activity stack is made up of one or more tasks. </p> @@ -331,7 +333,7 @@ itself. Browser are two applications that do this. For example, choosing an address in an email starts the Maps activity as a new task, and choosing a link in an email starts the Browser activity as a new - task. In these cases, the BACK key will return to the previous + task. In these cases, the <em>Back</em> button will return to the previous activity in a different task (Email), because it was not started from Home. </p> @@ -341,7 +343,7 @@ itself. <p> The following examples illustrate basic principles for applications, - activities, the activity stack, the BACK key, tasks and intents. It + activities, the activity stack, the <em>Back</em> button, tasks and intents. It shows how the system responds to user actions such as starting activities and switching between tasks. With most of these examples you can follow along, launching activities on your device as @@ -367,19 +369,20 @@ itself. <img src={@docRoot}images/activity_task_design/HomeTaskBasics1a.png> </p> -<h3 id=navigating_away_from_an_activity>Navigating Away from an Activity with BACK and HOME keys</h3> +<h3 id=navigating_away_from_an_activity>Navigating Away from an Activity with <em>Back</em> and +<em>Home</em> buttons</h3> <p> An activity can keep or lose its state depending on how the user - leaves the activity — by the HOME or BACK key. + leaves the activity — by the <em>Home</em> or <em>Back</em> button. </p> <p> - By default, pressing the BACK key finishes (destroys) the current + By default, pressing the <em>Back</em> button finishes (destroys) the current activity and displays the previous activity to the user. In the following figure, the user starts email by touching the Email icon in the Home screen, which displays a list of email messages. The user - scrolls down the list (changing its initial state). Pressing BACK + scrolls down the list (changing its initial state). Pressing <em>Back</em> destroys the List Messages activity and returns to the previous activity, which is Home. If the user re-launches Email, it would re-load the messages and display its initial, non-scrolled state. @@ -390,15 +393,15 @@ itself. </p> <p> - In the above example, pressing BACK goes to Home because it was the + In the above example, pressing <em>Back</em> goes to Home because it was the last activity the user was viewing. But if the user had gotten to List - Message from some other activity, then pressing BACK would have + Message from some other activity, then pressing <em>Back</em> would have returned there. </p> <p> By contrast, the next figure shows the user leaving List Messages by - pressing HOME instead of BACK — the List Messages activity is + pressing <em>Home</em> instead of <em>Back</em> — the List Messages activity is stopped and moved to the background rather than being destroyed. Starting Email again from its icon would simply bring the List Messages activity to the foreground (changing it from stopped to @@ -423,8 +426,8 @@ itself. <p> In addition, not all activities have the behavior that they are - destroyed when BACK is pressed. When the user starts playing music in - the Music application and then presses BACK, the application overrides + destroyed when <em>Back</em> is pressed. When the user starts playing music in + the Music application and then presses <em>Back</em>, the application overrides the normal back behavior, preventing the player activity from being destroyed, and continues playing music, even though its activity is no longer visible — as a visual substitute, the Music application @@ -451,7 +454,7 @@ itself. activity to get a picture. This is a good example of re-use of the Gallery activity. The following figure illustrates the sequence of activities to do this (up to crop). This is how it's done: The user - chooses Contacts, selects the contact for viewing, chooses MENU > + chooses Contacts, selects the contact for viewing, chooses <em>Menu</em> > Edit contact and touches the picture field, which launches the Gallery activity. The user then chooses the picture they want, crops and saves it. Saving it causes the picture to be inserted into the picture field @@ -484,12 +487,12 @@ itself. <b>Gallery Re-Uses Messaging for Sharing a Picture</b> - Sharing is another good example of one application re-using an activity from a different application. As shown in the following figure, the user - starts Gallery, picks a picture to view, chooses MENU > Share, and + starts Gallery, picks a picture to view, chooses <em>Menu</em> > Share, and picks "Messaging". This starts the Messaging activity, creates a new message and attaches the original picture to it. The user then fills in the "To" field, writes a short message and sends it. User focus remains in the Messaging program. If the user wants to go back to the - Gallery, they must press the BACK key. (The user can back up through + Gallery, they must press the <em>Back</em> button. (The user can back up through each activity all the way to Home.) </p> @@ -552,7 +555,7 @@ itself. <ul> <li> State 2 - The user wants to do something else while they're - waiting, so they press HOME, which does not interrupt the map's + waiting, so they press <em>Home</em>, which does not interrupt the map's network connection and allows the map to continue loading in the background. @@ -729,7 +732,7 @@ href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Fi <b>Start first task.</b> You want to send a text message and attach a photo. You would choose: <p> - Home > Messaging > New message > MENU > Attach + Home > Messaging > New message > <em>Menu</em> > Attach > Pictures. This last step launches the picture gallery for picking a photo. Notice that picture gallery is an activity in a separate application. @@ -961,7 +964,7 @@ MAIN and address in an email message (or web page), where the Maps activity is started to map the location. No result from maps is expected to be returned to the email message; the user - can return by pressing the BACK key. (Such an activity is + can return by pressing the <em>Back</em> button. (Such an activity is started with {@link android.content.Context#startActivity(android.content.Intent) startActivity()}.) @@ -1102,20 +1105,21 @@ MAIN and convenience to respond to your message. </p> -<h3 id=taking_over_back_key>Don't take over the BACK key unless you absolutely need to</h3> +<h3 id=taking_over_back_key>Don't take over the <em>Back</em> button unless you absolutely need +to</h3> <p> As a user navigates from one activity to the next, the system adds them to the activity stack. This forms a navigation history that is - accessible with the BACK key. Most activities are relatively limited + accessible with the <em>Back</em> button. Most activities are relatively limited in scope, with just one set of data, such as viewing a list of contacts, composing an email, or taking a photo. But what if your application is one big activity with several pages of content and - needs finer-grained control of the BACK key? Examples of such Google + needs finer-grained control of the <em>Back</em> button? Examples of such Google applications are the Browser, which can have several web pages open at once, and Maps, which can have several layers of geographic data to switch between. Both of these applications take control of the - BACK key and maintain their own internal back stacks that operate + <em>Back</em> button and maintain their own internal back stacks that operate only when these applications have focus. </p> @@ -1124,7 +1128,7 @@ MAIN and information on a map to the user: displaying the location of a search result, displaying locations of friends, and displaying a line for a street path providing direction between points. Maps - stores these layers in its own history so the BACK key can return to + stores these layers in its own history so the <em>Back</em> button can return to a previous layer. </p> @@ -1135,8 +1139,8 @@ MAIN and as Windows, Macintosh or Linux). For example, if you did a Google web search in one window of the Android Browser, clicking on a link in the search results displays a web page in that same window, and - then pressing BACK would to the search results page. Pressing - BACK goes to a previous window only if the current window was + then pressing <em>Back</em> would to the search results page. Pressing + <em>Back</em> goes to a previous window only if the current window was launched from that previous window. If the user keeps pressing back, they will eventually leave the browser activity and return Home. diff --git a/docs/html/guide/practices/ui_guidelines/menu_design.jd b/docs/html/guide/practices/ui_guidelines/menu_design.jd index 3edf33fe3759..7576b6c58858 100644 --- a/docs/html/guide/practices/ui_guidelines/menu_design.jd +++ b/docs/html/guide/practices/ui_guidelines/menu_design.jd @@ -71,7 +71,7 @@ parent.link=index.html <ul> <li>The <em>Options menu</em> contains primary functionality that applies globally to the current activity or starts a related activity. - It is typically invoked by a user pressing a hard button, often labeled MENU.</li> + It is typically invoked by a user pressing a hard button, often labeled <em>Menu</em>.</li> <li>The <em>Context menu</em> contains secondary functionality for the currently selected item. It is typically invoked by a user's touch & hold on an item. Like on the Options menu, the operation can run either @@ -109,10 +109,10 @@ or device to another. </p> <p> - On most devices, a user presses the MENU button to access the Options menu, + On most devices, a user presses the <em>Menu</em> button to access the Options menu, as shown in the screenshot below. To close the menu, the user presses - MENU again, or presses the BACK button. - In fact, to cancel out of any menu, press the BACK button. (Pressing the MENU + <em>Menu</em> again, or presses the <em>Back</em> button. + In fact, to cancel out of any menu, press the <em>Back</em> button. (Pressing the <em>Menu</em> button or touching outside the menu also works.) Note that how to invoke this menu may be different on different devices. </p> @@ -140,7 +140,7 @@ or device to another. <ul> <li> - <b>Options icon menu</b> - The first press of the MENU button displays a + <b>Options icon menu</b> - The first press of the <em>Menu</em> button displays a non-scrollable grid of icons at the bottom of the screen. (On the G1 phone, up to 6 buttons typically appear.) </li> @@ -156,7 +156,7 @@ or device to another. <p> On some versions of Android, the user can display keyboard shortcuts in the - icon menu by long pressing the MENU button — the text in the icon menu + icon menu by long pressing the <em>Menu</em> button — the text in the icon menu alternates between the command names and their keyboard shortcuts (if any). </p> @@ -299,7 +299,7 @@ or device to another. <a href="#location">location</a>) on the screen, put the command in the Context menu for that content. If the command acts on no specific content or location, put it in the Options menu. This separation of commands - is enforced by the system in the following way. When you press the MENU + is enforced by the system in the following way. When you press the <em>Menu</em> button to display the Options menu, the selected content becomes unselected, and so cannot be operated on. For an explanation of why the content becomes unselected, see the article on @@ -340,7 +340,7 @@ or device to another. <p> Before opening a Context menu, it has no visual representation that identifies - its presence (whereas the Options menu has the MENU button), and so is not + its presence (whereas the Options menu has the <em>Menu</em> button), and so is not particularly discoverable. Therefore, in general, a Context menu should <em>duplicate</em> commands found in the corresponding activity screen. For example, while it's useful to @@ -459,7 +459,8 @@ or device to another. <h3 id="a_dialog_should_not_have_an_options_menu">A dialog should not have an Options menu</h3> <p> - When a dialog is displayed, pressing the MENU button should do nothing. This also holds true + When a dialog is displayed, pressing the <em>Menu</em> button should do nothing. This also holds +true for activities that look like dialogs. A dialog box is recognizable by being smaller than full-screen, having zero to three buttons, is non-scrollable, and possibly a list of selectable items that can include checkboxes or radio buttons. @@ -475,7 +476,7 @@ or device to another. <h3 id="do_not_substitute_message">If an activity has no Options menu, do not display a message</h3> <p> - When the user presses the MENU button, if there is no Options menu, the system + When the user presses the <em>Menu</em> button, if there is no Options menu, the system currently does nothing. We recommend you do not perform any action (such as displaying a message). It's a better user experience for this behavior to be consistent across applications. diff --git a/docs/html/guide/topics/fundamentals/activities.jd b/docs/html/guide/topics/fundamentals/activities.jd index 3b311997e4b5..8736aa89ed4c 100644 --- a/docs/html/guide/topics/fundamentals/activities.jd +++ b/docs/html/guide/topics/fundamentals/activities.jd @@ -63,7 +63,7 @@ activity can then start another activity in order to perform different actions. activity starts, the previous activity is stopped, but the system preserves the activity in a stack (the "back stack"). When a new activity starts, it is pushed onto the back stack and takes user focus. The back stack abides to the basic "last in, first out" queue mechanism, -so, when the user is done with the current activity and presses the BACK key, it +so, when the user is done with the current activity and presses the <em>Back</em> button, it is popped from the stack (and destroyed) and the previous activity resumes. (The back stack is discussed more in the <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> document.)</p> @@ -649,7 +649,8 @@ remains intact.</p> <p class="note"><strong>Note:</strong> There's no guarantee that {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} will be called before your activity is destroyed, because there are cases in which it won't be necessary to save the state -(such as when the user leaves your activity using the BACK key, because the user is explicitly +(such as when the user leaves your activity using the <em>Back</em> button, because the user is +explicitly closing the activity). If the system calls {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}, it does so before {@link android.app.Activity#onStop onStop()} and possibly before {@link android.app.Activity#onPause diff --git a/docs/html/guide/topics/fundamentals/fragments.jd b/docs/html/guide/topics/fundamentals/fragments.jd index e8f6cd888ef8..d4f9342dd632 100644 --- a/docs/html/guide/topics/fundamentals/fragments.jd +++ b/docs/html/guide/topics/fundamentals/fragments.jd @@ -78,7 +78,7 @@ manipulate each fragment independently, such as add or remove them. When you per fragment transaction, you can also add it to a back stack that's managed by the activity—each back stack entry in the activity is a record of the fragment transaction that occurred. The back stack allows the user to reverse a fragment transaction (navigate backwards), -by pressing the BACK button.</p> +by pressing the <em>Back</em> button.</p> <p>When you add a fragment as a part of your activity layout, it lives in a {@link android.view.ViewGroup} inside the activity's view hierarchy and the fragment defines its own view @@ -398,7 +398,7 @@ android.app.FragmentManager#findFragmentById findFragmentById()} (for fragments the activity layout) or {@link android.app.FragmentManager#findFragmentByTag findFragmentByTag()} (for fragments that do or don't provide a UI).</li> <li>Pop fragments off the back stack, with {@link -android.app.FragmentManager#popBackStack()} (simulating a BACK command by the user).</li> +android.app.FragmentManager#popBackStack()} (simulating a <em>Back</em> command by the user).</li> <li>Register a listener for changes to the back stack, with {@link android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()}.</li> </ul> @@ -439,7 +439,7 @@ to the activity, you must call {@link android.app.FragmentTransaction#commit()}. android.app.FragmentTransaction#commit()}, however, you might want to call {@link android.app.FragmentTransaction#addToBackStack addToBackStack()}, in order to add the transaction to a back stack of fragment transactions. This back stack is managed by the activity and allows -the user to return to the previous fragment state, by pressing the BACK key.</p> +the user to return to the previous fragment state, by pressing the <em>Back</em> button.</p> <p>For example, here's how you can replace one fragment with another, and preserve the previous state in the back stack:</p> @@ -462,14 +462,14 @@ transaction.commit(); layout container identified by the {@code R.id.fragment_container} ID. By calling {@link android.app.FragmentTransaction#addToBackStack addToBackStack()}, the replace transaction is saved to the back stack so the user can reverse the transaction and bring back the -previous fragment by pressing the BACK key.</p> +previous fragment by pressing the <em>Back</em> button.</p> <p>If you add multiple changes to the transaction (such as another {@link android.app.FragmentTransaction#add add()} or {@link android.app.FragmentTransaction#remove remove()}) and call {@link android.app.FragmentTransaction#addToBackStack addToBackStack()}, then all changes applied before you call {@link android.app.FragmentTransaction#commit commit()} are added to the -back stack as a single transaction and the BACK key will reverse them all together.</p> +back stack as a single transaction and the <em>Back</em> button will reverse them all together.</p> <p>The order in which you add changes to a {@link android.app.FragmentTransaction} doesn't matter, except:</p> @@ -696,7 +696,7 @@ document.</p> <p>The most significant difference in lifecycle between an activity and a fragment is how one is stored in its respective back stack. An activity is placed into a back stack of activities that's managed by the system when it's stopped, by default (so that the user can navigate back -to it with the BACK key, as discussed in <a +to it with the <em>Back</em> button, as discussed in <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a>). However, a fragment is placed into a back stack managed by the host activity only when you explicitly request that the instance be saved by calling {@link diff --git a/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd index 086ba71d717f..465cf542dfb1 100644 --- a/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd +++ b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd @@ -74,7 +74,7 @@ order in which each activity is opened.</p> suppose you have a two-pane layout using fragments, one of which is a list view (fragment A) and the other being a layout to display an item from the list (fragment B). When the user selects an item from the list, fragment B is replaced by a new fragment (fragment C). In this case, it might be -desireable for the user to navigate back to reveal fragment B, using the BACK button.</p> +desireable for the user to navigate back to reveal fragment B, using the <em>Back</em> button.</p> <p>In order to add fragment B to the back stack so that this is possible, you must call {@link android.app.FragmentTransaction#addToBackStack addToBackStack()} before you {@link android.app.FragmentTransaction#commit()} the transaction that replaces fragment B with fragment @@ -94,22 +94,26 @@ is created and the "main" activity for that application opens as the root activi <p>When the current activity starts another, the new activity is pushed on the top of the stack and takes focus. The previous activity remains in the stack, but is stopped. When an activity -stops, the system retains the current state of its user interface. When the user presses the BACK +stops, the system retains the current state of its user interface. When the user presses the +<em>Back</em> button, the current activity is popped from the top of the stack (the activity is destroyed) and the previous activity resumes (the previous state of its UI is restored). Activities in the stack are never rearranged, only pushed and popped from the stack—pushed onto the stack when started by -the current activity and popped off when the user leaves it using the BACK button. As such, the back +the current activity and popped off when the user leaves it using the <em>Back</em> button. As such, +the back stack operates as a "last in, first out" object structure. Figure 1 visualizes this behavior with a timeline showing the progress between activities along with the current back stack at each point in time.</p> <img src="{@docRoot}images/fundamentals/diagram_backstack.png" alt="" /> <p class="img-caption"><strong>Figure 1.</strong> A representation of how each new activity in a -task adds an item to the back stack. When the user presses the BACK button, the current activity is +task adds an item to the back stack. When the user presses the <em>Back</em> button, the current +activity is destroyed and the previous activity resumes.</p> -<p>If the user continues to press BACK, then each activity in the stack is popped off to reveal the +<p>If the user continues to press <em>Back</em>, then each activity in the stack is popped off to +reveal the previous one, until the user returns to the Home screen (or to whichever activity was running when the task began). When all activities are removed from the stack, the task no longer exists.</p> @@ -124,11 +128,13 @@ class="img-caption"><strong>Figure 3.</strong> A single activity is instantiated </div> <p>A task is a cohesive unit that can move to the "background" when users begin a new task or go -to the Home screen, via the HOME button. While in the background, all the activities in the task are +to the Home screen, via the <em>Home</em> button. While in the background, all the activities in the +task are stopped, but the back stack for the task remains intact—the task has simply lost focus while another task takes place, as shown in figure 2. A task can then return to the "foreground" so users can pick up where they left off. Suppose, for example, that the current task (Task A) has three -activities in its stack—two under the current activity. The user presses the HOME button, then +activities in its stack—two under the current activity. The user presses the <em>Home</em> +button, then starts a new application from the application launcher. When the Home screen appears, Task A goes into the background. When the new application starts, the system starts a task for that application (Task B) with its own stack of activities. After interacting with @@ -137,7 +143,8 @@ started Task A. Now, Task A comes to the foreground—all three activities in its stack are intact and the activity at the top of the stack resumes. At this point, the user can also switch back to Task B by going Home and selecting the application icon -that started that task (or by touching and holding the HOME button to reveal recent tasks and selecting +that started that task (or by touching and holding the <em>Home</em> button to reveal recent tasks +and selecting one). This is an example of multitasking on Android.</p> <p class="note"><strong>Note:</strong> Multiple tasks can be held in the background at once. @@ -150,7 +157,8 @@ users to start a particular activity from more than one activity, a new instance that activity is created and popped onto the stack (rather than bringing any previous instance of the activity to the top). As such, one activity in your application might be instantiated multiple times (even from different tasks), as shown in figure 3. As such, if the user navigates backward -using the BACK button, each instance of the activity is revealed in the order they were opened (each +using the <em>Back</em> button, each instance of the activity is revealed in the order they were +opened (each with their own UI state). However, you can modify this behavior if you do not want an activity to be instantiated more than once. How to do so is discussed in the later section about <a href="#ManagingTasks">Managing Tasks</a>.</p> @@ -161,13 +169,15 @@ href="#ManagingTasks">Managing Tasks</a>.</p> <ul> <li>When Activity A starts Activity B, Activity A is stopped, but the system retains its state (such as scroll position and text entered into forms). -If the user presses the BACK button while in Activity B, Activity A resumes with its state +If the user presses the <em>Back</em> button while in Activity B, Activity A resumes with its state restored.</li> - <li>When the user leaves a task by pressing the HOME button, the current activity is stopped and + <li>When the user leaves a task by pressing the <em>Home</em> button, the current activity is +stopped and its task goes into the background. The system retains the state of every activity in the task. If the user later resumes the task by selecting the launcher icon that began the task, the task comes to the foreground and resumes the activity at the top of the stack.</li> - <li>If the user presses the BACK button, the current activity is popped from the stack and + <li>If the user presses the <em>Back</em> button, the current activity is popped from the stack +and destroyed. The previous activity in the stack is resumed. When an activity is destroyed, the system <em>does not</em> retain the activity's state.</li> <li>Activities can be instantiated multiple times, even from other tasks.</li> @@ -256,7 +266,8 @@ flags to define how activities are associated with tasks and how the behave in t <p class="caution"><strong>Caution:</strong> Most applications should not interrupt the default behavior for activities and tasks. If you determine that it's necessary for your activity to modify the default behaviors, use caution and be sure to test the usability of the activity during -launch and when navigating back to it from other activities and tasks with the BACK button. Be sure +launch and when navigating back to it from other activities and tasks with the <em>Back</em> button. +Be sure to test for navigation behaviors that might conflict with the user's expected behavior.</p> @@ -320,8 +331,10 @@ android.app.Activity#onNewIntent onNewIntent()}, because it's at the top of the stack remains A-B-C-D. However, if an intent arrives for an activity of type B, then a new instance of B is added to the stack, even if its launch mode is {@code "singleTop"}.</p> <p class="note"><strong>Note:</strong> When a new instance of an activity is created, -the user can press the BACK button to return to the previous activity. But when an existing instance of -an activity handles a new intent, the user cannot press the BACK button to return to the state of +the user can press the <em>Back</em> button to return to the previous activity. But when an existing +instance of +an activity handles a new intent, the user cannot press the <em>Back</em> button to return to the +state of the activity before the new intent arrived in {@link android.app.Activity#onNewIntent onNewIntent()}.</p> </dd> @@ -333,7 +346,7 @@ intent to the existing instance through a call to its {@link android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance. Only one instance of the activity can exist at a time. <p class="note"><strong>Note:</strong> Although the activity starts in a new task, the -BACK button still returns the user to the previous activity.</p></dd> +<em>Back</em> button still returns the user to the previous activity.</p></dd> <dt>{@code "singleInstance"}.</dt> <dd>Same as {@code "singleTask"}, except that the system doesn't launch any other activities into the task holding the instance. The activity is always the single and only member of its task; @@ -351,7 +364,7 @@ already has a task running in the background, that task is brought forward to ha intent.</p> <p>Regardless of whether an activity starts in a new task or in the same task as the activity that -started it, the BACK button always takes the user to the previous activity. However, if you +started it, the <em>Back</em> button always takes the user to the previous activity. However, if you start an activity that specifies the {@code singleTask} launch mode, then if an instance of that activity exists in a background task, that whole task is brought to the foreground. At this point, the back stack now includes all activities from the task brought forward, at the top of the @@ -454,7 +467,8 @@ flag, the system looks for a different task to house the new activity. Often, it However, it doesn't have to be. If there's already an existing task with the same affinity as the new activity, the activity is launched into that task. If not, it begins a new task.</p> -<p>If this flag causes an activity to begin a new task and the user presses the HOME button to leave +<p>If this flag causes an activity to begin a new task and the user presses the <em>Home</em> button +to leave it, there must be some way for the user to navigate back to the task. Some entities (such as the notification manager) always start activities in an external task, never as part of their own, so they always put {@code FLAG_ACTIVITY_NEW_TASK} in the intents they pass to {@link @@ -556,7 +570,8 @@ android.content.Intent#ACTION_MAIN} and a {@link android.content.Intent#CATEGORY_LAUNCHER} filter. Imagine, for example, what could happen if the filter is missing: An intent launches a {@code "singleTask"} activity, initiating a new task, and the user spends some time working in -that task. The user then presses the HOME button. The task is now sent to the background and is +that task. The user then presses the <em>Home</em> button. The task is now sent to the background +and is not visible. Now the user has no way to return to the task, because it is not represented in the application launcher. </p> diff --git a/docs/html/guide/topics/intents/intents-filters.jd b/docs/html/guide/topics/intents/intents-filters.jd index 3f9455391a86..3ad3c9374a81 100644 --- a/docs/html/guide/topics/intents/intents-filters.jd +++ b/docs/html/guide/topics/intents/intents-filters.jd @@ -247,7 +247,7 @@ several category constants, including these: </tr><tr> <td>{@code CATEGORY_HOME} <td>The activity displays the home screen, the first screen the user sees when - the device is turned on or when the HOME key is pressed. + the device is turned on or when the <em>Home</em> button is pressed. </tr><tr> <td>{@code CATEGORY_LAUNCHER} <td>The activity can be the initial activity of a task and is listed in diff --git a/docs/html/guide/topics/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd index e76a6becb011..4d9603fd1186 100644 --- a/docs/html/guide/topics/manifest/activity-element.jd +++ b/docs/html/guide/topics/manifest/activity-element.jd @@ -133,21 +133,21 @@ is meaningful only for activities that start a new task (the root activity); it's ignored for all other activities in the task. <p> -When the value is "{@code true}", every time users start the task again, they -are brought to its root activity, regardless of what they were last doing in -the task and regardless of whether they used BACK or HOME to last leave it. -When the value is "{@code false}", the task may be cleared of activities in +When the value is "{@code true}", every time users start the task again, they +are brought to its root activity regardless of what they were last doing in +the task and regardless of whether they used the <em>Back</em> or <em>Home</em> button to +leave it. When the value is "{@code false}", the task may be cleared of activities in some situations (see the <code><a href="#always">alwaysRetainTaskState</a></code> attribute), but not always. </p> <p> Suppose, for example, that someone launches activity P from the home screen, -and from there goes to activity Q. The user next presses HOME, and then returns +and from there goes to activity Q. The user next presses <em>Home</em>, and then returns to activity P. Normally, the user would see activity Q, since that is what they were last doing in P's task. However, if P set this flag to "{@code true}", all of the activities on top of it (Q in this case) were removed when the user pressed -HOME and the task went to the background. So the user sees only P when returning +<em>Home</em> and the task went to the background. So the user sees only P when returning to the task. </p> @@ -501,7 +501,7 @@ users and is very different from most other applications. <p>Regardless of the launch mode that you choose, make sure to test the usability of the activity during launch and when navigating back to it from -other activities and tasks using the BACK key. </p> +other activities and tasks using the <em>Back</em> button. </p> <p>For more information on launch modes and their interaction with Intent flags, see the diff --git a/docs/html/guide/topics/search/search-dialog.jd b/docs/html/guide/topics/search/search-dialog.jd index e06563d47d2b..8b8e75bb8a73 100644 --- a/docs/html/guide/topics/search/search-dialog.jd +++ b/docs/html/guide/topics/search/search-dialog.jd @@ -544,7 +544,8 @@ public boolean onSearchRequested() { } </pre> -<p>If the user cancels search by pressing the BACK button, the search dialog closes and the activity +<p>If the user cancels search by pressing the <em>Back</em> button, the search dialog closes and the +activity regains input focus. You can register to be notified when the search dialog is closed with {@link android.app.SearchManager#setOnDismissListener(SearchManager.OnDismissListener) setOnDismissListener()} @@ -569,7 +570,8 @@ things happens:</p> android.content.Intent#ACTION_SEARCH} intent with a call to {@link android.app.Activity#onCreate(Bundle) onCreate()} and a new instance of the activity is brought to the top of the activity stack. There are now two instances of your -searchable activity in the activity stack (so pressing the BACK button goes back to the previous +searchable activity in the activity stack (so pressing the <em>Back</em> button goes back to the +previous instance of the searchable activity, rather than exiting the searchable activity).</li> <li>If you set {@code android:launchMode} to <code>"singleTop"</code>, then the searchable activity receives the {@link android.content.Intent#ACTION_SEARCH} intent with a call diff --git a/docs/html/guide/topics/ui/actionbar.jd b/docs/html/guide/topics/ui/actionbar.jd index b83bde75dd2a..e59fa0f1aaca 100644 --- a/docs/html/guide/topics/ui/actionbar.jd +++ b/docs/html/guide/topics/ui/actionbar.jd @@ -113,9 +113,10 @@ accessible to the user in a predictable way. href="{@docRoot}guide/topics/ui/menus.html#OptionsMenu">options menu</a> directly in the action bar, as "action items." Action items can also provide an "action view," which provides an embedded widget for even more immediate action behaviors. Menu items that are not promoted -to an action item are available in the overflow menu, revealed by either the device MENU button +to an action item are available in the overflow menu, revealed by either the device <em>Menu</em> +button (when available) or by an "overflow menu" button in the action bar (when the device does not -include a MENU button).</p> +include a <em>Menu</em> button).</p> </li> </ul> @@ -125,6 +126,10 @@ href="{@docRoot}resources/samples/HoneycombGallery/index.html">Honeycomb Gallery landscape handset), showing the logo on the left, navigation tabs, and an action item on the right (plus the overflow menu button).</p> +<p class="note"><strong>Note:</strong> If you're looking for information about the contextual +action bar for displaying contextual action items, see the <a +href="{@docRoot}guide/topics/ui/menus.html#context-menu">Menu</a> guide.</p> + <div class="design-announce"> <p><strong>Action Bar Design</strong></p> @@ -225,9 +230,10 @@ later—calling {@link android.app.Activity#getActionBar()} will return null href="{@docRoot}guide/topics/ui/menus.html#OptionsMenu">options menu</a>. To do this, you can declare that the menu item should appear in the action bar as an "action item." An action item can include an icon and/or a text title. If a menu item does not appear as an action item, then the -system places it in the overflow menu. The overflow menu is revealed either by the device MENU +system places it in the overflow menu. The overflow menu is revealed either by the device +<em>Menu</em> button (if provided by the device) or an additional button in the action bar (if the device does not -provide the MENU button).</p> +provide the <em>Menu</em> button).</p> <div class="figure" style="width:359px"> <img src="{@docRoot}images/ui/actionbar-item-withtext.png" height="57" alt="" /> @@ -1421,7 +1427,7 @@ href="#ActionView">action views</a>. (Added in API level 14.)</dd> </style> <!-- style for the action bar tab text --> - <style name="CustomTabTextStyle"> + <style name="CustomTabTextStyle" parent="@android:style/TextAppearance.Holo"> <item name="android:textColor">#2456c2</item> </style> </resources> @@ -1437,8 +1443,7 @@ action bar styles you want to change without re-implementing the styles you want manifest file like this:</p> <pre> -<application android:theme="@style/CustomActivityTheme" - ... /> +<application android:theme="@style/CustomActivityTheme" ... /> </pre> <p>For more information about using style and theme resources in your application, read <a @@ -1457,7 +1462,7 @@ android:backgroundStacked}. If you override these action bar styles, be sure tha parent action bar style such as {@link android.R.style#Widget_Holo_ActionBar Widget.Holo.ActionBar}.</p> -<p>For example, if you want to change the action bar's background, you could use the following +<p>For example, if you want to change the action bar's background, you can use the following styles:</p> <pre> @@ -1465,14 +1470,15 @@ styles:</p> <resources> <!-- the theme applied to the application or activity --> <style name="CustomActivityTheme" parent="@android:style/Theme.Holo"> - <item name="android:actionBarTabTextStyle">@style/customTabTextStyle</item> + <item name="android:actionBarStyle">@style/MyActionBar</item> <!-- other activity and action bar styles here --> </style> - <!-- style for the action bar, simply to change the background --> - <style parent="@android:style/Widget.Holo.ActionBar"> + <!-- style for the action bar backgrounds --> + <style name="MyActionBar" parent="@android:style/Widget.Holo.ActionBar"> <item name="android:background">@drawable/ab_background</item> - <item name="android:backgroundSplit">@drawable/ab_background</item> + <item name="android:backgroundStacked">@drawable/ab_background</item> + <item name="android:backgroundSplit">@drawable/ab_split_background</item> </style> </resources> </pre> diff --git a/docs/html/guide/topics/ui/how-android-draws.jd b/docs/html/guide/topics/ui/how-android-draws.jd index 3a57afacaa27..6a8cd869e535 100644 --- a/docs/html/guide/topics/ui/how-android-draws.jd +++ b/docs/html/guide/topics/ui/how-android-draws.jd @@ -62,7 +62,7 @@ and each View is responsible for drawing itself. <p> The measure pass uses two classes to communicate dimensions. The - {@link android.view.View.MeasureSpec} class is used by Views to tell their parents how they + {@link android.view.ViewGroup.LayoutParams} class is used by Views to tell their parents how they want to be measured and positioned. The base LayoutParams class just describes how big the View wants to be for both width and height. For each dimension, it can specify one of:</p> diff --git a/docs/html/guide/topics/ui/index.jd b/docs/html/guide/topics/ui/index.jd index d3060c587eea..83c815000367 100644 --- a/docs/html/guide/topics/ui/index.jd +++ b/docs/html/guide/topics/ui/index.jd @@ -174,7 +174,8 @@ href="ui-events.html">Input Events</a> document.</p> <p>Application menus are another important part of an application's UI. Menus offers a reliable interface that reveals application functions and settings. The most common application menu is revealed by pressing -the MENU key on the device. However, you can also add Context Menus, which may be revealed when the user presses +the <em>Menu</em> button on the device. However, you can also add Context Menus, which may be +revealed when the user presses and holds down on an item.</p> <p>Menus are also structured using a View hierarchy, but you don't define this structure yourself. Instead, diff --git a/docs/html/guide/topics/ui/menus.jd b/docs/html/guide/topics/ui/menus.jd index 7b5b3dc2f3c1..d51a378cb4a3 100644 --- a/docs/html/guide/topics/ui/menus.jd +++ b/docs/html/guide/topics/ui/menus.jd @@ -6,77 +6,129 @@ parent.link=index.html <div id="qv-wrapper"> <div id="qv"> <h2>In this document</h2> - <ol> - <li><a href="#xml">Creating a Menu Resource</a></li> - <li><a href="#Inflating">Inflating a Menu Resource</a> - <li><a href="#options-menu">Creating an Options Menu</a> - <ol> - <li><a href="#ChangingTheMenu">Changing menu items at runtime</a></li> - </ol> - </li> - <li><a href="#context-menu">Creating a Context Menu</a></li> - <li><a href="#submenu">Creating a Submenu</a></li> - <li><a href="#features">Other Menu Features</a> - <ol> - <li><a href="#groups">Menu groups</a></li> - <li><a href="#checkable">Checkable menu items</a></li> - <li><a href="#shortcuts">Shortcut keys</a></li> - <li><a href="#intents">Dynamically adding menu intents</a></li> - </ol> - </li> - </ol> +<ol> + <li><a href="#xml">Defining a Menu in XML</a></li> + <li><a href="#options-menu">Creating an Options Menu</a> + <ol> + <li><a href="#RespondingOptionsMenu">Handling click events</a></li> + <li><a href="#ChangingTheMenu">Changing menu items at runtime</a></li> + </ol> + </li> + <li><a href="#context-menu">Creating Contextual Menus</a> + <ol> + <li><a href="#FloatingContextMenu">Creating a floating context menu</a></li> + <li><a href="#CAB">Using the contextual action mode</a></li> + </ol> + </li> + <li><a href="#PopupMenu">Creating a Popup Menu</a> + <ol> + <li><a href="#PopupEvents">Handling click events</a></li> + </ol> + </li> + <li><a href="#groups">Creating Menu Groups</a> + <ol> + <li><a href="#checkable">Using checkable menu items</a></li> + </ol> + </li> + <li><a href="#intents">Adding Menu Items Based on an Intent</a> + <ol> + <li><a href="#AllowingToAdd">Allowing your activity to be added to other menus</a></li> + </ol> + </li> +</ol> <h2>Key classes</h2> <ol> <li>{@link android.view.Menu}</li> <li>{@link android.view.MenuItem}</li> <li>{@link android.view.ContextMenu}</li> - <li>{@link android.view.SubMenu}</li> + <li>{@link android.view.ActionMode}</li> </ol> <h2>See also</h2> <ol> <li><a href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a></li> <li><a href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a></li> + <li><a +href="http://android-developers.blogspot.com/2012/01/say-goodbye-to-menu-button.html">Say +Goodbye to the Menu Button</a></li> </ol> </div> </div> -<p>Menus are an important part of an activity's user interface, which provide users a familiar -way to perform actions. Android offers a simple framework for you to add standard -menus to your application.</p> +<p>Menus are a common user interface component in many types of applications. To provide a familiar +and consistent user experience, you should use the {@link android.view.Menu} APIs to present user +actions and other options in your activities.</p> + +<p>Beginning with Android 3.0 (API level 11), Android-powered devices are no longer required to +provide a dedicated <em>Menu</em> button. With this change, Android apps should migrate away from a +dependence on the traditional 6-item menu panel and instead provide an action bar to present common +user actions.</p> + +<p>Although the design and user experience for some menu items have changed, the semantics to define +a set of actions and options is still based on the {@link android.view.Menu} APIs. This +guide shows how to create the three fundamental types of menus or action presentations on all +versions of Android:</p> -<p>There are three types of application menus:</p> <dl> - <dt><strong>Options Menu</strong></dt> - <dd>The primary collection of menu items for an activity, which appears when the user touches -the MENU button. When your application is running on Android 3.0 or later, you can provide -quick access to select menu items by placing them directly in the <a -href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a>, as "action items."</dd> - <dt><strong>Context Menu</strong></dt> - <dd>A floating list of menu items that appears when the user touches and holds a view -that's registered to provide a context menu. + <dt><strong>Options menu and action bar</strong></dt> + <dd>The <a href="#options-menu">options menu</a> is the primary collection of menu items for an +activity. It's where you should place actions that have a global impact on the app, such as +"Search," "Compose email," and "Settings." + <p>If you're developing for Android 2.3 or lower, users can +reveal the options menu panel by pressing the <em>Menu</em> button.</p> + <p>On Android 3.0 and higher, items from the options menu are presented by the <a +href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a> as a combination of on-screen action +items and overflow options. Beginning with Android 3.0, the <em>Menu</em> button is deprecated (some +devices +don't have one), so you should migrate toward using the action bar to provide access to actions and +other options.</p> + <p>See the section about <a href="#options-menu">Creating an Options Menu</a>.</p> + </dd> + + <dt><strong>Context menu and contextual action mode</strong></dt> + + <dd>A context menu is a <a href="#FloatingContextMenu">floating menu</a> that appears when the +user performs a long-click on an element. It provides actions that affect the selected content or +context frame. + <p>When developing for Android 3.0 and higher, you should instead use the <a +href="#CAB">contextual action mode</a> to enable actions on selected content. This mode displays +action items that affect the selected content in a bar at the top of the screen and allows the user +to select multiple items.</p> + <p>See the section about <a href="#context-menu">Creating Contextual Menus</a>.</p> +</dd> + + <dt><strong>Popup menu</strong></dt> + <dd>A popup menu displays a list of items in a vertical list that's anchored to the view that +invoked the menu. It's good for providing an overflow of actions that relate to specific content or +to provide options for a second part of a command. Actions in a popup menu should +<strong>not</strong> directly affect the corresponding content—that's what contextual actions +are for. Rather, the popup menu is for extended actions that relate to regions of content in your +activity. + <p>See the section about <a href="#PopupMenu">Creating a Popup Menu</a>.</p> </dd> - <dt><strong>Submenu</strong></dt> - <dd>A floating list of menu items that appears when the user touches a menu item that contains -a nested menu.</dd> </dl> -<p>This document shows you how to create each type of menu, using XML to define the content of -the menu and callback methods in your activity to respond when the user selects an item.</p> +<h2 id="xml">Defining a Menu in XML</h2> -<h2 id="xml">Creating a Menu Resource</h2> +<p>For all menu types, Android provides a standard XML format to define menu items. +Instead of building a menu in your activity's code, you should define a menu and all its items in an +XML <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>. You can then +inflate the menu resource (load it as a {@link android.view.Menu} object) in your activity or +fragment.</p> -<p>Instead of instantiating a {@link android.view.Menu} in your application code, you should -define a menu and all its items in an XML <a -href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>, then inflate the menu -resource (load it as a programmable object) in your application code. Using a menu resource to -define your menu is a good practice because it separates the content for the menu from your -application code. It's also easier to visualize the structure and content of a menu in XML.</p> +<p>Using a menu resource is a good practice for a few reasons:</p> +<ul> + <li>It's easier to visualize the menu structure in XML.</li> + <li>It separates the content for the menu from your application's behavioral code.</li> + <li>It allows you to create alternative menu configurations for different platform versions, +screen sizes, and other configurations by leveraging the <a +href="{@docRoot}guide/topics/resources/index.html">app resources</a> framework.</li> +</ul> -<p>To create a menu resource, create an XML file inside your project's <code>res/menu/</code> +<p>To define the menu, create an XML file inside your project's <code>res/menu/</code> directory and build the menu with the following elements:</p> <dl> <dt><code><menu></code></dt> @@ -90,8 +142,8 @@ element may contain a nested <code><menu></code> element in order to create a <dt><code><group></code></dt> <dd>An optional, invisible container for {@code <item>} elements. It allows you to -categorize menu items so they share properties such as active state and visibility. See the -section about <a href="#groups">Menu groups</a>.</dd> +categorize menu items so they share properties such as active state and visibility. For more +information, see the section about <a href="#groups">Creating Menu Groups</a>.</dd> </dl> @@ -101,14 +153,17 @@ section about <a href="#groups">Menu groups</a>.</dd> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/new_game" android:icon="@drawable/ic_new_game" - android:title="@string/new_game" /> + android:title="@string/new_game" + android:showAsAction="ifRoom"/> <item android:id="@+id/help" android:icon="@drawable/ic_help" android:title="@string/help" /> </menu> </pre> -<p>This example defines a menu with two items. Each item includes the attributes:</p> +<p>The <code><item></code> element supports several attributes you can use to define an item's +appearance and behavior. The items in the above menu include the following attributes:</p> + <dl> <dt>{@code android:id}</dt> <dd>A resource ID that's unique to the item, which allows the application can recognize the item @@ -117,158 +172,175 @@ when the user selects it.</dd> <dd>A reference to a drawable to use as the item's icon.</dd> <dt>{@code android:title}</dt> <dd>A reference to a string to use as the item's title.</dd> + <dt>{@code android:showAsAction}</dt> + <dd>Specifies when and how this item should appear as an action item in the <a +href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>.</dd> </dl> -<p>There are many more attributes you can include in an {@code <item>}, including some that - specify how the item may appear in the <a -href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a>. For more information about the XML -syntax and attributes for a menu resource, see the <a -href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a> reference.</p> +<p>These are the most important attributes you should use, but there are many more available. +For information about all the supported attributes, see the <a +href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a> document.</p> - - -<h2 id="Inflating">Inflating a Menu Resource</h2> - -<p>From your application code, you can inflate a menu resource (convert the XML resource into a -programmable object) using -{@link android.view.MenuInflater#inflate(int,Menu) MenuInflater.inflate()}. For -example, the following code inflates the <code>game_menu.xml</code> file defined above, during the -{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} callback method, to -use the menu as the activity's Options Menu:</p> +<p>You can add a submenu to an item in any menu (except a submenu) by adding a {@code <menu>} +element as the child of an {@code <item>}. Submenus are useful when your application has a lot +of functions that can be organized into topics, like items in a PC application's menu bar (File, +Edit, View, etc.). For example:</p> <pre> -@Override -public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.game_menu, menu); - return true; -} +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/file" + android:title="@string/file" > + <!-- "file" submenu --> + <menu> + <item android:id="@+id/create_new" + android:title="@string/create_new" /> + <item android:id="@+id/open" + android:title="@string/open" /> + </menu> + </item> +</menu> </pre> -<p>The {@link android.app.Activity#getMenuInflater()} method returns a {@link -android.view.MenuInflater} for the activity. With this object, you can call {@link -android.view.MenuInflater#inflate(int,Menu) inflate()}, which inflates a menu resource into a -{@link android.view.Menu} object. In this example, the menu resource defined by -<code>game_menu.xml</code> -is inflated into the {@link android.view.Menu} that was passed into {@link -android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}. (This callback method for -the Options Menu is discussed more in the next section.)</p> +<p>To use the menu in your activity, you need to inflate the menu resource (convert the XML +resource into a programmable object) using {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()}. In the following sections, you'll see how to inflate a menu for each +menu type.</p> <h2 id="options-menu">Creating an Options Menu</h2> -<div class="figure" style="width:200px"> +<div class="figure" style="width:200px;margin:0"> <img src="{@docRoot}images/options_menu.png" height="333" alt="" /> - <p class="img-caption"><strong>Figure 1.</strong> Screenshot of the Options Menu in the -Browser.</p> + <p class="img-caption"><strong>Figure 1.</strong> Options menu in the +Browser, on Android 2.3.</p> </div> -<p>The Options Menu is where you should include basic activity actions and necessary navigation -items (for example, a button to open the application settings). Items in the Options Menu are -accessible in two distinct ways: the MENU button or in the <a -href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> (on devices running Android 3.0 -or higher).</p> - -<p>When running on a device with Android 2.3 and lower, the Options Menu appears at the bottom of -the screen, as shown in figure 1. When opened, the first visible portion of the Options Menu is -the icon menu. It holds the first six menu items. If you add more than six items to the -Options Menu, Android places the sixth item and those after it into the overflow menu, which the -user can open by touching the "More" menu item.</p> - -<p>On Android 3.0 and higher, items from the Options Menu is placed in the Action Bar, which appears -at the top of the activity in place of the traditional title bar. By default all items from the -Options Menu are placed in the overflow menu, which the user can open by touching the menu icon -on the right side of the Action Bar. However, you can place select menu items directly in the -Action Bar as "action items," for instant access, as shown in figure 2.</p> - -<p>When the Android system creates the Options Menu for the first time, it calls your -activity's {@link android.app.Activity#onCreateOptionsMenu(Menu) -onCreateOptionsMenu()} method. Override this method in your activity -and populate the {@link android.view.Menu} that is passed into the method, -{@link android.view.Menu} by inflating a menu resource as described above in <a -href="#Inflating">Inflating a Menu Resource</a>. For example:</p> +<p>The options menu is where you should include actions and other options that are relevant to the +current activity context, such as "Search," "Compose email," and "Settings."</p> + +<p>Where the items in your options menu appear on the screen depends on the version for which you've +developed your application:</p> + +<ul> + <li>If you've developed your application for <strong>Android 2.3.x (API level 10) or +lower</strong>, the contents of your options menu appear at the bottom of the screen when the user +presses the <em>Menu</em> button, as shown in figure 1. When opened, the first visible portion is +the icon +menu, which holds up to six menu items. If your menu includes more than six items, Android places +the sixth item and the rest into the overflow menu, which the user can open by selecting +<em>More</em>.</li> + + <li>If you've developed your application for <strong>Android 3.0 (API level 11) and +higher</strong>, items from the options menu are available in the <a +href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>. By default, the system +places all items in the action overflow, which the user can reveal with the action overflow icon on +the right side of the action bar (or by pressing the device <em>Menu</em> button, if available). To +enable +quick access to important actions, you can promote a few items to appear in the action bar by adding +{@code android:showAsAction="ifRoom"} to the corresponding {@code <item>} elements (see figure +2). <p>For more information about action items and other action bar behaviors, see the <a +href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> guide. </p> +<p class="note"><strong>Note:</strong> Even if you're <em>not</em> developing for Android 3.0 or +higher, you can build your own action bar layout for a similar effect. For an example of how you can +support older versions of Android with an action bar, see the <a +href="{@docRoot}resources/samples/ActionBarCompat/index.html">Action Bar Compatibility</a> +sample.</p> +</li> +</ul> + +<img src="{@docRoot}images/ui/actionbar.png" alt="" /> +<p class="img-caption"><strong>Figure 2.</strong> Action bar from the <a +href="{@docRoot}resources/samples/HoneycombGallery/index.html">Honeycomb Gallery</a> app, showing +navigation tabs and a camera action item (plus the action overflow button).</p> + +<p>You can declare items for the options menu from either your {@link android.app.Activity} +subclass or a {@link android.app.Fragment} subclass. If both your activity and fragment(s) +declare items for the options menu, they are combined in the UI. The activity's items appear +first, followed by those of each fragment in the order in which each fragment is added to the +activity. If necessary, you can re-order the menu items with the {@code android:orderInCategory} +attribute in each {@code <item>} you need to move.</p> + +<p>To specify the options menu for an activity, override {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} (fragments provide their +own {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} callback). In this +method, you can inflate your menu resource (<a href="#xml">defined in XML</a>) into the {@link +android.view.Menu} provided in the callback. For example:</p> <pre> @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); + MenuInflater inflater = {@link android.app.Activity#getMenuInflater()}; inflater.inflate(R.menu.game_menu, menu); return true; } </pre> -<div class="figure" style="width:450px"> -<img src="{@docRoot}images/ui/actionbar.png" alt="" /> -<p class="img-caption"><strong>Figure 2.</strong> Action bar from the <a -href="{@docRoot}resources/samples/HoneycombGallery/index.html">Honeycomb Gallery</a> app, including -navigation tabs and a camera action item (plus the overflow menu button).</p> -</div> +<p>You can also add menu items using {@link android.view.Menu#add(int,int,int,int) +add()} and retrieve items with {@link android.view.Menu#findItem findItem()} to revise their +properties with {@link android.view.MenuItem} APIs.</p> -<p>You can also populate the menu in code, using {@link android.view.Menu#add(int,int,int,int) -add()} to add items to the {@link android.view.Menu}.</p> +<p>If you've developed your application for Android 2.3.x and lower, the system calls {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} to create the options menu +when the user opens the menu for the first time. If you've developed for Android 3.0 and higher, the +system calls {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} when +starting the activity, in order to show items to the action bar.</p> -<p class="note"><strong>Note:</strong> On Android 2.3 and lower, the system calls {@link -android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} to create the Options Menu -when the user opens it for the first time, but on Android 3.0 and greater, the system creates it as -soon as the activity is created, in order to populate the Action Bar.</p> -<h3 id="RespondingOptionsMenu">Responding to user action</h3> +<h3 id="RespondingOptionsMenu">Handling click events</h3> -<p>When the user selects a menu item from the Options Menu (including action items in the -Action Bar), the system calls your activity's -{@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} -method. This method passes the -{@link android.view.MenuItem} that the user selected. You can identify the menu item by calling -{@link android.view.MenuItem#getItemId()}, which returns the unique ID for the menu -item (defined by the {@code android:id} attribute in the menu resource or with an integer -given to the {@link android.view.Menu#add(int,int,int,int) add()} method). You can match this ID -against known menu items and perform the appropriate action. For example:</p> +<p>When the user selects an item from the options menu (including action items in the action bar), +the system calls your activity's {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} method. This method passes the {@link android.view.MenuItem} selected. You +can identify the item by calling {@link android.view.MenuItem#getItemId()}, which returns the unique +ID for the menu item (defined by the {@code android:id} attribute in the menu resource or with an +integer given to the {@link android.view.Menu#add(int,int,int,int) add()} method). You can match +this ID against known menu items to perform the appropriate action. For example:</p> <pre> @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { - case R.id.new_game: - newGame(); - return true; - case R.id.help: - showHelp(); - return true; - default: - return super.onOptionsItemSelected(item); + case R.id.new_game: + newGame(); + return true; + case R.id.help: + showHelp(); + return true; + default: + return super.onOptionsItemSelected(item); } } </pre> -<p>In this example, {@link android.view.MenuItem#getItemId()} queries the ID for the selected menu -item and the switch statement compares the ID against the resource IDs that were assigned to menu -items in the XML resource. When a switch case successfully handles the menu item, it -returns {@code true} to indicate that the item selection was handled. Otherwise, the default -statement passes the menu item to the super class, in -case it can handle the item selected. (If you've directly extended the {@link android.app.Activity} -class, then the super class returns {@code false}, but it's a good practice to -pass unhandled menu items to the super class instead of directly returning {@code false}.)</p> - -<p>Additionally, Android 3.0 adds the ability for you to define the on-click behavior for a menu -item in the <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a> XML, -using the {@code android:onClick} attribute. So you don't need to implement {@link -android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}. Using the {@code -android:onClick} attribute, you can specify a method to call when the user selects the menu item. -Your activity must then implement the method specified in the {@code android:onClick} attribute so -that it accepts a single {@link android.view.MenuItem} parameter—when the system calls this -method, it passes the menu item selected.</p> +<p>When you successfully handle a menu item, return {@code true}. If you don't handle the menu +item, you should call the superclass implementation of {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} (the default +implementation returns false).</p> + +<p>If your activity includes fragments, the system first calls {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} for the activity then +for each fragment (in the order each fragment was added) until one returns +{@code true} or all fragments have been called.</p> + +<p class="note"><strong>Tip:</strong> Android 3.0 adds the ability for you to define the on-click +behavior for a menu item in XML, using the {@code android:onClick} attribute. The value for the +attribute must be the name of a method defined by the activity using the menu. The method +must be public and accept a single {@link android.view.MenuItem} parameter—when the system +calls this method, it passes the menu item selected. For more information and an example, see the <a +href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a> document.</p> <p class="note"><strong>Tip:</strong> If your application contains multiple activities and -some of them provide the same Options Menu, consider creating +some of them provide the same options menu, consider creating an activity that implements nothing except the {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} and {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} methods. Then extend this class for each activity that should share the -same Options Menu. This way, you have to manage only one set of code for handling menu -actions and each descendant class inherits the menu behaviors.<br/><br/> -If you want to add menu items to one of your descendant activities, +same options menu. This way, you can manage one set of code for handling menu +actions and each descendant class inherits the menu behaviors. +If you want to add menu items to one of the descendant activities, override {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} in that activity. Call {@code super.onCreateOptionsMenu(menu)} so the original menu items are created, then add new menu items with {@link @@ -278,180 +350,477 @@ behavior for individual menu items.</p> <h3 id="ChangingTheMenu">Changing menu items at runtime</h3> -<p>Once the activity is created, the {@link android.app.Activity#onCreateOptionsMenu(Menu) -onCreateOptionsMenu()} method is -called only once, as described above. The system keeps and re-uses the {@link -android.view.Menu} you define in this method until your activity is destroyed. If you want to change -the Options Menu any time after it's first created, you must override the -{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} method. This passes -you the {@link android.view.Menu} object as it currently exists. This is useful if you'd like to -remove, add, disable, or enable menu items depending on the current state of your application.</p> - -<p>On Android 2.3 and lower, the system calls {@link android.app.Activity#onPrepareOptionsMenu(Menu) -onPrepareOptionsMenu()} each time the user opens the Options Menu.</p> +<p>After the system calls {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}, it retains an instance of the {@link android.view.Menu} you populate and +will not call {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +again unless the menu is invalidated for some reason. However, you should use {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} only to create the initial +menu state and not to make changes during the activity lifecycle.</p> + +<p>If you want to modify the options menu based on +events that occur during the activity lifecycle, you can do so in +the {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} method. This +method passes you the {@link android.view.Menu} object as it currently exists so you can modify it, +such as add, remove, or disable items. (Fragments also provide an {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()} callback.)</p> + +<p>On Android 2.3.x and lower, the system calls {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()} each time the user opens the options menu (presses the <em>Menu</em> +button).</p> -<p>On Android 3.0 and higher, you must call {@link android.app.Activity#invalidateOptionsMenu -invalidateOptionsMenu()} when you want to update the menu, because the menu is always open. The -system will then call {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} -so you can update the menu items.</p> +<p>On Android 3.0 and higher, the options menu is considered to always be open when menu items are +presented in the action bar. When an event occurs and you want to perform a menu update, you must +call {@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()} to request that the +system call {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}.</p> <p class="note"><strong>Note:</strong> -You should never change items in the Options Menu based on the {@link android.view.View} currently +You should never change items in the options menu based on the {@link android.view.View} currently in focus. When in touch mode (when the user is not using a trackball or d-pad), views cannot take focus, so you should never use focus as the basis for modifying -items in the Options Menu. If you want to provide menu items that are context-sensitive to a {@link +items in the options menu. If you want to provide menu items that are context-sensitive to a {@link android.view.View}, use a <a href="#context-menu">Context Menu</a>.</p> -<p>If you're developing for Android 3.0 or higher, be sure to also read the <a -href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> developer guide.</p> +<h2 id="context-menu">Creating Contextual Menus</h2> -<h2 id="context-menu">Creating a Context Menu</h2> +<div class="figure" style="width:420px;margin-top:-1em"> + <img src="{@docRoot}images/ui/menu-context.png" alt="" /> + <p class="img-caption"><strong>Figure 3.</strong> Screenshots of a floating context menu (left) +and the contextual action bar (right).</p> +</div> -<p>A context menu is conceptually similar to the menu displayed when the user performs a -"right-click" on a PC. You should use a context menu to provide the user access to -actions that pertain to a specific item in the user interface. On Android, a context menu is -displayed when the user performs a "long press" (press and hold) on an item.</p> +<p>A contextual menu offers actions that affect a specific item or context frame in the UI. You +can provide a context menu for any view, but they are most often used for items in a {@link +android.widget.ListView}, {@link android.widget.GridView}, or other view collections in which +the user can perform direct actions on each item.</p> -<p>You can create a context menu for any View, though context menus are most often used for items in -a {@link android.widget.ListView}. When the user performs a long-press on an item in a ListView and -the list is registered to provide a context menu, the list item signals to the user that a context -menu is available by animating its background color—it transitions from -orange to white before opening the context menu. (The Contacts application demonstrates this -feature.)</p> +<p>There are two ways to provide contextual actions:</p> +<ul> + <li>In a <a href="#FloatingContextMenu">floating context menu</a>. A menu appears as a +floating list of menu items (similar to a dialog) when the user performs a long-click (press and +hold) on a view that declares support for a context menu. Users can perform a contextual +action on one item at a time.</li> + + <li>In the <a href="#CAB">contextual action mode</a>. This mode is a system implementation of +{@link android.view.ActionMode} that displays a <em>contextual action bar</em> at the top of the +screen with action items that affect the selected item(s). When this mode is active, users +can perform an action on multiple items at once (if your app allows it).</li> +</ul> -<div class="sidebox-wrapper"> -<div class="sidebox"> -<h3>Register a ListView</h3> -<p>If your activity uses a {@link android.widget.ListView} and -you want all list items to provide a context menu, register all items for a context -menu by passing the {@link android.widget.ListView} to {@link -android.app.Activity#registerForContextMenu(View) registerForContextMenu()}. For -example, if you're using a {@link android.app.ListActivity}, register all list items like this:</p> -<p><code>registerForContextMenu({@link android.app.ListActivity#getListView()});</code></p> -</div> -</div> +<p class="note"><strong>Note:</strong> The contextual action mode is available on Android 3.0 (API +level 11) and higher and is the preferred technique for displaying contextual actions when +available. If your app supports versions lower than 3.0 then you should fall back to a floating +context menu on those devices.</p> -<p>In order for a View to provide a context menu, you must "register" the view for a context -menu. Call {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} and -pass it the {@link android.view.View} you want to give a context menu. When this View then -receives a long-press, it displays a context menu.</p> -<p>To define the context menu's appearance and behavior, override your activity's context menu -callback methods, {@link android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) -onCreateContextMenu()} and -{@link android.app.Activity#onContextItemSelected(MenuItem) onContextItemSelected()}.</p> +<h3 id="FloatingContextMenu">Creating a floating context menu</h3> -<p>For example, here's an {@link -android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) -onCreateContextMenu()} that uses the {@code context_menu.xml} menu resource:</p> +<p>To provide a floating context menu:</p> +<ol> + <li>Register the {@link android.view.View} to which the context menu should be associated by +calling {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} and pass +it the {@link android.view.View}. + <p>If your activity uses a {@link android.widget.ListView} or {@link android.widget.GridView} and +you want each item to provide the same context menu, register all items for a context menu by +passing the {@link android.widget.ListView} or {@link android.widget.GridView} to {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()}.</p> +</li> + + <li>Implement the {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} method +in your {@link android.app.Activity} or {@link android.app.Fragment}. + <p>When the registered view receives a long-click event, the system calls your {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +method. This is where you define the menu items, usually by inflating a menu resource. For +example:</p> <pre> @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.context_menu, menu); + super.onCreateContextMenu(menu, v, menuInfo); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.context_menu, menu); } </pre> -<p>{@link android.view.MenuInflater} is used to inflate the context menu from a <a -href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>. (You can also use -{@link android.view.Menu#add(int,int,int,int) add()} to add menu items.) The callback method +<p>{@link android.view.MenuInflater} allows you to inflate the context menu from a <a +href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>. The callback method parameters include the {@link android.view.View} that the user selected and a {@link android.view.ContextMenu.ContextMenuInfo} object that provides -additional information about the item selected. You might use these parameters to determine -which context menu should be created, but in this example, all context menus for the activity are -the same.</p> +additional information about the item selected. If your activity has several views that each provide +a different context menu, you might use these parameters to determine which context menu to +inflate.</p> +</li> -<p>Then when the user selects an item from the context menu, the system calls {@link -android.app.Activity#onContextItemSelected(MenuItem) onContextItemSelected()}. Here is an example -of how you can handle selected items:</p> +<li>Implement {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}. + <p>When the user selects a menu item, the system calls this method so you can perform the +appropriate action. For example:</p> <pre> @Override public boolean onContextItemSelected(MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - switch (item.getItemId()) { - case R.id.edit: - editNote(info.id); - return true; - case R.id.delete: - deleteNote(info.id); - return true; - default: - return super.onContextItemSelected(item); - } + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + switch (item.getItemId()) { + case R.id.edit: + editNote(info.id); + return true; + case R.id.delete: + deleteNote(info.id); + return true; + default: + return super.onContextItemSelected(item); + } } </pre> -<p>The structure of this code is similar to the example for <a href="#options-menu">Creating an -Options Menu</a>, in which {@link android.view.MenuItem#getItemId()} queries the ID for the selected -menu item and a switch statement matches the item to the IDs that are defined in the menu resource. -And like the options menu example, the default statement calls the super class in case it -can handle menu items not handled here, if necessary.</p> +<p>The {@link android.view.MenuItem#getItemId()} method queries the ID for +the selected menu item, which you should assign to each menu item in XML using the {@code +android:id} attribute, as shown in the section about <a href="#xml">Defining a Menu in +XML</a>.</p> + +<p>When you successfully handle a menu item, return {@code true}. If you don't handle the menu item, +you should pass the menu item to the superclass implementation. If your activity includes fragments, +the activity receives this callback first. By calling the superclass when unhandled, the system +passes the event to the respective callback method in each fragment, one at a time (in the order +each fragment was added) until {@code true} or {@code false} is returned. (The default +implementation for {@link android.app.Activity} and {@code android.app.Fragment} return {@code +false}, so you should always call the superclass when unhandled.)</p> +</li> +</ol> + + +<h3 id="CAB">Using the contextual action mode</h3> -<p>In this example, the selected item is an item from a {@link android.widget.ListView}. To -perform an action on the selected item, the application needs to know the list -ID for the selected item (it's position in the ListView). To get the ID, the application calls -{@link android.view.MenuItem#getMenuInfo()}, which returns a {@link -android.widget.AdapterView.AdapterContextMenuInfo} object that includes the list ID for the -selected item in the {@link android.widget.AdapterView.AdapterContextMenuInfo#id id} field. The -local methods <code>editNote()</code> and <code>deleteNote()</code> methods accept this list ID to -perform an action on the data specified by the list ID.</p> +<p>The contextual action mode is a system implementation of {@link android.view.ActionMode} that +focuses user interaction toward performing contextual actions. When a +user enables this mode by selecting an item, a <em>contextual action bar</em> appears at the top of +the screen to present actions the user can perform on the currently selected item(s). While this +mode is enabled, the user can select multiple items (if you allow it), deselect items, and continue +to navigate within the activity (as much as you're willing to allow). The action mode is disabled +and the contextual action bar disappears when the user deselects all items, presses the BACK button, +or selects the <em>Done</em> action on the left side of the bar.</p> -<p class="note"><strong>Note:</strong> Items in a context menu do not support icons or shortcut -keys.</p> +<p class="note"><strong>Note:</strong> The contextual action bar is not necessarily +associated with the <a href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>. They operate +independently, even though the contextual action bar visually overtakes the action bar +position.</p> +<p>If you're developing for Android 3.0 (API level 11) or higher, you +should usually use the contextual action mode to present contextual actions, instead of the <a +href="#FloatingContextMenu">floating context menu</a>.</p> +<p>For views that provide contextual actions, you should usually invoke the contextual action mode +upon one of two events (or both):</p> +<ul> + <li>The user performs a long-click on the view.</li> + <li>The user selects a checkbox or similar UI component within the view.</li> +</ul> + +<p>How your application invokes the contextual action mode and defines the behavior for each +action depends on your design. There are basically two designs:</p> +<ul> + <li>For contextual actions on individual, arbitrary views.</li> + <li>For batch contextual actions on groups of items in a {@link +android.widget.ListView} or {@link android.widget.GridView} (allowing the user to select multiple +items and perform an action on them all).</li> +</ul> -<h2 id="submenu">Creating Submenus</h2> +<p>The following sections describe the setup required for each scenario.</p> -<p>A submenu is a menu that the user can open by selecting an item in another menu. You can add a -submenu to any menu (except a submenu). Submenus are useful when your application has a lot of -functions that can be organized into topics, like items in a PC application's menu bar (File, Edit, -View, etc.).</p> -<p>When creating your <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu -resource</a>, you can create a submenu by adding a {@code <menu>} element as the child of an -{@code <item>}. For example:</p> +<h4 id="CABforViews">Enabling the contextual action mode for individual views</h4> +<p>If you want to invoke the contextual action mode only when the user selects specific +views, you should:</p> +<ol> + <li>Implement the {@link android.view.ActionMode.Callback} interface. In its callback methods, you +can specify the actions for the contextual action bar, respond to click events on action items, and +handle other lifecycle events for the action mode.</li> + <li>Call {@link android.app.Activity#startActionMode startActionMode()} when you want to show the +bar (such as when the user long-clicks the view).</li> +</ol> + +<p>For example:</p> + +<ol> + <li>Implement the {@link android.view.ActionMode.Callback ActionMode.Callback} interface: <pre> -<?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:id="@+id/file" - android:icon="@drawable/file" - android:title="@string/file" > - <!-- "file" submenu --> - <menu> - <item android:id="@+id/create_new" - android:title="@string/create_new" /> - <item android:id="@+id/open" - android:title="@string/open" /> - </menu> - </item> -</menu> +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { + + // Called when the action mode is created; startActionMode() was called + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate a menu resource providing context menu items + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.context_menu, menu); + return true; + } + + // Called each time the action mode is shown. Always called after onCreateActionMode, but + // may be called multiple times if the mode is invalidated. + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; // Return false if nothing is done + } + + // Called when the user selects a contextual menu item + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + shareCurrentItem(); + mode.finish(); // Action picked, so close the CAB + return true; + default: + return false; + } + } + + // Called when the user exits the action mode + @Override + public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; + } +}; +</pre> + +<p>Notice that these event callbacks are almost exactly the same as the callbacks for the <a +href="#options-menu">options menu</a>, except each of these also pass the {@link +android.view.ActionMode} object associated with the event. You can use {@link +android.view.ActionMode} APIs to make various changes to the CAB, such as revise the title and +subtitle with {@link android.view.ActionMode#setTitle setTitle()} and {@link +android.view.ActionMode#setSubtitle setSubtitle()} (useful to indicate how many items are +selected).</p> + +<p>Also notice that the above sample sets the {@code mActionMode} variable null when the +action mode is destroyed. In the next step, you'll see how it's initialized and how saving +the member variable in your activity or fragment can be useful.</p> +</li> + + <li>Call {@link android.app.Activity#startActionMode startActionMode()} to enable the contextual +action mode when appropriate, such as in response to a long-click on a {@link +android.view.View}:</p> + +<pre> +someView.setOnLongClickListener(new View.OnLongClickListener() { + // Called when the user long-clicks on someView + public boolean onLongClick(View view) { + if (mActionMode != null) { + return false; + } + + // Start the CAB using the ActionMode.Callback defined above + mActionMode = getActivity().startActionMode(mActionModeCallback); + view.setSelected(true); + return true; + } +}); +</pre> + +<p>When you call {@link android.app.Activity#startActionMode startActionMode()}, the system returns +the {@link android.view.ActionMode} created. By saving this in a member variable, you can +make changes to the contextual action bar in response to other events. In the above sample, the +{@link android.view.ActionMode} is used to ensure that the {@link android.view.ActionMode} instance +is not recreated if it's already active, by checking whether the member is null before starting the +action mode.</p> +</li> +</ol> + + + +<h4 id="CABforListView">Enabling batch contextual actions in a ListView or GridView</h4> + +<p>If you have a collection of items in a {@link android.widget.ListView} or {@link +android.widget.GridView} (or another extension of {@link android.widget.AbsListView}) and want to +allow users to perform batch actions, you should:</p> + +<ul> + <li>Implement the {@link android.widget.AbsListView.MultiChoiceModeListener} interface and set it +for the view group with {@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()}. In the listener's callback methods, you can specify the actions +for the contextual action bar, respond to click events on action items, and handle other callbacks +inherited from the {@link android.view.ActionMode.Callback} interface.</li> + + <li>Call {@link android.widget.AbsListView#setChoiceMode setChoiceMode()} with the {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL} argument.</li> +</ul> + +<p>For example:</p> + +<pre> +ListView listView = getListView(); +listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); +listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, + long id, boolean checked) { + // Here you can do something when items are selected/de-selected, + // such as update the title in the CAB + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // Respond to clicks on the actions in the CAB + switch (item.getItemId()) { + case R.id.menu_delete: + deleteSelectedItems(); + mode.finish(); // Action picked, so close the CAB + return true; + default: + return false; + } + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate the menu for the CAB + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.context, menu); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // Here you can make any necessary updates to the activity when + // the CAB is removed. By default, selected items are deselected/unchecked. + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // Here you can perform updates to the CAB due to + // an {@link android.view.ActionMode#invalidate} request + return false; + } +}); +</pre> + +<p>That's it. Now when the user selects an item with a long-click, the system calls the {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +method and displays the contextual action bar with the specified actions. While the contextual +action bar is visible, users can select additional items.</p> + +<p>In some cases in which the contextual actions provide common action items, you might +want to add a checkbox or a similar UI element that allows users to select items, because they +might not discover the long-click behavior. When a user selects the checkbox, you +can invoke the contextual action mode by setting the respective list item to the checked +state with {@link android.widget.AbsListView#setItemChecked setItemChecked()}.</p> + + + + +<h2 id="PopupMenu">Creating a Popup Menu</h2> + +<div class="figure" style="width:220px"> +<img src="{@docRoot}images/ui/popupmenu.png" alt="" /> +<p><strong>Figure 4.</strong> A popup menu in the Gmail app, anchored to the overflow +button at the top-right.</p> +</div> + +<p>A {@link android.widget.PopupMenu} is a modal menu anchored to a {@link android.view.View}. +It appears below the anchor view if there is room, or above the view otherwise. It's useful for:</p> +<ul> + <li>Providing an overflow-style menu for actions that <em>relate to</em> specific content (such as +Gmail's email headers, shown in figure 4). + <p class="note"><strong>Note:</strong> This is not the same as a context menu, which is +generally for actions that <em>affect</em> selected content. For actions that affect selected +content, use the <a href="#CAB">contextual action mode</a> or <a +href="#FloatingContextMenu">floating context menu</a>.</p></li> + <li>Providing a second part of a command sentence (such as a button marked "Add" +that produces a popup menu with different "Add" options).</li> + <li>Providing a drop-down similar to {@link android.widget.Spinner} that does not retain +a persistent selection.</li> +</ul> + + +<p class="note"><strong>Note:</strong> {@link android.widget.PopupMenu} is available with API +level 11 and higher.</p> + +<p>If you <a href="#xml">define your menu in XML</a>, here's how you can show the popup menu:</p> +<ol> + <li>Instantate a {@link android.widget.PopupMenu} with its constructor, which takes the +current application {@link android.content.Context} and the {@link android.view.View} to which the +menu should be anchored.</li> + <li>Use {@link android.view.MenuInflater} to inflate your menu resource into the {@link +android.view.Menu} object returned by {@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()}. On API level 14 and above, you can use +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()} instead.</li> + <li>Call {@link android.widget.PopupMenu#show() PopupMenu.show()}.</li> +</ol> + +<p>For example, here's a button with the {@link android.R.attr#onClick android:onClick} attribute +that shows a popup menu:</p> + +<pre> +<ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_overflow_holo_dark" + android:contentDescription="@string/descr_overflow_button" + android:onClick="showPopup" /> +</pre> + +<p>The activity can then show the popup menu like this:</p> + +<pre> +public void showPopup(View v) { + PopupMenu popup = new PopupMenu(this, v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.actions, popup.getMenu()); + popup.show(); +} </pre> -<p>When the user selects an item from a submenu, the parent menu's respective on-item-selected -callback method receives the event. For instance, if the above menu is applied as an Options Menu, -then the {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} method -is called when a submenu item is selected.</p> +<p>In API level 14 and higher, you can combine the two lines that inflate the menu with {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()}.</p> + +<p>The menu is dismissed when the user selects an item or touches outside the menu +area. You can listen for the dismiss event using {@link +android.widget.PopupMenu.OnDismissListener}.</p> + +<h3 id="PopupEvents">Handling click events</h3> -<p>You can also use {@link android.view.Menu#addSubMenu(int,int,int,int) addSubMenu()} to -dynamically add a {@link android.view.SubMenu} to an existing {@link android.view.Menu}. This -returns the new {@link android.view.SubMenu} object, to which you can add -submenu items, using {@link android.view.Menu#add(int,int,int,int) add()}</p> +<p>To perform an +action when the user selects a menu item, you must implement the {@link +android.widget.PopupMenu.OnMenuItemClickListener} interface and register it with your {@link +android.widget.PopupMenu} by calling {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()}. When the user selects an item, the system calls the {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} callback in +your interface.</p> +<p>For example:</p> +<pre> +public void showMenu(View v) { + PopupMenu popup = new PopupMenu(this, v); -<h2 id="features">Other Menu Features</h2> + // This activity implements OnMenuItemClickListener + popup.setOnMenuItemClickListener(this); + popup.inflate(R.menu.actions); + popup.show(); +} + +@Override +public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.archive: + archive(item); + return true; + case R.id.delete: + delete(item); + return true; + default: + return false; + } +} +</pre> -<p>Here are some other features that you can apply to most menu items.</p> -<h3 id="groups">Menu groups</h3> +<h2 id="groups">Creating Menu Groups</h2> <p>A menu group is a collection of menu items that share certain traits. With a group, you can:</p> @@ -473,38 +842,41 @@ android.view.Menu#add(int,int,int,int) add()} method.</p> <pre> <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:id="@+id/item1" - android:icon="@drawable/item1" - android:title="@string/item1" /> + <item android:id="@+id/menu_save" + android:icon="@drawable/menu_save" + android:title="@string/menu_save" /> <!-- menu group --> - <group android:id="@+id/group1"> - <item android:id="@+id/groupItem1" - android:title="@string/groupItem1" /> - <item android:id="@+id/groupItem2" - android:title="@string/groupItem2" /> + <group android:id="@+id/group_delete"> + <item android:id="@+id/menu_archive" + android:title="@string/menu_archive" /> + <item android:id="@+id/menu_delete" + android:title="@string/menu_delete" /> </group> </menu> </pre> -<p>The items that are in the group appear the same as the first item that is not in a -group—all three items in the menu are siblings. However, you can modify the traits of the two -items in the group by referencing the group ID and using the methods listed above.</p> +<p>The items that are in the group appear at the same level as the first item—all three items +in the menu are siblings. However, you can modify the traits of the two +items in the group by referencing the group ID and using the methods listed above. The system +will also never separate grouped items. For example, if you declare {@code +android:showAsAction="ifRoom"} for each item, they will either both appear in the action +bar or both appear in the action overflow.</p> -<h3 id="checkable">Checkable menu items</h3> +<h3 id="checkable">Using checkable menu items</h3> <div class="figure" style="width:200px"> <img src="{@docRoot}images/radio_buttons.png" height="333" alt="" /> - <p class="img-caption"><strong>Figure 3.</strong> Screenshot of a submenu with checkable + <p class="img-caption"><strong>Figure 5.</strong> Screenshot of a submenu with checkable items.</p> </div> <p>A menu can be useful as an interface for turning options on and off, using a checkbox for stand-alone options, or radio buttons for groups of -mutually exclusive options. Figure 2 shows a submenu with items that are checkable with radio +mutually exclusive options. Figure 5 shows a submenu with items that are checkable with radio buttons.</p> -<p class="note"><strong>Note:</strong> Menu items in the Icon Menu (from the Options Menu) cannot +<p class="note"><strong>Note:</strong> Menu items in the Icon Menu (from the options menu) cannot display a checkbox or radio button. If you choose to make items in the Icon Menu checkable, you must manually indicate the checked state by swapping the icon and/or text each time the state changes.</p> @@ -550,15 +922,15 @@ user selected it) with {@link android.view.MenuItem#isChecked()} and then set th <pre> @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.vibrate: - case R.id.dont_vibrate: - if (item.isChecked()) item.setChecked(false); - else item.setChecked(true); - return true; - default: - return super.onOptionsItemSelected(item); - } + switch (item.getItemId()) { + case R.id.vibrate: + case R.id.dont_vibrate: + if (item.isChecked()) item.setChecked(false); + else item.setChecked(true); + return true; + default: + return super.onOptionsItemSelected(item); + } } </pre> @@ -575,30 +947,8 @@ you should store the data using <a href="{@docRoot}guide/topics/data/data-storage.html#pref">Shared Preferences</a>.</p> -<h3 id="shortcuts">Shortcut keys</h3> - -<p>To facilitate quick access to items in the Options Menu when the user's device has a hardware -keyboard, you can add quick-access shortcut keys using letters and/or numbers, with the -{@code android:alphabeticShortcut} and {@code android:numericShortcut} attributes in the {@code -<item>} element. You can also use the methods {@link -android.view.MenuItem#setAlphabeticShortcut(char)} and {@link -android.view.MenuItem#setNumericShortcut(char)}. Shortcut keys are <em>not</em> -case sensitive.</p> - -<p>For example, if you apply the "s" character as an alphabetic shortcut to a "save" menu item, then -when the menu is open (or while the user holds the MENU button) and the user presses the "s" key, -the "save" menu item is selected.</p> - -<p>This shortcut key is displayed as a tip in the menu item, below the menu item name -(except for items in the Icon Menu, which are displayed only if the user holds the MENU -button).</p> - -<p class="note"><strong>Note:</strong> Shortcut keys for menu items only work on devices with a -hardware keyboard. Shortcuts cannot be added to items in a Context Menu.</p> - - -<h3 id="intents">Dynamically adding menu intents</h3> +<h2 id="intents">Adding Menu Items Based on an Intent</h2> <p>Sometimes you'll want a menu item to launch an activity using an {@link android.content.Intent} (whether it's an activity in your application or another application). When you know the intent you @@ -671,7 +1021,7 @@ addIntentOptions()}, it overrides any and all menu items by the menu group speci argument.</p> -<h4>Allowing your activity to be added to other menus</h4> +<h3 id="AllowingToAdd">Allowing your activity to be added to other menus</h3> <p>You can also offer the services of your activity to other applications, so your application can be included in the menu of others (reverse the roles described above).</p> @@ -681,7 +1031,7 @@ filter as usual, but be sure to include the {@link android.content.Intent#CATEGO and/or {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} values for the intent filter category. For example:</p> <pre> -<intent-filter label="Resize Image"> +<intent-filter label="@string/resize_image"> ... <category android:name="android.intent.category.ALTERNATIVE" /> <category android:name="android.intent.category.SELECTED_ALTERNATIVE" /> diff --git a/docs/html/guide/topics/ui/notifiers/notifications.jd b/docs/html/guide/topics/ui/notifiers/notifications.jd index 33b0fecc9ed5..d104b4bccd14 100644 --- a/docs/html/guide/topics/ui/notifiers/notifications.jd +++ b/docs/html/guide/topics/ui/notifiers/notifications.jd @@ -173,7 +173,7 @@ in these two typical notification flows, first handling a Calendar notification: </li> <li> The user has seen enough to know they have a meeting coming up, - so they press the BACK button. They are now returned to Email, which + so they press the <em>Back</em> button. They are now returned to Email, which is where they were when they took the notification. </li> </ol> @@ -198,8 +198,8 @@ in these two typical notification flows, first handling a Calendar notification: (writing an e-mail), but that message is still saved in their drafts. </li> <li> - The user presses BACK once to go to the message list (the typical flow in the - Email app), and press BACK again to return to Calendar as they left it. + The user presses <em>Back</em> once to go to the message list (the typical flow in the + Email app), and press <em>Back</em> again to return to Calendar as they left it. </li> </ol> @@ -216,7 +216,7 @@ new notification state.</p> most interest is the <code>makeMessageIntentStack()</code> method, which constructs an array of intents representing the app's new activity stack for this state. (If you are using fragments, you may need to initialize your fragment and -app state so that pressing BACK will switch the UI back to its parent state.) +app state so that pressing <em>Back</em> will switch the UI back to its parent state.) The core of this is the {@link android.content.Intent#makeRestartActivityTask Intent.makeRestartActivityTask()} method, which constructs the root activity of the stack with the appropriate flags, such as @@ -245,31 +245,27 @@ an application to be part of that application's UI flow, so simply launching the activity like this can cause it to be mixed with your normal application back stack in undesired ways. To make it behave correctly, in the manifest declaration for the activity the attributes -<code>android:launchMode="singleInstance"</code> and +<code>android:launchMode="singleTask"</code>, +<code>android:taskAffinity=""</code> and <code>android:excludeFromRecents="true"</code> must be set. The full activity declaration for this sample is:</p> {@sample development/samples/ApiDemos/AndroidManifest.xml interstitial_affinity} -<p>Because of the use of <code>singleInstance</code>, you must be careful about launching -any other activities from this one. These activities will be launched -in their own task, and care must be taken to make sure this interacts -well with the current state of your application's task. This is essentially +<p>You must be careful when launching other activities from this initial activity, +because this is not a top-level part of the application, does not appear in +recents, and needs to be relaunched at any point from the notification with new data +to show. This best approach is to make sure any activity launched from it is +launched in its own task. When doing this care must be taken to make sure this +new task interacts well with the current state of your exiting application's +task. This is essentially the same as switching to the main application as described for the Email style notification shown before. Given the <code>makeMessageIntentStack()</code> -method previously shown, handling a click here would look something like this:</p> +method previously shown, handling a click then would look something like this:</p> {@sample development/samples/ApiDemos/src/com/example/android/apis/app/IncomingMessageInterstitial.java app_launch} -<p>If you don't want to use the <code>singleInstance</code> launch mode for -this activity, an alternative approach is to use <code>android:taskAffinity=""</code>. -This tells Android that the activity should not be treated as part of the -main application flow, so it will not get mixed together with that. All of the -other issues discussed here do still apply, though this would allow you to start -additional activities that are part of this notification task instead of switching -to and replacing the main application task.</p> - <h2 id="ManageYourNotifications">Managing your Notifications</h2> <p>The {@link android.app.NotificationManager} is a system service that manages all diff --git a/docs/html/guide/webapps/webview.jd b/docs/html/guide/webapps/webview.jd index ed28f2117281..66b550168820 100644 --- a/docs/html/guide/webapps/webview.jd +++ b/docs/html/guide/webapps/webview.jd @@ -298,18 +298,18 @@ history of visited web pages. You can navigate backward and forward through the history with {@link android.webkit.WebView#goBack()} and {@link android.webkit.WebView#goForward()}.</p> -<p>For example, here's how your {@link android.app.Activity} can use the device BACK key to navigate -backward:</p> +<p>For example, here's how your {@link android.app.Activity} can use the device <em>Back</em> button +to navigate backward:</p> <pre> @Override public boolean {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown}(int keyCode, KeyEvent event) { - // Check if the key event was the BACK key and if there's history + // Check if the key event was the Back button and if there's history if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.{@link android.webkit.WebView#canGoBack() canGoBack}() { myWebView.{@link android.webkit.WebView#goBack() goBack}(); return true; } - // If it wasn't the BACK key or there's no web page history, bubble up to the default + // If it wasn't the Back key or there's no web page history, bubble up to the default // system behavior (probably exit the activity) return super.onKeyDown(keyCode, event); } diff --git a/docs/html/images/ui/menu-context.png b/docs/html/images/ui/menu-context.png Binary files differnew file mode 100644 index 000000000000..f6975fbf2df4 --- /dev/null +++ b/docs/html/images/ui/menu-context.png diff --git a/docs/html/images/ui/popupmenu.png b/docs/html/images/ui/popupmenu.png Binary files differnew file mode 100644 index 000000000000..5c9982170461 --- /dev/null +++ b/docs/html/images/ui/popupmenu.png diff --git a/docs/html/index.jd b/docs/html/index.jd index 9197b5d202a3..b9d67584aa74 100644 --- a/docs/html/index.jd +++ b/docs/html/index.jd @@ -1,4 +1,5 @@ home=true +page.metaDescription=The official site for Android developers. Provides the Android SDK and documentation for app developers and designers. @jd:body @@ -142,6 +143,20 @@ href="{@docRoot}resources/dashboard/platform-versions.html">Learn more »</ + "href='{@docRoot}sdk/api_diff/15/changes.html'>diff report</a>. If you're new to Android, " + "get started with the <a href='/sdk/index.html'>SDK starter package</a>.</p>" }, + + 'plus': { + 'layout':"imgLeft", + 'icon':"google-plus-small.png", + 'name':"Google+ Page", + 'img':"google-plus.png", + 'title':"Android Developers on Google+", + 'desc': "<p>We now have a Google+ page for <a " ++ "href='https://plus.google.com/108967384991768947849'>+Android Developers</a>. " ++ "We'll use it to host Hangouts for developers, talk about the latest releases, " ++ "development and design tips, and much more.</p>" ++ "<div style='margin:.7em 0 0 -1.2em'><g:plus href='https://plus.google.com/108967384991768947849' " ++ "size=\"smallbadge\" width=\"275\"></g:plus></div>" + }, 'tv': { 'layout':"imgLeft", @@ -186,5 +201,16 @@ href="{@docRoot}resources/dashboard/platform-versions.html">Learn more »</ </script> <script type="text/javascript" src="{@docRoot}assets/carousel.js"></script> <script type="text/javascript"> - initCarousel("sdk"); + initCarousel("plus"); </script> + +<script type="text/javascript" src="https://plus.google.com/108967384991768947849" +rel="publisher"></script> +<script type="text/javascript"> +window.___gcfg = {lang: 'en'}; +(function() +{var po = document.createElement("script"); +po.type = "text/javascript"; po.async = true;po.src = "https://apis.google.com/js/plusone.js"; +var s = document.getElementsByTagName("script")[0]; +s.parentNode.insertBefore(po, s); +})();</script> diff --git a/docs/html/resources/community-more.jd b/docs/html/resources/community-more.jd index df729265cc05..3089d45aa9b5 100644 --- a/docs/html/resources/community-more.jd +++ b/docs/html/resources/community-more.jd @@ -1,5 +1,5 @@ community=true -page.title=IRC and Twitter +page.title=IRC, G+, Twitter @jd:body <p>In addition to the <a href="community-groups.html">Android developer forums</a>, you can participate in the Android developer community through IRC and you can follow us on Twitter. </p> @@ -45,8 +45,27 @@ does not require any installation, to join discussions on the Android IRC channe </li> </ul> + +<h3 id="gplus">Google+</h3> +<p>We use a Google+ page to host Hangouts for developers, talk about the latest +releases, development and design tips, and much more.</p> + +<div style='margin-top:1em'><g:plus href='https://plus.google.com/108967384991768947849' +size='badge'></g:plus></div> + + <h3 id="twitter">Twitter</h3> <p>You can follow us on Twitter at this account:</p> <p style="margin-left:2em;"><a href="http://twitter.com/androiddev">http://twitter.com/androiddev</a></p> +<script type="text/javascript" src="https://plus.google.com/108967384991768947849" +rel="publisher"></script> +<script type="text/javascript"> +window.___gcfg = {lang: 'en'}; +(function() +{var po = document.createElement("script"); +po.type = "text/javascript"; po.async = true;po.src = "https://apis.google.com/js/plusone.js"; +var s = document.getElementsByTagName("script")[0]; +s.parentNode.insertBefore(po, s); +})();</script>
\ No newline at end of file diff --git a/docs/html/resources/dashboard/opengl.jd b/docs/html/resources/dashboard/opengl.jd index 357c1ea4b797..d55ab2b54935 100644 --- a/docs/html/resources/dashboard/opengl.jd +++ b/docs/html/resources/dashboard/opengl.jd @@ -57,7 +57,7 @@ ending on the data collection date noted below.</p> <div class="dashboard-panel"> <img alt="" width="400" height="250" -src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.5,90.5" /> +src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1%20only|GL%202.0%20%26%201.1&chd=t%3A10.7,89.3" /> <table> <tr> @@ -65,15 +65,15 @@ src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl= <th scope="col">Distribution</th> </tr> <tr> -<td>1.1</th> -<td>9.5%</td> +<td>1.1 only</th> +<td>10.7%</td> </tr> <tr> -<td>2.0</th> -<td>90.5%</td> +<td>2.0 & 1.1</th> +<td>89.3%</td> </tr> </table> -<p><em>Data collected during a 7-day period ending on January 3, 2012</em></p> +<p><em>Data collected during a 7-day period ending on February 1, 2012</em></p> </div> diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd index 2618a0424b49..4ea52aff393d 100644 --- a/docs/html/resources/dashboard/platform-versions.jd +++ b/docs/html/resources/dashboard/platform-versions.jd @@ -52,7 +52,7 @@ Android Market within a 14-day period ending on the data collection date noted b <div class="dashboard-panel"> <img alt="" height="250" width="470" -src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.6,1.1,8.5,30.4,0.6,54.9,0.1,1.5,1.7,0.3,0.3&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2|Android%204.0|Android%204.0.3&chco=c4df9b,6fad0c" /> +src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.6,1.0,7.6,27.8,0.5,58.1,0.1,1.4,1.9,0.3,0.7&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2|Android%204.0|Android%204.0.3&chco=c4df9b,6fad0c" /> <table> <tr> @@ -62,24 +62,24 @@ src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.6,1.1,8.5,30. <th>Distribution</th> </tr> <tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td> <td>3</td><td>0.6%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>1.1%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>8.5%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>30.4%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>1.0%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>7.6%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>27.8%</td></tr> <tr><td><a href="{@docRoot}sdk/android-2.3.html">Android 2.3 -<br/> - Android 2.3.2</a></td><td rowspan="2">Gingerbread</td> <td>9</td><td>0.6%</td></tr> + Android 2.3.2</a></td><td rowspan="2">Gingerbread</td> <td>9</td><td>0.5%</td></tr> <tr><td><a href="{@docRoot}sdk/android-2.3.3.html">Android 2.3.3 -<br/> - Android 2.3.7</a></td><!-- Gingerbread --> <td>10</td><td>54.9%</td></tr> + Android 2.3.7</a></td><!-- Gingerbread --> <td>10</td><td>58.1%</td></tr> <tr><td><a href="{@docRoot}sdk/android-3.0.html">Android 3.0</a></td> <td rowspan="3">Honeycomb</td> <td>11</td><td>0.1%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>1.5%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>1.7%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>1.4%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>1.9%</td></tr> <tr><td><a href="{@docRoot}sdk/android-4.0.html">Android 4.0 -<br/> Android 4.0.2</a></td> <td rowspan="2">Ice Cream Sandwich</td><td>14</td><td>0.3%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-4.0.3.html">Android 4.0.3</a></td><!-- ICS --><td>15</td><td>0.3%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-4.0.3.html">Android 4.0.3</a></td><!-- ICS --><td>15</td><td>0.7%</td></tr> </table> -<p><em>Data collected during a 14-day period ending on January 3, 2012</em></p> +<p><em>Data collected during a 14-day period ending on February 1, 2012</em></p> <!-- <p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p> --> @@ -108,9 +108,9 @@ Android Market within a 14-day period ending on the date indicated on the x-axis <div class="dashboard-panel"> <img alt="" height="250" width="660" style="padding:5px;background:#fff" -src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2012%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.2,99.0,98.8,98.7,98.5,98.5,98.2,98.1,98.0,99.9,99.9,99.7,99.2|97.7,97.6,97.5,97.5,97.5,97.5,97.1,97.1,97.0,99.1,99.1,99.0,98.6|95.5,95.5,95.5,95.6,95.7,95.8,95.6,95.9,95.7,97.7,97.8,97.8,97.5|77.6,79.0,80.2,81.1,82.4,83.3,83.8,84.9,85.1,87.5,88.2,88.6,89.0|17.8,20.6,24.3,27.5,31.2,34.7,38.3,41.3,44.0,48.9,52.9,55.7,58.5|16.8,20.0,23.7,26.9,30.6,34.1,37.8,40.8,43.5,48.4,52.4,55.2,57.9|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0,2.3,2.6,3.2|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.2,1.3,1.7&chm=b,c3df9b,0,1,0|b,b8dc82,1,2,0|tAndroid 2.1,608920,2,0,15,,t::-5|b,addb67,2,3,0|tAndroid 2.2,517617,3,0,15,,t::-5|b,a3db4b,3,4,0|b,98dc2e,4,5,0|tAndroid 2.3.3,334d0a,5,0,15,,t::-5|b,8cd41b,5,6,0|b,7ec113,6,7,0|B,6fad0c,7,8,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3|Android 3.1|Android 3.2&chco=add274,a2d15a,97d13e,8bcb28,7dba1e,6ea715,5f920e,507d08" /> +src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C01/15%7C02/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2012%7C%7C2012%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:98.2,98.1,97.9,97.9,97.7,97.6,97.5,99.4,99.4,99.2,98.6,98.4,98.5|96.9,96.9,96.9,96.9,96.6,96.6,96.5,98.6,98.6,98.5,98.0,97.8,97.9|94.9,95.0,95.1,95.2,95.1,95.4,95.2,97.2,97.3,97.3,96.9,96.8,96.9|79.6,80.5,81.8,82.7,83.3,84.4,84.6,87.0,87.7,88.1,88.4,88.8,89.2|23.7,26.9,30.6,34.1,37.8,40.8,43.5,48.4,52.4,55.2,57.9,59.7,61.3|0.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0,2.3,2.6,3.2,3.2,3.3|0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.2,1.3,1.7,1.8,1.9&chm=b,c3df9b,0,1,0|b,b6dc7d,1,2,0|tAndroid%202.1,5b831d,2,0,15,,t::-5|b,aadb5e,2,3,0|tAndroid%202.2,496c13,3,0,15,,t::-5|b,9ddb3d,3,4,0|tAndroid%202.3.3,38540b,4,0,15,,t::-5|b,91da1e,4,5,0|b,80c414,5,6,0|B,6fad0c,6,7,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3.3|Android%203.1|Android%203.2&chco=add274,a0d155,94d134,84c323,73ad18,62960f,507d08" /> -<p><em>Last historical dataset collected during a 14-day period ending on January 3, 2012</em></p> +<p><em>Last historical dataset collected during a 14-day period ending on February 1, 2012</em></p> </div><!-- end dashboard-panel --> diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd index 79d59d91aa7b..ae5cdc735915 100644 --- a/docs/html/resources/dashboard/screens.jd +++ b/docs/html/resources/dashboard/screens.jd @@ -60,7 +60,7 @@ ending on the data collection date noted below.</p> <div class="dashboard-panel"> <img alt="" width="400" height="250" -src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A3.1,0.1,3.1,71.0,1.0,17.5,2.9,1.3" /> +src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Normal%20/%20xhdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A4.8,0.2,2.9,67.1,0.7,18.4,1.8,2.5,1.6" /> <table> <tr> @@ -71,31 +71,31 @@ src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl= <th scope="col">xhdpi</th> </tr> <tr><th scope="row">small</th> -<td>1.3%</td> <!-- small/ldpi --> +<td>1.6%</td> <!-- small/ldpi --> <td></td> <!-- small/mdpi --> -<td>2.9%</td> <!-- small/hdpi --> +<td>2.5%</td> <!-- small/hdpi --> <td></td> <!-- small/xhdpi --> </tr> <tr><th scope="row">normal</th> -<td>1.0%</td> <!-- normal/ldpi --> -<td>17.5%</td> <!-- normal/mdpi --> -<td>71%</td> <!-- normal/hdpi --> -<td></td> <!-- normal/xhdpi --> +<td>0.7%</td> <!-- normal/ldpi --> +<td>18.4%</td> <!-- normal/mdpi --> +<td>67.1%</td> <!-- normal/hdpi --> +<td>1.8%</td> <!-- normal/xhdpi --> </tr> <tr><th scope="row">large</th> -<td>0.1%</td> <!-- large/ldpi --> -<td>3.1%</td> <!-- large/mdpi --> +<td>0.2%</td> <!-- large/ldpi --> +<td>2.9%</td> <!-- large/mdpi --> <td></td> <!-- large/hdpi --> <td></td> <!-- large/xhdpi --> </tr> <tr><th scope="row">xlarge</th> <td></td> <!-- xlarge/ldpi --> -<td>3.1%</td> <!-- xlarge/mdpi --> +<td>4.8%</td> <!-- xlarge/mdpi --> <td></td> <!-- xlarge/hdpi --> <td></td> <!-- xlarge/xhdpi --> </tr> </table> -<p><em>Data collected during a 7-day period ending on December 1, 2011</em></p> +<p><em>Data collected during a 7-day period ending on February 1, 2012</em></p> </div> diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs index 8df419fd5a6a..848303751a1c 100644 --- a/docs/html/resources/resources_toc.cs +++ b/docs/html/resources/resources_toc.cs @@ -1,7 +1,8 @@ <ul> <li> - <h2><span class="en">Android Training</span> - </h2> + <span class="heading"> + <span class="en">Android Training</span> + </span> <ul> <li><a href="<?cs var:toroot ?>training/index.html"> @@ -237,8 +238,9 @@ <li> - <h2><span class="en">Technical Resources</span> - </h2> + <span class="heading"> + <span class="en">Technical Resources</span> + </span> <ul> <li class="toggle-list"> <div><a href="<?cs var:toroot ?>resources/browser.html?tag=sample"> @@ -288,29 +290,31 @@ </ul> </li> <li> - <h2><span class="en">Community</span> - <span style="display:none" class="de"></span> - <span style="display:none" class="es">Comunidad</span> - <span style="display:none" class="fr">Communauté</span> - <span style="display:none" class="it"></span> - <span style="display:none" class="ja">コミュニティ</span> - <span style="display:none" class="zh-CN">社区</span> - <span style="display:none" class="zh-TW">社群</span> - </h2> + <span class="heading"> + <span class="en">Community</span> + <span style="display:none" class="de"></span> + <span style="display:none" class="es">Comunidad</span> + <span style="display:none" class="fr">Communauté</span> + <span style="display:none" class="it"></span> + <span style="display:none" class="ja">コミュニティ</span> + <span style="display:none" class="zh-CN">社区</span> + <span style="display:none" class="zh-TW">社群</span> + </span> <ul> <li><a href="<?cs var:toroot ?>resources/community-groups.html"> <span class="en">Developer Forums</span> </a></li> <li><a href="<?cs var:toroot ?>resources/community-more.html"> - <span class="en">IRC, Twitter</span> + <span class="en">IRC, G+, Twitter</span> </a></li> </ul> </li> <?cs if:android.whichdoc == "online" ?> <li> - <h2><span class="en">Device Dashboard</span> - </h2> + <span class="heading"> + <span class="en">Device Dashboard</span> + </span> <ul> <li><a href="<?cs var:toroot ?>resources/dashboard/platform-versions.html"> <span class="en">Platform Versions</span> @@ -327,7 +331,9 @@ ?> <li> - <h2><span class="en">More</span></h2> + <span class="heading"> + <span class="en">More</span> + </span> <ul> <li><a href="<?cs var:toroot ?>resources/faq/commontasks.html"> <span class="en">Common Tasks </span> diff --git a/docs/html/resources/tutorials/hello-world.jd b/docs/html/resources/tutorials/hello-world.jd index 9afab6a2ef41..cc8cb3ebd605 100644 --- a/docs/html/resources/tutorials/hello-world.jd +++ b/docs/html/resources/tutorials/hello-world.jd @@ -24,7 +24,7 @@ Eclipse as your IDE, because we've provided a great plugin that handles your pro management to greatly speed up your development cycles.</p> <p>This tutorial assumes that you're using Eclipse. If you're using the command line, see -<a href="{@docRoot}/guide/developing/building/building-cmdline.html">Building and Running from the +<a href="{@docRoot}guide/developing/building/building-cmdline.html">Building and Running from the Command Line</a>. You can then return to this tutorial and ignore anything about Eclipse.</p> <p>Before you start, you should already have the SDK installed, and if you're diff --git a/docs/html/sdk/android-2.3.3.jd b/docs/html/sdk/android-2.3.3.jd index 7a5b044c9687..023e2e41294a 100644 --- a/docs/html/sdk/android-2.3.3.jd +++ b/docs/html/sdk/android-2.3.3.jd @@ -336,7 +336,7 @@ descriptor).</p> <li>English, New Zealand (en_NZ)</li> <li>English, Singapore(en_SG)</li> <li>English, US (en_US)</li> -<li>English, Zimbabwe (en_ZA)</li> +<li>English, South Africa (en_ZA)</li> <li>Spanish (es_ES)</li> <li>Spanish, US (es_US)</li> <li>Finnish, Finland (fi_FI)</li> diff --git a/docs/html/sdk/android-2.3.4.jd b/docs/html/sdk/android-2.3.4.jd index 4cb44b921b5a..eeaa69ab169f 100644 --- a/docs/html/sdk/android-2.3.4.jd +++ b/docs/html/sdk/android-2.3.4.jd @@ -296,7 +296,7 @@ descriptor).</p> <li>English, New Zealand (en_NZ)</li> <li>English, Singapore(en_SG)</li> <li>English, US (en_US)</li> -<li>English, Zimbabwe (en_ZA)</li> +<li>English, South Africa (en_ZA)</li> <li>Spanish (es_ES)</li> <li>Spanish, US (es_US)</li> <li>Finnish, Finland (fi_FI)</li> diff --git a/docs/html/sdk/android-2.3.jd b/docs/html/sdk/android-2.3.jd index e7aa0fac727a..fc4f5aa95a24 100644 --- a/docs/html/sdk/android-2.3.jd +++ b/docs/html/sdk/android-2.3.jd @@ -859,7 +859,7 @@ descriptor).</p> <li>English, New Zealand (en_NZ)</li> <li>English, Singapore(en_SG)</li> <li>English, US (en_US)</li> -<li>English, Zimbabwe (en_ZA)</li> +<li>English, South Africa (en_ZA)</li> <li>Spanish (es_ES)</li> <li>Spanish, US (es_US)</li> <li>Finnish, Finland (fi_FI)</li> diff --git a/docs/html/sdk/android-3.0.jd b/docs/html/sdk/android-3.0.jd index 96b92d9a59e3..49fefee535d4 100644 --- a/docs/html/sdk/android-3.0.jd +++ b/docs/html/sdk/android-3.0.jd @@ -1119,7 +1119,7 @@ descriptor).</p> <li>English, New Zealand (en_NZ)</li> <li>English, Singapore(en_SG)</li> <li>English, US (en_US)</li> -<li>English, Zimbabwe (en_ZA)</li> +<li>English, South Africa (en_ZA)</li> <li>Spanish (es_ES)</li> <li>Spanish, US (es_US)</li> <li>Finnish, Finland (fi_FI)</li> diff --git a/docs/html/sdk/android-3.1.jd b/docs/html/sdk/android-3.1.jd index 78f265d2f055..b9cf96940364 100644 --- a/docs/html/sdk/android-3.1.jd +++ b/docs/html/sdk/android-3.1.jd @@ -1040,7 +1040,7 @@ descriptor).</p> <li>English, New Zealand (en_NZ)</li> <li>English, Singapore(en_SG)</li> <li>English, US (en_US)</li> -<li>English, Zimbabwe (en_ZA)</li> +<li>English, South Africa (en_ZA)</li> <li>Spanish (es_ES)</li> <li>Spanish, US (es_US)</li> <li>Finnish, Finland (fi_FI)</li> diff --git a/docs/html/sdk/android-4.0.3.jd b/docs/html/sdk/android-4.0.3.jd index 1fca8dff35ea..809c83c6e61d 100644 --- a/docs/html/sdk/android-4.0.3.jd +++ b/docs/html/sdk/android-4.0.3.jd @@ -435,7 +435,7 @@ image are listed below (with <em>language</em>_<em>country/region</em> locale de <li>English, New Zealand (en_NZ)</li> <li>English, Singapore(en_SG)</li> <li>English, US (en_US)</li> -<li>English, Zimbabwe (en_ZA)</li> +<li>English, South Africa (en_ZA)</li> <li>Spanish (es_ES)</li> <li>Spanish, US (es_US)</li> <li>Finnish, Finland (fi_FI)</li> diff --git a/docs/html/sdk/android-4.0.jd b/docs/html/sdk/android-4.0.jd index 5f55947bb0e6..2cad86b7a686 100644 --- a/docs/html/sdk/android-4.0.jd +++ b/docs/html/sdk/android-4.0.jd @@ -1963,7 +1963,7 @@ image are listed below (with <em>language</em>_<em>country/region</em> locale de <li>English, New Zealand (en_NZ)</li> <li>English, Singapore(en_SG)</li> <li>English, US (en_US)</li> -<li>English, Zimbabwe (en_ZA)</li> +<li>English, South Africa (en_ZA)</li> <li>Spanish (es_ES)</li> <li>Spanish, US (es_US)</li> <li>Finnish, Finland (fi_FI)</li> diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd index f15da78710ad..30825eec060c 100644 --- a/docs/html/sdk/eclipse-adt.jd +++ b/docs/html/sdk/eclipse-adt.jd @@ -42,7 +42,7 @@ export signed (or unsigned) {@code .apk} files in order to distribute your appli <p>Developing in Eclipse with ADT is highly recommended and is the fastest way to get started. With the guided project setup it provides, as well as tools -integration, custom XML editors, and debug ouput pane, ADT gives you an +integration, custom XML editors, and debug output pane, ADT gives you an incredible boost in developing Android applications. </p> <p>This document provides step-by-step instructions on how to download the ADT @@ -1104,7 +1104,7 @@ ADT installation as described in the steps below. </p> <h3 id="downloading">Downloading the ADT Plugin</h3> <p>Use the Update Manager feature of your Eclipse installation to install the latest -revision of ADT on your development computer.<> +revision of ADT on your development computer.</p> <p>Assuming that you have a compatible version of the Eclipse IDE installed, as described in <a href="#preparing">Preparing for Installation</a>, above, follow diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd index 65a1f465c475..5cf05e0ec1bc 100644 --- a/docs/html/sdk/index.jd +++ b/docs/html/sdk/index.jd @@ -1,4 +1,5 @@ page.title=Android SDK +page.metaDescription=Download the official Android SDK to develop apps for Android-powered devices. sdk.redirect=0 sdk.win_installer=installer_r16-windows.exe diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs index 5f6a57fc2168..0de477a25dfa 100644 --- a/docs/html/sdk/sdk_toc.cs +++ b/docs/html/sdk/sdk_toc.cs @@ -1,7 +1,7 @@ <?cs if:!sdk.redirect ?> <ul> <li> - <h2> + <span class="heading"> <span class="en">Android SDK Starter Package</span> <span style="display:none" class="de">Aktuelle SDK-Version</span> <span style="display:none" class="es">Versión actual del SDK</span> @@ -10,7 +10,7 @@ <span style="display:none" class="ja">現在リリースされている SDK</span> <span style="display:none" class="zh-CN">当前的 SDK 版本</span> <span style="display:none" class="zh-TW">目前 SDK 發行版本</span> - </h2> + </span> <ul><?cs if:android.whichdoc == "online" ?> <li><a href="<?cs var:toroot ?>sdk/index.html"> @@ -37,7 +37,8 @@ </ul> </li><?cs if:sdk.preview ?> - <li><h2>Android 3.0 Preview SDK</h2> + <li> + <span class="heading">Android 3.0 Preview SDK</span> <ul> <li><a href="<?cs var:toroot ?>sdk/preview/start.html">Getting Started</a> <span class="new">new!</span></li> @@ -46,13 +47,14 @@ class="new">new!</span></li> /if ?> <?cs if:sdk.preview ?> - <li><h2>Android x.x Preview</h2> + <li> + <span class="heading">Android x.x Preview</span> <ul> </ul> </li><?cs /if ?> <li> - <h2> + <span class="heading"> <span class="en">Downloadable SDK Components</span> <span style="display:none" class="de"></span> <span style="display:none" class="es"></span> @@ -61,7 +63,7 @@ class="new">new!</span></li> <span style="display:none" class="ja"></span> <span style="display:none" class="zh-CN"></span> <span style="display:none" class="zh-TW"></span> - </h2> + </span> <ul> <li><a href="<?cs var:toroot ?>sdk/adding-components.html"> <span class="en">Adding SDK Components</span> @@ -158,7 +160,7 @@ class="new">new!</span></li> </ul> </li> <li> - <h2> + <span class="heading"> <span class="en">ADT Plugin for Eclipse</span> <span style="display:none" class="de"></span> <span style="display:none" class="es"></span> @@ -167,7 +169,7 @@ class="new">new!</span></li> <span style="display:none" class="ja"></span> <span style="display:none" class="zh-CN"></span> <span style="display:none" class="zh-TW"></span> - </h2> + </span> <ul> <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 16.0.1 <span style="display:none" class="de"></span> @@ -182,7 +184,8 @@ class="new">new!</span> </ul> </li> <li> - <h2><span class="en">Native Development Tools</span> + <span class="heading"> + <span class="en">Native Development Tools</span> <span style="display:none" class="de"></span> <span style="display:none" class="es"></span> <span style="display:none" class="fr"></span> @@ -190,7 +193,7 @@ class="new">new!</span> <span style="display:none" class="ja"></span> <span style="display:none" class="zh-CN"></span> <span style="display:none" class="zh-TW"></span> - </h2> + </span> <ul> <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r7</a> <span class="new">new!</span> @@ -200,7 +203,7 @@ class="new">new!</span> </li> <li> - <h2> + <span class="heading"> <span class="en">More Information</span> <span style="display:none" class="de"></span> <span style="display:none" class="es"></span> @@ -209,7 +212,7 @@ class="new">new!</span> <span style="display:none" class="ja"></span> <span style="display:none" class="zh-CN"></span> <span style="display:none" class="zh-TW"></span> - </h2> + </span> <ul> <li><a href="<?cs var:toroot ?>sdk/oem-usb.html"> <span class="en">OEM USB Drivers</span> diff --git a/docs/html/training/design-navigation/ancestral-temporal.jd b/docs/html/training/design-navigation/ancestral-temporal.jd index c0b1aef738e9..ab6a64d2925a 100644 --- a/docs/html/training/design-navigation/ancestral-temporal.jd +++ b/docs/html/training/design-navigation/ancestral-temporal.jd @@ -29,7 +29,10 @@ next.link=wireframing.html </div> -<p>Now that users can navigate <a href="descendant-lateral.html">deep into</a> the application's screen hierarchy, we need to provide a method for navigating up the hierarchy, to parent and ancestor screens. Additionally, we should ensure that temporal navigation via the BACK button is respected to respect Android conventions.</p> +<p>Now that users can navigate <a href="descendant-lateral.html">deep into</a> the application's +screen hierarchy, we need to provide a method for navigating up the hierarchy, to parent and +ancestor screens. Additionally, we should ensure that temporal navigation via the <em>Back</em> +button is respected to respect Android conventions.</p> <div class="design-announce"> <p><strong>Back/Up Navigation Design</strong></p> @@ -39,27 +42,52 @@ next.link=wireframing.html <h2 id="temporal-navigation">Support Temporal Navigation: <em>Back</em></h2> -<p>Temporal navigation, or navigation between historical screens, is deeply rooted in the Android system. All Android users expect the BACK button to take them to the previous screen, regardless of other state. The set of historical screens is always rooted at the user's Launcher application (the phone's "home" screen). That is, pressing BACK enough times should land you back at the Launcher, after which the BACK button will do nothing.</p> +<p>Temporal navigation, or navigation between historical screens, is deeply rooted in the Android +system. All Android users expect the <em>Back</em> button to take them to the previous screen, +regardless of other state. The set of historical screens is always rooted at the user's Launcher +application (the phone's "home" screen). That is, pressing <em>Back</em> enough times should land +you back at the Launcher, after which the <em>Back</em> button will do nothing.</p> <img src="{@docRoot}images/training/app-navigation-ancestral-navigate-back.png" - alt="The BACK button behavior after entering the Email app from the People (or Contacts) app" id="figure-navigate-back"> + alt="The Back button behavior after entering the Email app from the People (or Contacts) app" +id="figure-navigate-back"> -<p class="img-caption"><strong>Figure 1.</strong> The BACK button behavior after entering the Email app from the People (or Contacts) app.</p> +<p class="img-caption"><strong>Figure 1.</strong> The <em>Back</em> button behavior after entering +the Email app from the People (or Contacts) app.</p> -<p>Applications generally don't have to worry about managing the BACK button themselves; the system handles <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">tasks and the <em>back stack</em></a>, or the list of previous screens, automatically. The BACK button by default simply traverses this list of screens, removing the current screen from the list upon being pressed.</p> +<p>Applications generally don't have to worry about managing the <em>Back</em> button themselves; +the system handles <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">tasks and +the <em>back stack</em></a>, or the list of previous screens, automatically. The <em>Back</em> +button by default simply traverses this list of screens, removing the current screen from the list +upon being pressed.</p> -<p>There are, however, cases where you may want to override the behavior for BACK. For example, if your screen contains an embedded web browser where users can interact with page elements to navigate between web pages, you may wish to trigger the embedded browser's default <em>back</em> behavior when users press the device's BACK button. Upon reaching the beginning of the browser's internal history, you should always defer to the system's default behavior for the BACK button.</p> +<p>There are, however, cases where you may want to override the behavior for <em>Back</em>. For +example, if your screen contains an embedded web browser where users can interact with page elements +to navigate between web pages, you may wish to trigger the embedded browser's default <em>back</em> +behavior when users press the device's <em>Back</em> button. Upon reaching the beginning of the +browser's internal history, you should always defer to the system's default behavior for the +<em>Back</em> button.</p> <h2 id="ancestral-navigation">Provide Ancestral Navigation: <em>Up</em> and <em>Home</em></h2> -<p>Before Android 3.0, the most common form of ancestral navigation was the <em>Home</em> metaphor. This was generally implemented as a <em>Home</em> item accessible via the device's MENU button, or a <em>Home</em> button at the top-left of the screen, usually as a component of the Action Bar (<a href="{@docRoot}design/patterns/actionbar.html">pattern docs</a> at Android Design). Upon selecting <em>Home</em>, the user would be taken to the screen at the top of the screen hierarchy, generally known as the application's home screen.</p> +<p>Before Android 3.0, the most common form of ancestral navigation was the <em>Home</em> metaphor. +This was generally implemented as a <em>Home</em> item accessible via the device's <em>Menu</em> +button, or a <em>Home</em> button at the top-left of the screen, usually as a component of the +Action Bar (<a href="{@docRoot}design/patterns/actionbar.html">pattern docs</a> at Android Design). +Upon selecting <em>Home</em>, the user would be taken to the screen at the top of the screen +hierarchy, generally known as the application's home screen.</p> <p>Providing direct access to the application's home screen can give the user a sense of comfort and security. Regardless of where they are in the application, if they get lost in the app, they can select <em>Home</em> to arrive back at the familiar home screen.</p> -<p>Android 3.0 introduced the <em>Up</em> metaphor, which is presented in the Action Bar as a substitute for the <em>Home</em> button described above. Upon tapping <em>Up</em>, the user should be taken to the parent screen in the hierarchy. This navigation step is usually the previous screen (as described with the BACK button discussion above), but this is not universally the case. Thus, developers must ensure that <em>Up</em> for each screen navigates to a single, predetermined parent screen.</p> +<p>Android 3.0 introduced the <em>Up</em> metaphor, which is presented in the Action Bar as a +substitute for the <em>Home</em> button described above. Upon tapping <em>Up</em>, the user should +be taken to the parent screen in the hierarchy. This navigation step is usually the previous screen +(as described with the <em>Back</em> button discussion above), but this is not universally the case. +Thus, developers must ensure that <em>Up</em> for each screen navigates to a single, predetermined +parent screen.</p> <img src="{@docRoot}images/training/app-navigation-ancestral-navigate-up.png" @@ -70,6 +98,12 @@ next.link=wireframing.html <p>In some cases, it's appropriate for <em>Up</em> to perform an action rather than navigating to a parent screen. Take for example, the Gmail application for Android 3.0-based tablets. When viewing a mail conversation while holding the device in landscape, the conversation list, as well as the conversation details are presented side-by-side. This is a form of parent-child screen grouping, as discussed in a <a href="multiple-sizes.html">previous lesson</a>. However, when viewing a mail conversation in the portrait orientation, only the conversation details are shown. The <em>Up</em> button is used to temporarily show the parent pane, which slides in from the left of the screen. Pressing the <em>Up</em> button again while the left pane is visible exits the context of the individual conversation, up to a full-screen list of conversations.</p> -<p class="note"><strong>Implementation Note:</strong> As a best practice, when implementing either <em>Home</em> or <em>Up</em>, make sure to clear the back stack of any descendent screens. For <em>Home</em>, the only remaining screen on the back stack should be the home screen. For <em>Up</em> navigation, the current screen should be removed from the back stack, unless BACK navigates across screen hierarchies. You can use the {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP} and {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} intent flags together to achieve this.</p> +<p class="note"><strong>Implementation Note:</strong> As a best practice, when implementing either +<em>Home</em> or <em>Up</em>, make sure to clear the back stack of any descendent screens. For +<em>Home</em>, the only remaining screen on the back stack should be the home screen. For +<em>Up</em> navigation, the current screen should be removed from the back stack, unless +<em>Back</em> navigates across screen hierarchies. You can use the {@link +android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP} and {@link +android.content.Intent#FLAG_ACTIVITY_NEW_TASK} intent flags together to achieve this.</p> <p>In the last lesson, we apply the concepts discussed in all of the lessons so far to create interaction design wireframes for our example news application.</p> diff --git a/docs/html/training/design-navigation/descendant-lateral.jd b/docs/html/training/design-navigation/descendant-lateral.jd index 0064bd495c2d..2d97e401d3a5 100644 --- a/docs/html/training/design-navigation/descendant-lateral.jd +++ b/docs/html/training/design-navigation/descendant-lateral.jd @@ -117,7 +117,16 @@ next.link=ancestral-temporal.html <p class="img-caption"><strong>Figure 5.</strong> Example phone and tablet tab-based navigation interfaces with relevant screen map excerpt.</p> -<p>Several best practices apply when using tabs. Tabs should be persistent across immediate related screens. Only the designated content region should change when selecting a tab, and tab indicators should remain available at all times. Additionally, tab switches should not be treated as history. For example, if a user switches from a tab <em>A</em> to another tab <em>B</em>, pressing the BACK button (more on that in the <a href="ancestral-temporal.html">next lesson</a>) should not re-select tab <em>A</em>. Tabs are usually laid out horizontally, although other presentations of tab navigation such as using a drop-down list in the Action Bar (<a href="{@docRoot}design/patterns/actionbar.html">pattern docs</a> at Android Design) are sometimes appropriate. Lastly, and most importantly, <em>tabs should always run along the top of the screen</em>, and should not be aligned to the bottom of the screen.</p> +<p>Several best practices apply when using tabs. Tabs should be persistent across immediate related +screens. Only the designated content region should change when selecting a tab, and tab indicators +should remain available at all times. Additionally, tab switches should not be treated as history. +For example, if a user switches from a tab <em>A</em> to another tab <em>B</em>, pressing the +<em>Back</em> button (more on that in the <a href="ancestral-temporal.html">next lesson</a>) should +not re-select tab <em>A</em>. Tabs are usually laid out horizontally, although other presentations +of tab navigation such as using a drop-down list in the Action Bar (<a +href="{@docRoot}design/patterns/actionbar.html">pattern docs</a> at Android Design) are sometimes +appropriate. Lastly, and most importantly, <em>tabs should always run along the top of the +screen</em>, and should not be aligned to the bottom of the screen.</p> <p>There are some obvious immediate benefits of tabs over simpler list- and button-based navigation:</p> diff --git a/docs/html/training/design-navigation/index.jd b/docs/html/training/design-navigation/index.jd index cb20a606861e..af6071714437 100644 --- a/docs/html/training/design-navigation/index.jd +++ b/docs/html/training/design-navigation/index.jd @@ -41,7 +41,9 @@ Relationships</a></strong></dt> <dd>Learn about techniques for allowing users to navigate deep into, as well as across, your content hierarchy. Also learn about pros and cons of, and best practices for, specific navigational UI elements for various situations.</dd> <dt><strong><a href="ancestral-temporal.html">Providing Ancestral and Temporal Navigation</a></strong></dt> - <dd>Learn how to allow users to navigate upwards in the content hierarchy. Also learn about best practices for the BACK button and temporal navigation, or navigation to previous screens that may not be hierarchically related.</dd> + <dd>Learn how to allow users to navigate upwards in the content hierarchy. Also learn about best +practices for the <em>Back</em> button and temporal navigation, or navigation to previous screens +that may not be hierarchically related.</dd> <dt><strong><a href="wireframing.html">Putting it All Together: Wireframing the Example App</a></strong></dt> <dd>Learn how to create screen wireframes (low-fidelity graphic mockups) representing the screens in a news application based on the desired information model. These wireframes utilize navigational elements discussed in previous lessons to demonstrate intuitive and efficient navigation.</dd> diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java index 82ed1992bac3..96a71e390713 100644 --- a/graphics/java/android/graphics/LinearGradient.java +++ b/graphics/java/android/graphics/LinearGradient.java @@ -28,8 +28,8 @@ public class LinearGradient extends Shader { the the colors are distributed evenly along the gradient line. @param tile The Shader tiling mode */ - public LinearGradient(float x0, float y0, float x1, float y1, - int colors[], float positions[], TileMode tile) { + public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], + TileMode tile) { if (colors.length < 2) { throw new IllegalArgumentException("needs >= 2 number of colors"); } @@ -50,8 +50,8 @@ public class LinearGradient extends Shader { @param color1 The color at the end of the gradient line. @param tile The Shader tiling mode */ - public LinearGradient(float x0, float y0, float x1, float y1, - int color0, int color1, TileMode tile) { + public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, + TileMode tile) { native_instance = nativeCreate2(x0, y0, x1, y1, color0, color1, tile.nativeInt); native_shader = nativePostCreate2(native_instance, x0, y0, x1, y1, color0, color1, tile.nativeInt); diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 78302244e1c1..0ada1fbdcd8d 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -76,13 +76,21 @@ public final class Rect implements Parcelable { } @Override - public boolean equals(Object obj) { - Rect r = (Rect) obj; - if (r != null) { - return left == r.left && top == r.top && right == r.right - && bottom == r.bottom; - } - return false; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Rect r = (Rect) o; + return left == r.left && top == r.top && right == r.right && bottom == r.bottom; + } + + @Override + public int hashCode() { + int result = left; + result = 31 * result + top; + result = 31 * result + right; + result = 31 * result + bottom; + return result; } @Override diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java index 00e9609a71bf..293dfccb4b5d 100644 --- a/graphics/java/android/graphics/RectF.java +++ b/graphics/java/android/graphics/RectF.java @@ -79,6 +79,24 @@ public class RectF implements Parcelable { bottom = r.bottom; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Rect r = (Rect) o; + return left == r.left && top == r.top && right == r.right && bottom == r.bottom; + } + + @Override + public int hashCode() { + int result = (left != +0.0f ? Float.floatToIntBits(left) : 0); + result = 31 * result + (top != +0.0f ? Float.floatToIntBits(top) : 0); + result = 31 * result + (right != +0.0f ? Float.floatToIntBits(right) : 0); + result = 31 * result + (bottom != +0.0f ? Float.floatToIntBits(bottom) : 0); + return result; + } + public String toString() { return "RectF(" + left + ", " + top + ", " + right + ", " + bottom + ")"; diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 50964d5e5546..5b50bebd64ac 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -173,9 +173,20 @@ public class GradientDrawable extends Drawable { } /** - * Specify radii for each of the 4 corners. For each corner, the array - * contains 2 values, [X_radius, Y_radius]. The corners are ordered - * top-left, top-right, bottom-right, bottom-left + * <p>Specify radii for each of the 4 corners. For each corner, the array + * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are ordered + * top-left, top-right, bottom-right, bottom-left. This property + * is honored only when the shape is of type {@link #RECTANGLE}.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param radii 4 pairs of X and Y radius for each corner, specified in pixels. + * The length of this array must be >= 8 + * + * @see #mutate() + * @see #setCornerRadii(float[]) + * @see #setShape(int) */ public void setCornerRadii(float[] radii) { mGradientState.setCornerRadii(radii); @@ -184,23 +195,57 @@ public class GradientDrawable extends Drawable { } /** - * Specify radius for the corners of the gradient. If this is > 0, then the - * drawable is drawn in a round-rectangle, rather than a rectangle. + * <p>Specify radius for the corners of the gradient. If this is > 0, then the + * drawable is drawn in a round-rectangle, rather than a rectangle. This property + * is honored only when the shape is of type {@link #RECTANGLE}.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param radius The radius in pixels of the corners of the rectangle shape + * + * @see #mutate() + * @see #setCornerRadii(float[]) + * @see #setShape(int) */ public void setCornerRadius(float radius) { mGradientState.setCornerRadius(radius); mPathIsDirty = true; invalidateSelf(); } - + /** - * Set the stroke width and color for the drawable. If width is zero, - * then no stroke is drawn. + * <p>Set the stroke width and color for the drawable. If width is zero, + * then no stroke is drawn.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param width The width in pixels of the stroke + * @param color The color of the stroke + * + * @see #mutate() + * @see #setStroke(int, int, float, float) */ public void setStroke(int width, int color) { setStroke(width, color, 0, 0); } - + + /** + * <p>Set the stroke width and color for the drawable. If width is zero, + * then no stroke is drawn. This method can also be used to dash the stroke.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param width The width in pixels of the stroke + * @param color The color of the stroke + * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes + * @param dashGap The gap in pixels between dashes + * + * @see #mutate() + * @see #setStroke(int, int) + */ public void setStroke(int width, int color, float dashWidth, float dashGap) { mGradientState.setStroke(width, color, dashWidth, dashGap); @@ -218,13 +263,37 @@ public class GradientDrawable extends Drawable { mStrokePaint.setPathEffect(e); invalidateSelf(); } - + + + /** + * <p>Sets the size of the shape drawn by this drawable.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param width The width of the shape used by this drawable + * @param height The height of the shape used by this drawable + * + * @see #mutate() + * @see #setGradientType(int) + */ public void setSize(int width, int height) { mGradientState.setSize(width, height); mPathIsDirty = true; invalidateSelf(); } - + + /** + * <p>Sets the type of shape used to draw the gradient.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param shape The desired shape for this drawable: {@link #LINE}, + * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING} + * + * @see #mutate() + */ public void setShape(int shape) { mRingPath = null; mPathIsDirty = true; @@ -232,24 +301,73 @@ public class GradientDrawable extends Drawable { invalidateSelf(); } + /** + * <p>Sets the type of gradient used by this drawable..</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT}, + * {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT} + * + * @see #mutate() + */ public void setGradientType(int gradient) { mGradientState.setGradientType(gradient); mRectIsDirty = true; invalidateSelf(); } + /** + * <p>Sets the center location of the gradient. The radius is honored only when + * the gradient type is set to {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param x The x coordinate of the gradient's center + * @param y The y coordinate of the gradient's center + * + * @see #mutate() + * @see #setGradientType(int) + */ public void setGradientCenter(float x, float y) { mGradientState.setGradientCenter(x, y); mRectIsDirty = true; invalidateSelf(); } + /** + * <p>Sets the radius of the gradient. The radius is honored only when the + * gradient type is set to {@link #RADIAL_GRADIENT}.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param gradientRadius The radius of the gradient in pixels + * + * @see #mutate() + * @see #setGradientType(int) + */ public void setGradientRadius(float gradientRadius) { mGradientState.setGradientRadius(gradientRadius); mRectIsDirty = true; invalidateSelf(); } + /** + * <p>Sets whether or not this drawable will honor its <code>level</code> + * property.</p> + * <p><strong>Note</strong>: changing this property will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing this property.</p> + * + * @param useLevel True if this drawable should honor its level, false otherwise + * + * @see #mutate() + * @see #setLevel(int) + * @see #getLevel() + */ public void setUseLevel(boolean useLevel) { mGradientState.mUseLevel = useLevel; mRectIsDirty = true; @@ -261,6 +379,47 @@ public class GradientDrawable extends Drawable { return alpha * scale >> 8; } + /** + * Returns the orientation of the gradient defined in this drawable. + */ + public Orientation getOrientation() { + return mGradientState.mOrientation; + } + + /** + * <p>Changes the orientation of the gradient defined in this drawable.</p> + * <p><strong>Note</strong>: changing orientation will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing the orientation.</p> + * + * @param orientation The desired orientation (angle) of the gradient + * + * @see #mutate() + */ + public void setOrientation(Orientation orientation) { + mGradientState.mOrientation = orientation; + mRectIsDirty = true; + invalidateSelf(); + } + + /** + * <p>Sets the colors used to draw the gradient. Each color is specified as an + * ARGB integer and the array must contain at least 2 colors.</p> + * <p><strong>Note</strong>: changing orientation will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing the orientation.</p> + * + * @param colors 2 or more ARGB colors + * + * @see #mutate() + * @see #setColor(int) + */ + public void setColors(int[] colors) { + mGradientState.setColors(colors); + mRectIsDirty = true; + invalidateSelf(); + } + @Override public void draw(Canvas canvas) { if (!ensureValidRect()) { @@ -442,6 +601,17 @@ public class GradientDrawable extends Drawable { return ringPath; } + /** + * <p>Changes this drawbale to use a single color instead of a gradient.</p> + * <p><strong>Note</strong>: changing orientation will affect all instances + * of a drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing the orientation.</p> + * + * @param argb The color used to fill the shape + * + * @see #mutate() + * @see #setColors(int[]) + */ public void setColor(int argb) { mGradientState.setSolidColor(argb); mFillPaint.setColor(argb); @@ -450,10 +620,9 @@ public class GradientDrawable extends Drawable { @Override public int getChangingConfigurations() { - return super.getChangingConfigurations() - | mGradientState.mChangingConfigurations; + return super.getChangingConfigurations() | mGradientState.mChangingConfigurations; } - + @Override public void setAlpha(int alpha) { if (alpha != mAlpha) { @@ -480,7 +649,6 @@ public class GradientDrawable extends Drawable { @Override public int getOpacity() { - // XXX need to figure out the actual opacity... return PixelFormat.TRANSLUCENT; } @@ -911,11 +1079,6 @@ public class GradientDrawable extends Drawable { private float mGradientRadius = 0.5f; private boolean mUseLevel; private boolean mUseLevelForShape; - - - GradientState() { - mOrientation = Orientation.TOP_BOTTOM; - } GradientState(Orientation orientation, int[] colors) { mOrientation = orientation; @@ -987,6 +1150,11 @@ public class GradientDrawable extends Drawable { mCenterY = y; } + public void setColors(int[] colors) { + mHasSolidColor = false; + mColors = colors; + } + public void setSolidColor(int argb) { mHasSolidColor = true; mSolidColor = argb; @@ -1055,4 +1223,3 @@ public class GradientDrawable extends Drawable { } } } - diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java index f285f5b5ac0c..11c242759d91 100644 --- a/graphics/java/android/renderscript/Allocation.java +++ b/graphics/java/android/renderscript/Allocation.java @@ -487,6 +487,7 @@ public class Allocation extends BaseObj { final byte[] data = fp.getData(); int eSize = mType.mElement.mElements[component_number].getSizeBytes(); + eSize *= mType.mElement.mArraySizes[component_number]; if (data.length != eSize) { throw new RSIllegalArgumentException("Field packer sizelength " + data.length + diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index ad10832b8966..bfe412c72185 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2008-2012 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. @@ -837,7 +837,8 @@ public class RenderScript { mRS.mErrorCallback.mErrorNum = subID; mRS.mErrorCallback.run(); } else { - //throw new RSRuntimeException("Received error num " + subID + ", details: " + e); + // Do not throw here. In these cases, we do not have + // a fatal error. } continue; } diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp index bc1db4dc3310..9622bd244f89 100644 --- a/graphics/jni/android_renderscript_RenderScript.cpp +++ b/graphics/jni/android_renderscript_RenderScript.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2011-2012 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. @@ -567,7 +567,7 @@ nAllocationElementData1D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint len = _env->GetArrayLength(data); LOG_API("nAllocationElementData1D, con(%p), alloc(%p), offset(%i), comp(%i), len(%i), sizeBytes(%i)", con, (RsAllocation)alloc, offset, compIdx, len, sizeBytes); jbyte *ptr = _env->GetByteArrayElements(data, NULL); - rsAllocation1DElementData(con, (RsAllocation)alloc, offset, lod, ptr, compIdx, sizeBytes); + rsAllocation1DElementData(con, (RsAllocation)alloc, offset, lod, ptr, sizeBytes, compIdx); _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT); } @@ -623,7 +623,7 @@ nAllocationData2D_alloc(JNIEnv *_env, jobject _this, RsContext con, jint srcAlloc, jint srcXoff, jint srcYoff, jint srcMip, jint srcFace) { - LOG_API("nAllocation2DData_s, con(%p), dstAlloc(%p), dstXoff, dstYoff," + LOG_API("nAllocation2DData_s, con(%p), dstAlloc(%p), dstXoff(%i), dstYoff(%i)," " dstMip(%i), dstFace(%i), width(%i), height(%i)," " srcAlloc(%p), srcXoff(%i), srcYoff(%i), srcMip(%i), srcFace(%i)", con, (RsAllocation)dstAlloc, dstXoff, dstYoff, dstMip, dstFace, diff --git a/include/binder/IPCThreadState.h b/include/binder/IPCThreadState.h index 3378d97255bf..691ba2fa1d82 100644 --- a/include/binder/IPCThreadState.h +++ b/include/binder/IPCThreadState.h @@ -41,6 +41,7 @@ public: int getCallingPid(); int getCallingUid(); + int getOrigCallingUid(); void setStrictModePolicy(int32_t policy); int32_t getStrictModePolicy() const; @@ -116,6 +117,7 @@ private: status_t mLastError; pid_t mCallingPid; uid_t mCallingUid; + uid_t mOrigCallingUid; int32_t mStrictModePolicy; int32_t mLastTransactionBinderFlags; }; diff --git a/include/gui/DisplayEventReceiver.h b/include/gui/DisplayEventReceiver.h index dccc1643c93a..7bca8d6ea892 100644 --- a/include/gui/DisplayEventReceiver.h +++ b/include/gui/DisplayEventReceiver.h @@ -63,7 +63,9 @@ public: public: /* * DisplayEventReceiver creates and registers an event connection with - * SurfaceFlinger. Events start being delivered immediately. + * SurfaceFlinger. VSync events are disabled by default. Call setVSyncRate + * or requestNextVsync to receive them. + * Other events start being delivered immediately. */ DisplayEventReceiver(); @@ -93,6 +95,8 @@ public: * should be destroyed and getEvents() shouldn't be called again. */ ssize_t getEvents(Event* events, size_t count); + static ssize_t getEvents(const sp<BitTube>& dataChannel, + Event* events, size_t count); /* * setVsyncRate() sets the Event::VSync delivery rate. A value of diff --git a/include/media/AudioEffect.h b/include/media/AudioEffect.h index 1417416538dc..4c57ed2931eb 100644 --- a/include/media/AudioEffect.h +++ b/include/media/AudioEffect.h @@ -108,7 +108,8 @@ public: * Returned value * *descriptor updated with effect descriptor */ - static status_t getEffectDescriptor(effect_uuid_t *uuid, effect_descriptor_t *descriptor); + static status_t getEffectDescriptor(effect_uuid_t *uuid, + effect_descriptor_t *descriptor) /*const*/; /* @@ -226,8 +227,8 @@ public: AudioEffect(const effect_uuid_t *type, const effect_uuid_t *uuid = NULL, int32_t priority = 0, - effect_callback_t cbf = 0, - void* user = 0, + effect_callback_t cbf = NULL, + void* user = NULL, int sessionId = 0, audio_io_handle_t io = 0 ); @@ -238,8 +239,8 @@ public: AudioEffect(const char *typeStr, const char *uuidStr = NULL, int32_t priority = 0, - effect_callback_t cbf = 0, - void* user = 0, + effect_callback_t cbf = NULL, + void* user = NULL, int sessionId = 0, audio_io_handle_t io = 0 ); @@ -260,8 +261,8 @@ public: status_t set(const effect_uuid_t *type, const effect_uuid_t *uuid = NULL, int32_t priority = 0, - effect_callback_t cbf = 0, - void* user = 0, + effect_callback_t cbf = NULL, + void* user = NULL, int sessionId = 0, audio_io_handle_t io = 0 ); diff --git a/include/media/AudioRecord.h b/include/media/AudioRecord.h index 76ec3b1190eb..c8c5dbaf9cac 100644 --- a/include/media/AudioRecord.h +++ b/include/media/AudioRecord.h @@ -155,8 +155,8 @@ public: uint32_t channelMask = AUDIO_CHANNEL_IN_MONO, int frameCount = 0, uint32_t flags = 0, - callback_t cbf = 0, - void* user = 0, + callback_t cbf = NULL, + void* user = NULL, int notificationFrames = 0, int sessionId = 0); @@ -181,8 +181,8 @@ public: uint32_t channelMask = AUDIO_CHANNEL_IN_MONO, int frameCount = 0, uint32_t flags = 0, - callback_t cbf = 0, - void* user = 0, + callback_t cbf = NULL, + void* user = NULL, int notificationFrames = 0, bool threadCanCallJava = false, int sessionId = 0); diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h index 6b12c1451ca6..74a1e623cf8c 100644 --- a/include/media/AudioSystem.h +++ b/include/media/AudioSystem.h @@ -248,7 +248,7 @@ private: static sp<IAudioPolicyService> gAudioPolicyService; // mapping between stream types and outputs - static DefaultKeyedVector<int, audio_io_handle_t> gStreamOutputMap; + static DefaultKeyedVector<audio_stream_type_t, audio_io_handle_t> gStreamOutputMap; // list of output descriptors containing cached parameters // (sampling rate, framecount, channel count...) static DefaultKeyedVector<audio_io_handle_t, OutputDescriptor *> gOutputs; diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h index 98abfbd508e0..9a42bc1d4ea8 100644 --- a/include/media/AudioTrack.h +++ b/include/media/AudioTrack.h @@ -148,8 +148,8 @@ public: int channelMask = 0, int frameCount = 0, uint32_t flags = 0, - callback_t cbf = 0, - void* user = 0, + callback_t cbf = NULL, + void* user = NULL, int notificationFrames = 0, int sessionId = 0); @@ -180,8 +180,8 @@ public: int channelMask = 0, const sp<IMemory>& sharedBuffer = 0, uint32_t flags = 0, - callback_t cbf = 0, - void* user = 0, + callback_t cbf = NULL, + void* user = NULL, int notificationFrames = 0, int sessionId = 0); @@ -204,8 +204,8 @@ public: int channelMask = 0, int frameCount = 0, uint32_t flags = 0, - callback_t cbf = 0, - void* user = 0, + callback_t cbf = NULL, + void* user = NULL, int notificationFrames = 0, const sp<IMemory>& sharedBuffer = 0, bool threadCanCallJava = false, @@ -273,18 +273,18 @@ public: * left and right volumes. Levels must be >= 0.0 and <= 1.0. */ status_t setVolume(float left, float right); - void getVolume(float* left, float* right); + void getVolume(float* left, float* right) const; /* Set the send level for this track. An auxiliary effect should be attached * to the track with attachEffect(). Level must be >= 0.0 and <= 1.0. */ status_t setAuxEffectSendLevel(float level); - void getAuxEffectSendLevel(float* level); + void getAuxEffectSendLevel(float* level) const; /* Set sample rate for this track, mostly used for games' sound effects */ status_t setSampleRate(int sampleRate); - uint32_t getSampleRate(); + uint32_t getSampleRate() const; /* Enables looping and sets the start and end points of looping. * @@ -299,7 +299,7 @@ public: * (loopEnd-loopStart) <= framecount() */ status_t setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount); - status_t getLoop(uint32_t *loopStart, uint32_t *loopEnd, int *loopCount); + status_t getLoop(uint32_t *loopStart, uint32_t *loopEnd, int *loopCount) const; /* Sets marker position. When playback reaches the number of frames specified, a callback with * event type EVENT_MARKER is called. Calling setMarkerPosition with marker == 0 cancels marker @@ -315,7 +315,7 @@ public: * - INVALID_OPERATION: the AudioTrack has no callback installed. */ status_t setMarkerPosition(uint32_t marker); - status_t getMarkerPosition(uint32_t *marker); + status_t getMarkerPosition(uint32_t *marker) const; /* Sets position update period. Every time the number of frames specified has been played, @@ -333,7 +333,7 @@ public: * - INVALID_OPERATION: the AudioTrack has no callback installed. */ status_t setPositionUpdatePeriod(uint32_t updatePeriod); - status_t getPositionUpdatePeriod(uint32_t *updatePeriod); + status_t getPositionUpdatePeriod(uint32_t *updatePeriod) const; /* Sets playback head position within AudioTrack buffer. The new position is specified * in number of frames. @@ -384,7 +384,7 @@ public: * Returned value: * AudioTrack session ID. */ - int getSessionId(); + int getSessionId() const; /* Attach track auxiliary output to specified effect. Use effectId = 0 * to detach track from effect. diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h index 7c0d88671ad4..ba94bad0d37e 100644 --- a/include/media/IAudioFlinger.h +++ b/include/media/IAudioFlinger.h @@ -103,13 +103,13 @@ public: virtual bool getMicMute() const = 0; virtual status_t setParameters(int ioHandle, const String8& keyValuePairs) = 0; - virtual String8 getParameters(int ioHandle, const String8& keys) = 0; + virtual String8 getParameters(int ioHandle, const String8& keys) const = 0; // register a current process for audio output change notifications virtual void registerClient(const sp<IAudioFlingerClient>& client) = 0; // retrieve the audio recording buffer size - virtual size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount) = 0; + virtual size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount) const = 0; virtual int openOutput(uint32_t *pDevices, uint32_t *pSamplingRate, @@ -126,27 +126,28 @@ public: uint32_t *pSamplingRate, audio_format_t *pFormat, uint32_t *pChannels, - uint32_t acoustics) = 0; + audio_in_acoustics_t acoustics) = 0; virtual status_t closeInput(int input) = 0; virtual status_t setStreamOutput(audio_stream_type_t stream, int output) = 0; virtual status_t setVoiceVolume(float volume) = 0; - virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) = 0; + virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) const = 0; - virtual unsigned int getInputFramesLost(int ioHandle) = 0; + virtual unsigned int getInputFramesLost(int ioHandle) const = 0; virtual int newAudioSessionId() = 0; virtual void acquireAudioSessionId(int audioSession) = 0; virtual void releaseAudioSessionId(int audioSession) = 0; - virtual status_t queryNumberEffects(uint32_t *numEffects) = 0; + virtual status_t queryNumberEffects(uint32_t *numEffects) const = 0; - virtual status_t queryEffect(uint32_t index, effect_descriptor_t *pDescriptor) = 0; + virtual status_t queryEffect(uint32_t index, effect_descriptor_t *pDescriptor) const = 0; - virtual status_t getEffectDescriptor(effect_uuid_t *pEffectUUID, effect_descriptor_t *pDescriptor) = 0; + virtual status_t getEffectDescriptor(effect_uuid_t *pEffectUUID, + effect_descriptor_t *pDescriptor) const = 0; virtual sp<IEffect> createEffect(pid_t pid, effect_descriptor_t *pDesc, diff --git a/include/media/IOMX.h b/include/media/IOMX.h index c4cc947a8902..a295e9a19281 100644 --- a/include/media/IOMX.h +++ b/include/media/IOMX.h @@ -42,10 +42,10 @@ public: typedef void *buffer_id; typedef void *node_id; - // Given the calling process' pid, returns true iff + // Given a node_id and the calling process' pid, returns true iff // the implementation of the OMX interface lives in the same // process. - virtual bool livesLocally(pid_t pid) = 0; + virtual bool livesLocally(node_id node, pid_t pid) = 0; struct ComponentInfo { String8 mName; diff --git a/include/media/ToneGenerator.h b/include/media/ToneGenerator.h index 7d890bd541d6..df0c97ebed3b 100644 --- a/include/media/ToneGenerator.h +++ b/include/media/ToneGenerator.h @@ -154,7 +154,7 @@ public: ToneGenerator(audio_stream_type_t streamType, float volume, bool threadCanCallJava = false); ~ToneGenerator(); - bool startTone(int toneType, int durationMs = -1); + bool startTone(tone_type toneType, int durationMs = -1); void stopTone(); bool isInited() { return (mState == TONE_IDLE)?false:true;} @@ -274,7 +274,7 @@ private: bool prepareWave(); unsigned int numWaves(unsigned int segmentIdx); void clearWaveGens(); - int getToneForRegion(int toneType); + tone_type getToneForRegion(tone_type toneType); // WaveGenerator generates a single sine wave class WaveGenerator { diff --git a/include/media/Visualizer.h b/include/media/Visualizer.h index 1a4cbca29490..60fa15b39594 100644 --- a/include/media/Visualizer.h +++ b/include/media/Visualizer.h @@ -66,8 +66,8 @@ public: * See AudioEffect constructor for details on parameters. */ Visualizer(int32_t priority = 0, - effect_callback_t cbf = 0, - void* user = 0, + effect_callback_t cbf = NULL, + void* user = NULL, int sessionId = 0); ~Visualizer(); diff --git a/include/media/stagefright/AACWriter.h b/include/media/stagefright/AACWriter.h index fa3ab8af17eb..49397ee261b9 100644 --- a/include/media/stagefright/AACWriter.h +++ b/include/media/stagefright/AACWriter.h @@ -34,7 +34,7 @@ struct AACWriter : public MediaWriter { virtual status_t addSource(const sp<MediaSource> &source); virtual bool reachedEOS(); virtual status_t start(MetaData *params = NULL); - virtual status_t stop(); + virtual status_t stop() { return reset(); } virtual status_t pause(); protected: @@ -66,6 +66,7 @@ private: bool exceedsFileSizeLimit(); bool exceedsFileDurationLimit(); status_t writeAdtsHeader(uint32_t frameLength); + status_t reset(); DISALLOW_EVIL_CONSTRUCTORS(AACWriter); }; diff --git a/include/media/stagefright/AMRWriter.h b/include/media/stagefright/AMRWriter.h index 62d57b4ba596..392f96883437 100644 --- a/include/media/stagefright/AMRWriter.h +++ b/include/media/stagefright/AMRWriter.h @@ -37,7 +37,7 @@ struct AMRWriter : public MediaWriter { virtual status_t addSource(const sp<MediaSource> &source); virtual bool reachedEOS(); virtual status_t start(MetaData *params = NULL); - virtual status_t stop(); + virtual status_t stop() { return reset(); } virtual status_t pause(); protected: @@ -60,6 +60,7 @@ private: status_t threadFunc(); bool exceedsFileSizeLimit(); bool exceedsFileDurationLimit(); + status_t reset(); AMRWriter(const AMRWriter &); AMRWriter &operator=(const AMRWriter &); diff --git a/include/media/stagefright/AudioSource.h b/include/media/stagefright/AudioSource.h index 2427e2fe9967..79437bfcdb63 100644 --- a/include/media/stagefright/AudioSource.h +++ b/include/media/stagefright/AudioSource.h @@ -40,7 +40,7 @@ struct AudioSource : public MediaSource, public MediaBufferObserver { status_t initCheck() const; virtual status_t start(MetaData *params = NULL); - virtual status_t stop(); + virtual status_t stop() { return reset(); } virtual sp<MetaData> getFormat(); // Returns the maximum amplitude since last call. @@ -97,6 +97,7 @@ private: void releaseQueuedFrames_l(); void waitOutstandingEncodingFrames_l(); + status_t reset(); AudioSource(const AudioSource &); AudioSource &operator=(const AudioSource &); diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h index 446720b88cd5..5a35358d4675 100644 --- a/include/media/stagefright/CameraSource.h +++ b/include/media/stagefright/CameraSource.h @@ -79,7 +79,7 @@ public: virtual ~CameraSource(); virtual status_t start(MetaData *params = NULL); - virtual status_t stop(); + virtual status_t stop() { return reset(); } virtual status_t read( MediaBuffer **buffer, const ReadOptions *options = NULL); @@ -163,7 +163,6 @@ protected: bool storeMetaDataInVideoBuffers); virtual void startCameraRecording(); - virtual void stopCameraRecording(); virtual void releaseRecordingFrame(const sp<IMemory>& frame); // Returns true if need to skip the current frame. @@ -220,7 +219,9 @@ private: status_t checkFrameRate(const CameraParameters& params, int32_t frameRate); + void stopCameraRecording(); void releaseCamera(); + status_t reset(); CameraSource(const CameraSource &); CameraSource &operator=(const CameraSource &); diff --git a/include/media/stagefright/CameraSourceTimeLapse.h b/include/media/stagefright/CameraSourceTimeLapse.h index b0606914c11d..0936da2f3efc 100644 --- a/include/media/stagefright/CameraSourceTimeLapse.h +++ b/include/media/stagefright/CameraSourceTimeLapse.h @@ -121,9 +121,6 @@ private: // Wrapper over CameraSource::read() to implement quick stop. virtual status_t read(MediaBuffer **buffer, const ReadOptions *options = NULL); - // For video camera case, just stops the camera's video recording. - virtual void stopCameraRecording(); - // mSkipCurrentFrame is set to true in dataCallbackTimestamp() if the current // frame needs to be skipped and this function just returns the value of mSkipCurrentFrame. virtual bool skipCurrentFrame(int64_t timestampUs); diff --git a/include/media/stagefright/MPEG2TSWriter.h b/include/media/stagefright/MPEG2TSWriter.h index e4c1c49a72d6..a7c9ecf7c504 100644 --- a/include/media/stagefright/MPEG2TSWriter.h +++ b/include/media/stagefright/MPEG2TSWriter.h @@ -37,7 +37,7 @@ struct MPEG2TSWriter : public MediaWriter { virtual status_t addSource(const sp<MediaSource> &source); virtual status_t start(MetaData *param = NULL); - virtual status_t stop(); + virtual status_t stop() { return reset(); } virtual status_t pause(); virtual bool reachedEOS(); virtual status_t dump(int fd, const Vector<String16>& args); @@ -78,6 +78,7 @@ private: void writeAccessUnit(int32_t sourceIndex, const sp<ABuffer> &buffer); ssize_t internalWrite(const void *data, size_t size); + status_t reset(); DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSWriter); }; diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h index 77166edadbff..0409b30746f5 100644 --- a/include/media/stagefright/MPEG4Writer.h +++ b/include/media/stagefright/MPEG4Writer.h @@ -37,7 +37,7 @@ public: virtual status_t addSource(const sp<MediaSource> &source); virtual status_t start(MetaData *param = NULL); - virtual status_t stop(); + virtual status_t stop() { return reset(); } virtual status_t pause(); virtual bool reachedEOS(); virtual status_t dump(int fd, const Vector<String16>& args); @@ -184,6 +184,7 @@ private: void writeLongitude(int degreex10000); void sendSessionSummary(); void release(); + status_t reset(); MPEG4Writer(const MPEG4Writer &); MPEG4Writer &operator=(const MPEG4Writer &); diff --git a/include/media/stagefright/SurfaceMediaSource.h b/include/media/stagefright/SurfaceMediaSource.h index d0940bb64242..54baab6ba9c3 100644 --- a/include/media/stagefright/SurfaceMediaSource.h +++ b/include/media/stagefright/SurfaceMediaSource.h @@ -58,7 +58,7 @@ public: // For the MediaSource interface for use by StageFrightRecorder: virtual status_t start(MetaData *params = NULL); - virtual status_t stop(); + virtual status_t stop() { return reset(); } virtual status_t read( MediaBuffer **buffer, const ReadOptions *options = NULL); virtual sp<MetaData> getFormat(); @@ -359,6 +359,8 @@ private: Condition mFrameAvailableCondition; Condition mFrameCompleteCondition; + status_t reset(); + // Avoid copying and equating and default constructor DISALLOW_IMPLICIT_CONSTRUCTORS(SurfaceMediaSource); }; diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h index ffc546ee6cf8..dd97ce48d23e 100644 --- a/include/private/media/AudioTrackShared.h +++ b/include/private/media/AudioTrackShared.h @@ -76,7 +76,9 @@ struct audio_track_cblk_t // Left channel is in [0:15], right channel is in [16:31]. // Always read and write the combined pair atomically. // For AudioTrack only, not used by AudioRecord. - uint32_t volumeLR; +private: + uint32_t mVolumeLR; +public: uint32_t sampleRate; // NOTE: audio_track_cblk_t::frameSize is not equal to AudioTrack::frameSize() for @@ -116,6 +118,17 @@ public: uint16_t getSendLevel_U4_12() const { return mSendLevel; } + + // for AudioTrack client only, caller must limit to 0 <= volumeLR <= 0x10001000 + void setVolumeLR(uint32_t volumeLR) { + mVolumeLR = volumeLR; + } + + // for AudioFlinger only; the return value must be validated by the caller + uint32_t getVolumeLR() const { + return mVolumeLR; + } + }; diff --git a/include/ui/Region.h b/include/ui/Region.h index 6c9a6203e793..f242f18eb251 100644 --- a/include/ui/Region.h +++ b/include/ui/Region.h @@ -55,43 +55,51 @@ public: void set(uint32_t w, uint32_t h); Region& orSelf(const Rect& rhs); + Region& xorSelf(const Rect& rhs); Region& andSelf(const Rect& rhs); Region& subtractSelf(const Rect& rhs); // boolean operators, applied on this Region& orSelf(const Region& rhs); + Region& xorSelf(const Region& rhs); Region& andSelf(const Region& rhs); Region& subtractSelf(const Region& rhs); // boolean operators const Region merge(const Rect& rhs) const; + const Region mergeExclusive(const Rect& rhs) const; const Region intersect(const Rect& rhs) const; const Region subtract(const Rect& rhs) const; // boolean operators const Region merge(const Region& rhs) const; + const Region mergeExclusive(const Region& rhs) const; const Region intersect(const Region& rhs) const; const Region subtract(const Region& rhs) const; // these translate rhs first Region& translateSelf(int dx, int dy); Region& orSelf(const Region& rhs, int dx, int dy); + Region& xorSelf(const Region& rhs, int dx, int dy); Region& andSelf(const Region& rhs, int dx, int dy); Region& subtractSelf(const Region& rhs, int dx, int dy); // these translate rhs first const Region translate(int dx, int dy) const; const Region merge(const Region& rhs, int dx, int dy) const; + const Region mergeExclusive(const Region& rhs, int dx, int dy) const; const Region intersect(const Region& rhs, int dx, int dy) const; const Region subtract(const Region& rhs, int dx, int dy) const; // convenience operators overloads inline const Region operator | (const Region& rhs) const; + inline const Region operator ^ (const Region& rhs) const; inline const Region operator & (const Region& rhs) const; inline const Region operator - (const Region& rhs) const; inline const Region operator + (const Point& pt) const; inline Region& operator |= (const Region& rhs); + inline Region& operator ^= (const Region& rhs); inline Region& operator &= (const Region& rhs); inline Region& operator -= (const Region& rhs); inline Region& operator += (const Point& pt); @@ -158,6 +166,9 @@ private: const Region Region::operator | (const Region& rhs) const { return merge(rhs); } +const Region Region::operator ^ (const Region& rhs) const { + return mergeExclusive(rhs); +} const Region Region::operator & (const Region& rhs) const { return intersect(rhs); } @@ -172,6 +183,9 @@ const Region Region::operator + (const Point& pt) const { Region& Region::operator |= (const Region& rhs) { return orSelf(rhs); } +Region& Region::operator ^= (const Region& rhs) { + return xorSelf(rhs); +} Region& Region::operator &= (const Region& rhs) { return andSelf(rhs); } diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index b741ed6142bd..c496da6bac56 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -444,23 +444,31 @@ public: void uninit(); + // Return string entry as UTF16; if the pool is UTF8, the string will + // be converted before returning. inline const char16_t* stringAt(const ResStringPool_ref& ref, size_t* outLen) const { return stringAt(ref.index, outLen); } const char16_t* stringAt(size_t idx, size_t* outLen) const; + // Note: returns null if the string pool is not UTF8. const char* string8At(size_t idx, size_t* outLen) const; + // Return string whether the pool is UTF8 or UTF16. Does not allow you + // to distinguish null. + const String8 string8ObjectAt(size_t idx) const; + const ResStringPool_span* styleAt(const ResStringPool_ref& ref) const; const ResStringPool_span* styleAt(size_t idx) const; ssize_t indexOfString(const char16_t* str, size_t strLen) const; size_t size() const; + size_t styleCount() const; + size_t bytes() const; -#ifndef HAVE_ANDROID_OS + bool isSorted() const; bool isUTF8() const; -#endif private: status_t mError; @@ -746,7 +754,9 @@ private: /** * Header for a resource table. Its data contains a series of * additional chunks: - * * A ResStringPool_header containing all table values. + * * A ResStringPool_header containing all table values. This string pool + * contains all of the string values in the entire resource table (not + * the names of entries or type identifiers however). * * One or more ResTable_package chunks. * * Specific entries within a resource table can be uniquely identified @@ -984,68 +994,15 @@ struct ResTable_config uint32_t screenSizeDp; }; - inline void copyFromDeviceNoSwap(const ResTable_config& o) { - const size_t size = dtohl(o.size); - if (size >= sizeof(ResTable_config)) { - *this = o; - } else { - memcpy(this, &o, size); - memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size); - } - } + void copyFromDeviceNoSwap(const ResTable_config& o); - inline void copyFromDtoH(const ResTable_config& o) { - copyFromDeviceNoSwap(o); - size = sizeof(ResTable_config); - mcc = dtohs(mcc); - mnc = dtohs(mnc); - density = dtohs(density); - screenWidth = dtohs(screenWidth); - screenHeight = dtohs(screenHeight); - sdkVersion = dtohs(sdkVersion); - minorVersion = dtohs(minorVersion); - smallestScreenWidthDp = dtohs(smallestScreenWidthDp); - screenWidthDp = dtohs(screenWidthDp); - screenHeightDp = dtohs(screenHeightDp); - } - - inline void swapHtoD() { - size = htodl(size); - mcc = htods(mcc); - mnc = htods(mnc); - density = htods(density); - screenWidth = htods(screenWidth); - screenHeight = htods(screenHeight); - sdkVersion = htods(sdkVersion); - minorVersion = htods(minorVersion); - smallestScreenWidthDp = htods(smallestScreenWidthDp); - screenWidthDp = htods(screenWidthDp); - screenHeightDp = htods(screenHeightDp); - } - - inline int compare(const ResTable_config& o) const { - int32_t diff = (int32_t)(imsi - o.imsi); - if (diff != 0) return diff; - diff = (int32_t)(locale - o.locale); - if (diff != 0) return diff; - diff = (int32_t)(screenType - o.screenType); - if (diff != 0) return diff; - diff = (int32_t)(input - o.input); - if (diff != 0) return diff; - diff = (int32_t)(screenSize - o.screenSize); - if (diff != 0) return diff; - diff = (int32_t)(version - o.version); - if (diff != 0) return diff; - diff = (int32_t)(screenLayout - o.screenLayout); - if (diff != 0) return diff; - diff = (int32_t)(uiMode - o.uiMode); - if (diff != 0) return diff; - diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp); - if (diff != 0) return diff; - diff = (int32_t)(screenSizeDp - o.screenSizeDp); - return (int)diff; - } + void copyFromDtoH(const ResTable_config& o); + void swapHtoD(); + + int compare(const ResTable_config& o) const; + int compareLogical(const ResTable_config& o) const; + // Flags indicating a set of config values. These flag constants must // match the corresponding ones in android.content.pm.ActivityInfo and // attrs_manifest.xml. @@ -1068,158 +1025,10 @@ struct ResTable_config // Compare two configuration, returning CONFIG_* flags set for each value // that is different. - inline int diff(const ResTable_config& o) const { - int diffs = 0; - if (mcc != o.mcc) diffs |= CONFIG_MCC; - if (mnc != o.mnc) diffs |= CONFIG_MNC; - if (locale != o.locale) diffs |= CONFIG_LOCALE; - if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION; - if (density != o.density) diffs |= CONFIG_DENSITY; - if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN; - if (((inputFlags^o.inputFlags)&(MASK_KEYSHIDDEN|MASK_NAVHIDDEN)) != 0) - diffs |= CONFIG_KEYBOARD_HIDDEN; - if (keyboard != o.keyboard) diffs |= CONFIG_KEYBOARD; - if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION; - if (screenSize != o.screenSize) diffs |= CONFIG_SCREEN_SIZE; - if (version != o.version) diffs |= CONFIG_VERSION; - if (screenLayout != o.screenLayout) diffs |= CONFIG_SCREEN_LAYOUT; - if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE; - if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE; - if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE; - return diffs; - } + int diff(const ResTable_config& o) const; // Return true if 'this' is more specific than 'o'. - inline bool - isMoreSpecificThan(const ResTable_config& o) const { - // The order of the following tests defines the importance of one - // configuration parameter over another. Those tests first are more - // important, trumping any values in those following them. - if (imsi || o.imsi) { - if (mcc != o.mcc) { - if (!mcc) return false; - if (!o.mcc) return true; - } - - if (mnc != o.mnc) { - if (!mnc) return false; - if (!o.mnc) return true; - } - } - - if (locale || o.locale) { - if (language[0] != o.language[0]) { - if (!language[0]) return false; - if (!o.language[0]) return true; - } - - if (country[0] != o.country[0]) { - if (!country[0]) return false; - if (!o.country[0]) return true; - } - } - - if (smallestScreenWidthDp || o.smallestScreenWidthDp) { - if (smallestScreenWidthDp != o.smallestScreenWidthDp) { - if (!smallestScreenWidthDp) return false; - if (!o.smallestScreenWidthDp) return true; - } - } - - if (screenSizeDp || o.screenSizeDp) { - if (screenWidthDp != o.screenWidthDp) { - if (!screenWidthDp) return false; - if (!o.screenWidthDp) return true; - } - - if (screenHeightDp != o.screenHeightDp) { - if (!screenHeightDp) return false; - if (!o.screenHeightDp) return true; - } - } - - if (screenLayout || o.screenLayout) { - if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0) { - if (!(screenLayout & MASK_SCREENSIZE)) return false; - if (!(o.screenLayout & MASK_SCREENSIZE)) return true; - } - if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0) { - if (!(screenLayout & MASK_SCREENLONG)) return false; - if (!(o.screenLayout & MASK_SCREENLONG)) return true; - } - } - - if (orientation != o.orientation) { - if (!orientation) return false; - if (!o.orientation) return true; - } - - if (uiMode || o.uiMode) { - if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0) { - if (!(uiMode & MASK_UI_MODE_TYPE)) return false; - if (!(o.uiMode & MASK_UI_MODE_TYPE)) return true; - } - if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0) { - if (!(uiMode & MASK_UI_MODE_NIGHT)) return false; - if (!(o.uiMode & MASK_UI_MODE_NIGHT)) return true; - } - } - - // density is never 'more specific' - // as the default just equals 160 - - if (touchscreen != o.touchscreen) { - if (!touchscreen) return false; - if (!o.touchscreen) return true; - } - - if (input || o.input) { - if (((inputFlags^o.inputFlags) & MASK_KEYSHIDDEN) != 0) { - if (!(inputFlags & MASK_KEYSHIDDEN)) return false; - if (!(o.inputFlags & MASK_KEYSHIDDEN)) return true; - } - - if (((inputFlags^o.inputFlags) & MASK_NAVHIDDEN) != 0) { - if (!(inputFlags & MASK_NAVHIDDEN)) return false; - if (!(o.inputFlags & MASK_NAVHIDDEN)) return true; - } - - if (keyboard != o.keyboard) { - if (!keyboard) return false; - if (!o.keyboard) return true; - } - - if (navigation != o.navigation) { - if (!navigation) return false; - if (!o.navigation) return true; - } - } - - if (screenSize || o.screenSize) { - if (screenWidth != o.screenWidth) { - if (!screenWidth) return false; - if (!o.screenWidth) return true; - } - - if (screenHeight != o.screenHeight) { - if (!screenHeight) return false; - if (!o.screenHeight) return true; - } - } - - if (version || o.version) { - if (sdkVersion != o.sdkVersion) { - if (!sdkVersion) return false; - if (!o.sdkVersion) return true; - } - - if (minorVersion != o.minorVersion) { - if (!minorVersion) return false; - if (!o.minorVersion) return true; - } - } - return false; - } + bool isMoreSpecificThan(const ResTable_config& o) const; // Return true if 'this' is a better match than 'o' for the 'requested' // configuration. This assumes that match() has already been used to @@ -1231,222 +1040,7 @@ struct ResTable_config // they are not equal then one must be generic because only generic and // '==requested' will pass the match() call. So if this is not generic, // it wins. If this IS generic, o wins (return false). - inline bool - isBetterThan(const ResTable_config& o, - const ResTable_config* requested) const { - if (requested) { - if (imsi || o.imsi) { - if ((mcc != o.mcc) && requested->mcc) { - return (mcc); - } - - if ((mnc != o.mnc) && requested->mnc) { - return (mnc); - } - } - - if (locale || o.locale) { - if ((language[0] != o.language[0]) && requested->language[0]) { - return (language[0]); - } - - if ((country[0] != o.country[0]) && requested->country[0]) { - return (country[0]); - } - } - - if (smallestScreenWidthDp || o.smallestScreenWidthDp) { - // The configuration closest to the actual size is best. - // We assume that larger configs have already been filtered - // out at this point. That means we just want the largest one. - return smallestScreenWidthDp >= o.smallestScreenWidthDp; - } - - if (screenSizeDp || o.screenSizeDp) { - // "Better" is based on the sum of the difference between both - // width and height from the requested dimensions. We are - // assuming the invalid configs (with smaller dimens) have - // already been filtered. Note that if a particular dimension - // is unspecified, we will end up with a large value (the - // difference between 0 and the requested dimension), which is - // good since we will prefer a config that has specified a - // dimension value. - int myDelta = 0, otherDelta = 0; - if (requested->screenWidthDp) { - myDelta += requested->screenWidthDp - screenWidthDp; - otherDelta += requested->screenWidthDp - o.screenWidthDp; - } - if (requested->screenHeightDp) { - myDelta += requested->screenHeightDp - screenHeightDp; - otherDelta += requested->screenHeightDp - o.screenHeightDp; - } - //ALOGI("Comparing this %dx%d to other %dx%d in %dx%d: myDelta=%d otherDelta=%d", - // screenWidthDp, screenHeightDp, o.screenWidthDp, o.screenHeightDp, - // requested->screenWidthDp, requested->screenHeightDp, myDelta, otherDelta); - return (myDelta <= otherDelta); - } - - if (screenLayout || o.screenLayout) { - if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0 - && (requested->screenLayout & MASK_SCREENSIZE)) { - // A little backwards compatibility here: undefined is - // considered equivalent to normal. But only if the - // requested size is at least normal; otherwise, small - // is better than the default. - int mySL = (screenLayout & MASK_SCREENSIZE); - int oSL = (o.screenLayout & MASK_SCREENSIZE); - int fixedMySL = mySL; - int fixedOSL = oSL; - if ((requested->screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) { - if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL; - if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL; - } - // For screen size, the best match is the one that is - // closest to the requested screen size, but not over - // (the not over part is dealt with in match() below). - if (fixedMySL == fixedOSL) { - // If the two are the same, but 'this' is actually - // undefined, then the other is really a better match. - if (mySL == 0) return false; - return true; - } - return fixedMySL >= fixedOSL; - } - if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0 - && (requested->screenLayout & MASK_SCREENLONG)) { - return (screenLayout & MASK_SCREENLONG); - } - } - - if ((orientation != o.orientation) && requested->orientation) { - return (orientation); - } - - if (uiMode || o.uiMode) { - if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0 - && (requested->uiMode & MASK_UI_MODE_TYPE)) { - return (uiMode & MASK_UI_MODE_TYPE); - } - if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0 - && (requested->uiMode & MASK_UI_MODE_NIGHT)) { - return (uiMode & MASK_UI_MODE_NIGHT); - } - } - - if (screenType || o.screenType) { - if (density != o.density) { - // density is tough. Any density is potentially useful - // because the system will scale it. Scaling down - // is generally better than scaling up. - // Default density counts as 160dpi (the system default) - // TODO - remove 160 constants - int h = (density?density:160); - int l = (o.density?o.density:160); - bool bImBigger = true; - if (l > h) { - int t = h; - h = l; - l = t; - bImBigger = false; - } - - int reqValue = (requested->density?requested->density:160); - if (reqValue >= h) { - // requested value higher than both l and h, give h - return bImBigger; - } - if (l >= reqValue) { - // requested value lower than both l and h, give l - return !bImBigger; - } - // saying that scaling down is 2x better than up - if (((2 * l) - reqValue) * h > reqValue * reqValue) { - return !bImBigger; - } else { - return bImBigger; - } - } - - if ((touchscreen != o.touchscreen) && requested->touchscreen) { - return (touchscreen); - } - } - - if (input || o.input) { - const int keysHidden = inputFlags & MASK_KEYSHIDDEN; - const int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN; - if (keysHidden != oKeysHidden) { - const int reqKeysHidden = - requested->inputFlags & MASK_KEYSHIDDEN; - if (reqKeysHidden) { - - if (!keysHidden) return false; - if (!oKeysHidden) return true; - // For compatibility, we count KEYSHIDDEN_NO as being - // the same as KEYSHIDDEN_SOFT. Here we disambiguate - // these by making an exact match more specific. - if (reqKeysHidden == keysHidden) return true; - if (reqKeysHidden == oKeysHidden) return false; - } - } - - const int navHidden = inputFlags & MASK_NAVHIDDEN; - const int oNavHidden = o.inputFlags & MASK_NAVHIDDEN; - if (navHidden != oNavHidden) { - const int reqNavHidden = - requested->inputFlags & MASK_NAVHIDDEN; - if (reqNavHidden) { - - if (!navHidden) return false; - if (!oNavHidden) return true; - } - } - - if ((keyboard != o.keyboard) && requested->keyboard) { - return (keyboard); - } - - if ((navigation != o.navigation) && requested->navigation) { - return (navigation); - } - } - - if (screenSize || o.screenSize) { - // "Better" is based on the sum of the difference between both - // width and height from the requested dimensions. We are - // assuming the invalid configs (with smaller sizes) have - // already been filtered. Note that if a particular dimension - // is unspecified, we will end up with a large value (the - // difference between 0 and the requested dimension), which is - // good since we will prefer a config that has specified a - // size value. - int myDelta = 0, otherDelta = 0; - if (requested->screenWidth) { - myDelta += requested->screenWidth - screenWidth; - otherDelta += requested->screenWidth - o.screenWidth; - } - if (requested->screenHeight) { - myDelta += requested->screenHeight - screenHeight; - otherDelta += requested->screenHeight - o.screenHeight; - } - return (myDelta <= otherDelta); - } - - if (version || o.version) { - if ((sdkVersion != o.sdkVersion) && requested->sdkVersion) { - return (sdkVersion > o.sdkVersion); - } - - if ((minorVersion != o.minorVersion) && - requested->minorVersion) { - return (minorVersion); - } - } - - return false; - } - return isMoreSpecificThan(o); - } + bool isBetterThan(const ResTable_config& o, const ResTable_config* requested) const; // Return true if 'this' can be considered a match for the parameters in // 'settings'. @@ -1454,150 +1048,11 @@ struct ResTable_config // but a request for the default should not match odd specifics // (ie, request with no mcc should not match a particular mcc's data) // settings is the requested settings - inline bool match(const ResTable_config& settings) const { - if (imsi != 0) { - if (mcc != 0 && mcc != settings.mcc) { - return false; - } - if (mnc != 0 && mnc != settings.mnc) { - return false; - } - } - if (locale != 0) { - if (language[0] != 0 - && (language[0] != settings.language[0] - || language[1] != settings.language[1])) { - return false; - } - if (country[0] != 0 - && (country[0] != settings.country[0] - || country[1] != settings.country[1])) { - return false; - } - } - if (screenConfig != 0) { - const int screenSize = screenLayout&MASK_SCREENSIZE; - const int setScreenSize = settings.screenLayout&MASK_SCREENSIZE; - // Any screen sizes for larger screens than the setting do not - // match. - if (screenSize != 0 && screenSize > setScreenSize) { - return false; - } - - const int screenLong = screenLayout&MASK_SCREENLONG; - const int setScreenLong = settings.screenLayout&MASK_SCREENLONG; - if (screenLong != 0 && screenLong != setScreenLong) { - return false; - } - - const int uiModeType = uiMode&MASK_UI_MODE_TYPE; - const int setUiModeType = settings.uiMode&MASK_UI_MODE_TYPE; - if (uiModeType != 0 && uiModeType != setUiModeType) { - return false; - } - - const int uiModeNight = uiMode&MASK_UI_MODE_NIGHT; - const int setUiModeNight = settings.uiMode&MASK_UI_MODE_NIGHT; - if (uiModeNight != 0 && uiModeNight != setUiModeNight) { - return false; - } - - if (smallestScreenWidthDp != 0 - && smallestScreenWidthDp > settings.smallestScreenWidthDp) { - return false; - } - } - if (screenSizeDp != 0) { - if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) { - //ALOGI("Filtering out width %d in requested %d", screenWidthDp, settings.screenWidthDp); - return false; - } - if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) { - //ALOGI("Filtering out height %d in requested %d", screenHeightDp, settings.screenHeightDp); - return false; - } - } - if (screenType != 0) { - if (orientation != 0 && orientation != settings.orientation) { - return false; - } - // density always matches - we can scale it. See isBetterThan - if (touchscreen != 0 && touchscreen != settings.touchscreen) { - return false; - } - } - if (input != 0) { - const int keysHidden = inputFlags&MASK_KEYSHIDDEN; - const int setKeysHidden = settings.inputFlags&MASK_KEYSHIDDEN; - if (keysHidden != 0 && keysHidden != setKeysHidden) { - // For compatibility, we count a request for KEYSHIDDEN_NO as also - // matching the more recent KEYSHIDDEN_SOFT. Basically - // KEYSHIDDEN_NO means there is some kind of keyboard available. - //ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden); - if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) { - //ALOGI("No match!"); - return false; - } - } - const int navHidden = inputFlags&MASK_NAVHIDDEN; - const int setNavHidden = settings.inputFlags&MASK_NAVHIDDEN; - if (navHidden != 0 && navHidden != setNavHidden) { - return false; - } - if (keyboard != 0 && keyboard != settings.keyboard) { - return false; - } - if (navigation != 0 && navigation != settings.navigation) { - return false; - } - } - if (screenSize != 0) { - if (screenWidth != 0 && screenWidth > settings.screenWidth) { - return false; - } - if (screenHeight != 0 && screenHeight > settings.screenHeight) { - return false; - } - } - if (version != 0) { - if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) { - return false; - } - if (minorVersion != 0 && minorVersion != settings.minorVersion) { - return false; - } - } - return true; - } + bool match(const ResTable_config& settings) const; - void getLocale(char str[6]) const { - memset(str, 0, 6); - if (language[0]) { - str[0] = language[0]; - str[1] = language[1]; - if (country[0]) { - str[2] = '_'; - str[3] = country[0]; - str[4] = country[1]; - } - } - } + void getLocale(char str[6]) const; - String8 toString() const { - char buf[200]; - sprintf(buf, "imsi=%d/%d lang=%c%c reg=%c%c orient=%d touch=%d dens=%d " - "kbd=%d nav=%d input=%d ssz=%dx%d sw%ddp w%ddp h%ddp sz=%d long=%d " - "ui=%d night=%d vers=%d.%d", - mcc, mnc, - language[0] ? language[0] : '-', language[1] ? language[1] : '-', - country[0] ? country[0] : '-', country[1] ? country[1] : '-', - orientation, touchscreen, density, keyboard, navigation, inputFlags, - screenWidth, screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp, - screenLayout&MASK_SCREENSIZE, screenLayout&MASK_SCREENLONG, - uiMode&MASK_UI_MODE_TYPE, uiMode&MASK_UI_MODE_NIGHT, - sdkVersion, minorVersion); - return String8(buf); - } + String8 toString() const; }; /** @@ -2056,8 +1511,14 @@ public: const char16_t* getBasePackageName(size_t idx) const; uint32_t getBasePackageId(size_t idx) const; + // Return the number of resource tables that the object contains. size_t getTableCount() const; + // Return the values string pool for the resource table at the given + // index. This string pool contains all of the strings for values + // contained in the resource table -- that is the item values themselves, + // but not the names their entries or types. const ResStringPool* getTableStringBlock(size_t index) const; + // Return unique cookie identifier for the given resource table. void* getTableCookie(size_t index) const; // Return the configurations (ResTable_config) that we know about diff --git a/include/utils/threads.h b/include/utils/threads.h index ab3e8cdb638d..b4a8b7c0e12b 100644 --- a/include/utils/threads.h +++ b/include/utils/threads.h @@ -526,6 +526,12 @@ public: // Do not call from this object's thread; will return WOULD_BLOCK in that case. status_t join(); +#ifdef HAVE_ANDROID_OS + // Return the thread's kernel ID, same as the thread itself calling gettid() or + // androidGetTid(), or -1 if the thread is not running. + pid_t getTid() const; +#endif + protected: // exitPending() returns true if requestExit() has been called. bool exitPending() const; @@ -551,8 +557,10 @@ private: volatile bool mExitPending; volatile bool mRunning; sp<Thread> mHoldSelf; -#if HAVE_ANDROID_OS - int mTid; +#ifdef HAVE_ANDROID_OS + // legacy for debugging, not used by getTid() as it is set by the child thread + // and so is not initialized until the child reaches that point + pid_t mTid; #endif }; diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp index 629b89926d5a..b578a6ce9a50 100644 --- a/libs/binder/IPCThreadState.cpp +++ b/libs/binder/IPCThreadState.cpp @@ -371,6 +371,11 @@ int IPCThreadState::getCallingUid() return mCallingUid; } +int IPCThreadState::getOrigCallingUid() +{ + return mOrigCallingUid; +} + int64_t IPCThreadState::clearCallingIdentity() { int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid; @@ -641,6 +646,7 @@ IPCThreadState::IPCThreadState() { pthread_setspecific(gTLS, this); clearCaller(); + mOrigCallingUid = mCallingUid; mIn.setDataCapacity(256); mOut.setDataCapacity(256); } @@ -987,6 +993,7 @@ status_t IPCThreadState::executeCommand(int32_t cmd) mCallingPid = tr.sender_pid; mCallingUid = tr.sender_euid; + mOrigCallingUid = tr.sender_euid; int curPrio = getpriority(PRIO_PROCESS, mMyThreadId); if (gDisableBackgroundScheduling) { @@ -1045,6 +1052,7 @@ status_t IPCThreadState::executeCommand(int32_t cmd) mCallingPid = origPid; mCallingUid = origUid; + mOrigCallingUid = origUid; IF_LOG_TRANSACTIONS() { TextOutput::Bundle _b(alog); diff --git a/libs/gui/BitTube.cpp b/libs/gui/BitTube.cpp index 785da39b955d..55f41784215b 100644 --- a/libs/gui/BitTube.cpp +++ b/libs/gui/BitTube.cpp @@ -17,8 +17,9 @@ #include <stdint.h> #include <sys/types.h> -#include <unistd.h> #include <fcntl.h> +#include <signal.h> +#include <unistd.h> #include <utils/Errors.h> @@ -38,6 +39,8 @@ BitTube::BitTube() mSendFd = fds[1]; fcntl(mReceiveFd, F_SETFL, O_NONBLOCK); fcntl(mSendFd, F_SETFL, O_NONBLOCK); + // ignore SIGPIPE, we handle write errors through EPIPE instead + signal(SIGPIPE, SIG_IGN); } else { mReceiveFd = -errno; ALOGE("BitTube: pipe creation failed (%s)", strerror(-mReceiveFd)); diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp index 3b3ccaaf74c5..6a4763d5a72b 100644 --- a/libs/gui/DisplayEventReceiver.cpp +++ b/libs/gui/DisplayEventReceiver.cpp @@ -80,7 +80,13 @@ status_t DisplayEventReceiver::requestNextVsync() { ssize_t DisplayEventReceiver::getEvents(DisplayEventReceiver::Event* events, size_t count) { - ssize_t size = mDataChannel->read(events, sizeof(events[0])*count); + return DisplayEventReceiver::getEvents(mDataChannel, events, count); +} + +ssize_t DisplayEventReceiver::getEvents(const sp<BitTube>& dataChannel, + Event* events, size_t count) +{ + ssize_t size = dataChannel->read(events, sizeof(events[0])*count); ALOGE_IF(size<0, "DisplayEventReceiver::getEvents error (%s)", strerror(-size)); diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 95e0a181333e..5ec3983cf728 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -26,6 +26,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) ShapeCache.cpp \ SkiaColorFilter.cpp \ SkiaShader.cpp \ + Snapshot.cpp \ TextureCache.cpp \ TextDropShadowCache.cpp diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index d1af1a3161a8..0ef846903343 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -55,6 +55,16 @@ Caches::Caches(): Singleton<Caches>(), mInitialized(false) { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + if (extensions.hasDebugMarker()) { + eventMark = glInsertEventMarkerEXT; + startMark = glPushGroupMarkerEXT; + endMark = glPopGroupMarkerEXT; + } else { + eventMark = eventMarkNull; + startMark = startMarkNull; + endMark = endMarkNull; + } + init(); mDebugLevel = readDebugLevel(); diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 409584fb0e06..f8c7bccad628 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -240,7 +240,15 @@ public: GammaFontRenderer fontRenderer; ResourceCache resourceCache; + PFNGLINSERTEVENTMARKEREXTPROC eventMark; + PFNGLPUSHGROUPMARKEREXTPROC startMark; + PFNGLPOPGROUPMARKEREXTPROC endMark; + private: + static void eventMarkNull(GLsizei length, const GLchar *marker) { } + static void startMarkNull(GLsizei length, const GLchar *marker) { } + static void endMarkNull() { } + GLuint mCurrentBuffer; GLuint mCurrentIndicesBuffer; void* mCurrentPositionPointer; diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h index 0ad0c2a71971..16a3d73a121f 100644 --- a/libs/hwui/Debug.h +++ b/libs/hwui/Debug.h @@ -62,6 +62,9 @@ // Turn on to display debug info about the layer renderer #define DEBUG_LAYER_RENDERER 0 +// Turn on to enable additional debugging in the font renderers +#define DEBUG_FONT_RENDERER 0 + // Turn on to dump display list state #define DEBUG_DISPLAY_LIST 0 diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index ee935e44381f..1a11fbcfad68 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -19,9 +19,10 @@ #include "DisplayListLogBuffer.h" #include "DisplayListRenderer.h" -#include <utils/String8.h> #include "Caches.h" +#include <utils/String8.h> + namespace android { namespace uirenderer { @@ -217,7 +218,7 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { indent[i] = ' '; } indent[count] = '\0'; - ALOGD("%sStart display list (%p)", (char*) indent + 2, this); + ALOGD("%sStart display list (%p, %s)", (char*) indent + 2, this, mName.string()); int saveCount = renderer.getSaveCount() - 1; @@ -562,9 +563,11 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) indent[i] = ' '; } indent[count] = '\0'; - DISPLAY_LIST_LOGD("%sStart display list (%p)", (char*) indent + 2, this); + DISPLAY_LIST_LOGD("%sStart display list (%p, %s)", (char*) indent + 2, this, mName.string()); #endif + renderer.startMark(mName.string()); + DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance(); int saveCount = renderer.getSaveCount() - 1; while (!mReader.eof()) { @@ -575,7 +578,9 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) case DrawGLFunction: { Functor *functor = (Functor *) getInt(); DISPLAY_LIST_LOGD("%s%s %p", (char*) indent, OP_NAMES[op], functor); + renderer.startMark("GL functor"); needsInvalidate |= renderer.callDrawGLFunction(functor, dirty); + renderer.endMark(); } break; case Save: { @@ -934,6 +939,8 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) } } + renderer.endMark(); + DISPLAY_LIST_LOGD("%sDone, returning %d", (char*) indent + 2, needsInvalidate); return needsInvalidate; } diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 6fe4205b7443..46506e4e9a96 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -28,6 +28,8 @@ #include <cutils/compiler.h> +#include <utils/String8.h> + #include "DisplayListLogBuffer.h" #include "OpenGLRenderer.h" #include "utils/Functor.h" @@ -128,6 +130,12 @@ public: return mIsRenderable; } + void setName(const char* name) { + if (name) { + mName.setTo(name); + } + } + private: void init(); @@ -224,6 +232,8 @@ private: size_t mSize; bool mIsRenderable; + + String8 mName; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index 1069e3f7fbd0..f11fecc7ae9c 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -67,6 +67,7 @@ public: mHasNPot = hasExtension("GL_OES_texture_npot"); mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch"); mHasDiscardFramebuffer = hasExtension("GL_EXT_discard_framebuffer"); + mHasDebugMarker = hasExtension("GL_EXT_debug_marker"); const char* vendor = (const char*) glGetString(GL_VENDOR); EXT_LOGD("Vendor: %s", vendor); @@ -82,6 +83,7 @@ public: inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } inline bool needsHighpTexCoords() const { return mNeedsHighpTexCoords; } inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; } + inline bool hasDebugMarker() const { return mHasDebugMarker; } bool hasExtension(const char* extension) const { const String8 s(extension); @@ -101,6 +103,7 @@ private: bool mNeedsHighpTexCoords; bool mHasFramebufferFetch; bool mHasDiscardFramebuffer; + bool mHasDebugMarker; }; // class Extensions }; // namespace uirenderer diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 74efda2576b0..3df105be19ea 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -151,10 +151,12 @@ void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, int32_t bX = 0, bY = 0; for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) { for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) { +#if DEBUG_FONT_RENDERER if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) { ALOGE("Skipping invalid index"); continue; } +#endif uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX]; bitmap[bY * bitmapW + bX] = tempCol; } @@ -226,7 +228,7 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len }; RenderGlyph render = gRenderGlyph[mode]; - if (positions == NULL) { + if (CC_LIKELY(positions == NULL)) { SkFixed prevRsbDelta = 0; float penX = x; diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index cc0e05ef914b..afae70fddb5d 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -124,9 +124,25 @@ OpenGLRenderer::~OpenGLRenderer() { } /////////////////////////////////////////////////////////////////////////////// +// Debug +/////////////////////////////////////////////////////////////////////////////// + +void OpenGLRenderer::startMark(const char* name) const { + mCaches.startMark(0, name); +} + +void OpenGLRenderer::endMark() const { + mCaches.endMark(); +} + +/////////////////////////////////////////////////////////////////////////////// // Setup /////////////////////////////////////////////////////////////////////////////// +uint32_t OpenGLRenderer::getStencilSize() { + return STENCIL_BUFFER_SIZE; +} + void OpenGLRenderer::setViewport(int width, int height) { mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1); @@ -738,7 +754,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { // TODO: See LayerRenderer.cpp::generateMesh() for important // information about this implementation - if (!layer->region.isEmpty()) { + if (CC_LIKELY(!layer->region.isEmpty())) { size_t count; const android::Rect* rects = layer->region.getArray(&count); @@ -1382,7 +1398,7 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint if (!texture) return; const AutoTexture autoCleanup(texture); - if (bitmap->getConfig() == SkBitmap::kA8_Config) { + if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) { drawAlphaBitmap(texture, left, top, paint); } else { drawTextureRect(left, top, right, bottom, texture, paint); @@ -1438,9 +1454,9 @@ void OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHei float bottom = FLT_MIN; #if RENDER_LAYERS_AS_REGIONS - bool hasActiveLayer = hasLayer(); + const bool hasActiveLayer = hasLayer(); #else - bool hasActiveLayer = false; + const bool hasActiveLayer = false; #endif // TODO: Support the colors array @@ -1525,7 +1541,7 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, texture->setWrap(GL_CLAMP_TO_EDGE, true); - if (mSnapshot->transform->isPureTranslate()) { + if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) { const float x = (int) floorf(dstLeft + mSnapshot->transform->getTranslateX() + 0.5f); const float y = (int) floorf(dstTop + mSnapshot->transform->getTranslateY() + 0.5f); @@ -1571,7 +1587,7 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(), right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors); - if (mesh && mesh->verticesCount > 0) { + if (CC_LIKELY(mesh && mesh->verticesCount > 0)) { const bool pureTranslate = mSnapshot->transform->isPureTranslate(); #if RENDER_LAYERS_AS_REGIONS // Mark the current layer dirty where we are going to draw the patch @@ -1581,7 +1597,7 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int const size_t count = mesh->quads.size(); for (size_t i = 0; i < count; i++) { const Rect& bounds = mesh->quads.itemAt(i); - if (pureTranslate) { + if (CC_LIKELY(pureTranslate)) { const float x = (int) floorf(bounds.left + offsetX + 0.5f); const float y = (int) floorf(bounds.top + offsetY + 0.5f); dirtyLayer(x, y, x + bounds.getWidth(), y + bounds.getHeight()); @@ -1593,7 +1609,7 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int } #endif - if (pureTranslate) { + if (CC_LIKELY(pureTranslate)) { const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f); const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f); @@ -1621,7 +1637,7 @@ void OpenGLRenderer::drawAARect(float left, float top, float right, float bottom float inverseScaleX = 1.0f; float inverseScaleY = 1.0f; // The quad that we use needs to account for scaling. - if (!mSnapshot->transform->isPureTranslate()) { + if (CC_UNLIKELY(!mSnapshot->transform->isPureTranslate())) { Matrix4 *mat = mSnapshot->transform; float m00 = mat->data[Matrix4::kScaleX]; float m01 = mat->data[Matrix4::kSkewY]; @@ -1727,7 +1743,7 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { // The quad that we use for AA and hairlines needs to account for scaling. For hairlines // the line on the screen should always be one pixel wide regardless of scale. For // AA lines, we only want one pixel of translucent boundary around the quad. - if (!mSnapshot->transform->isPureTranslate()) { + if (CC_UNLIKELY(!mSnapshot->transform->isPureTranslate())) { Matrix4 *mat = mSnapshot->transform; float m00 = mat->data[Matrix4::kScaleX]; float m01 = mat->data[Matrix4::kSkewY]; @@ -1735,8 +1751,8 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { float m10 = mat->data[Matrix4::kSkewX]; float m11 = mat->data[Matrix4::kScaleX]; float m12 = mat->data[6]; - float scaleX = sqrt(m00*m00 + m01*m01); - float scaleY = sqrt(m10*m10 + m11*m11); + float scaleX = sqrtf(m00 * m00 + m01 * m01); + float scaleY = sqrtf(m10 * m10 + m11 * m11); inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0; inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0; if (inverseScaleX != 1.0f || inverseScaleY != 1.0f) { @@ -1754,11 +1770,7 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { setupDrawColor(paint->getColor(), alpha); setupDrawColorFilter(); setupDrawShader(); - if (isAA) { - setupDrawBlending(true, mode); - } else { - setupDrawBlending(mode); - } + setupDrawBlending(isAA, mode); setupDrawProgram(); setupDrawModelViewIdentity(true); setupDrawColorUniforms(); @@ -1776,7 +1788,7 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { Vertex* vertices = &lines[0]; AAVertex wLines[verticesCount]; AAVertex* aaVertices = &wLines[0]; - if (!isAA) { + if (CC_UNLIKELY(!isAA)) { setupDrawVertices(vertices); } else { void* widthCoords = ((GLbyte*) aaVertices) + gVertexAAWidthOffset; @@ -2136,9 +2148,9 @@ void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count, Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); #if RENDER_LAYERS_AS_REGIONS - bool hasActiveLayer = hasLayer(); + const bool hasActiveLayer = hasLayer(); #else - bool hasActiveLayer = false; + const bool hasActiveLayer = false; #endif if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, @@ -2185,7 +2197,7 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, const float oldX = x; const float oldY = y; const bool pureTranslate = mSnapshot->transform->isPureTranslate(); - if (pureTranslate) { + if (CC_LIKELY(pureTranslate)) { x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f); y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f); } @@ -2202,7 +2214,7 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, SkXfermode::Mode mode; getAlphaAndMode(paint, &alpha, &mode); - if (mHasShadow) { + if (CC_UNLIKELY(mHasShadow)) { mCaches.activeTexture(0); mCaches.dropShadowCache.setFontRenderer(fontRenderer); @@ -2261,9 +2273,9 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); #if RENDER_LAYERS_AS_REGIONS - bool hasActiveLayer = hasLayer(); + const bool hasActiveLayer = hasLayer(); #else - bool hasActiveLayer = false; + const bool hasActiveLayer = false; #endif if (fontRenderer.renderText(paint, clip, text, 0, bytesCount, count, x, y, @@ -2310,7 +2322,7 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* paint) { layer->setAlpha(alpha, mode); #if RENDER_LAYERS_AS_REGIONS - if (!layer->region.isEmpty()) { + if (CC_LIKELY(!layer->region.isEmpty())) { if (layer->region.isRect()) { composeLayerRect(layer, layer->regionRect); } else if (layer->mesh) { @@ -2326,7 +2338,7 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* paint) { setupDrawPureColorUniforms(); setupDrawColorFilterUniforms(); setupDrawTexture(layer->getTexture()); - if (mSnapshot->transform->isPureTranslate()) { + if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) { x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f); y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f); @@ -2486,7 +2498,7 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float break; } - if (underlineWidth > 0.0f) { + if (CC_LIKELY(underlineWidth > 0.0f)) { const float textSize = paintCopy.getTextSize(); const float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); @@ -2555,7 +2567,7 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b texture->setWrap(GL_CLAMP_TO_EDGE, true); - if (mSnapshot->transform->isPureTranslate()) { + if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) { const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f); const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f); @@ -2615,8 +2627,8 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, // the blending, turn blending off here // If the blend mode cannot be implemented using shaders, fall // back to the default SrcOver blend mode instead - if (mode > SkXfermode::kScreen_Mode) { - if (mCaches.extensions.hasFramebufferFetch()) { + if CC_UNLIKELY((mode > SkXfermode::kScreen_Mode)) { + if (CC_UNLIKELY(mCaches.extensions.hasFramebufferFetch())) { description.framebufferMode = mode; description.swapSrcDst = swapSrcDst; diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index ae355bba4e9b..3c2d09e81769 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -141,6 +141,11 @@ public: SkPaint* filterPaint(SkPaint* paint); + ANDROID_API static uint32_t getStencilSize(); + + void startMark(const char* name) const; + void endMark() const; + protected: /** * Compose the layer defined in the current snapshot with the layer diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 2eae0f18bbb8..7854729be2da 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -37,6 +37,11 @@ // Textures used by layers must have dimensions multiples of this number #define LAYER_SIZE 64 +// Defines the size in bits of the stencil buffer +// Note: Only 1 bit is required for clipping but more bits are required +// to properly implement the winding fill rule when rasterizing paths +#define STENCIL_BUFFER_SIZE 0 + /** * Debug level for app developers. */ diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp new file mode 100644 index 000000000000..de2c6745cc97 --- /dev/null +++ b/libs/hwui/Snapshot.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2012 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 "Snapshot.h" + +#include <SkCanvas.h> + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// Constructors +/////////////////////////////////////////////////////////////////////////////// + +Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0), + invisible(false), empty(false) { + + transform = &mTransformRoot; + clipRect = &mClipRectRoot; + region = NULL; + clipRegion = NULL; +} + +/** + * Copies the specified snapshot/ The specified snapshot is stored as + * the previous snapshot. + */ +Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags): + flags(0), previous(s), layer(NULL), fbo(s->fbo), + invisible(s->invisible), empty(false), + viewport(s->viewport), height(s->height) { + + clipRegion = NULL; + + if (saveFlags & SkCanvas::kMatrix_SaveFlag) { + mTransformRoot.load(*s->transform); + transform = &mTransformRoot; + } else { + transform = s->transform; + } + + if (saveFlags & SkCanvas::kClip_SaveFlag) { + mClipRectRoot.set(*s->clipRect); + clipRect = &mClipRectRoot; +#if STENCIL_BUFFER_SIZE + if (s->clipRegion) { + mClipRegionRoot.merge(*s->clipRegion); + clipRegion = &mClipRegionRoot; + } +#endif + } else { + clipRect = s->clipRect; +#if STENCIL_BUFFER_SIZE + clipRegion = s->clipRegion; +#endif + } + + if (s->flags & Snapshot::kFlagFboTarget) { + flags |= Snapshot::kFlagFboTarget; + region = s->region; + } else { + region = NULL; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Clipping +/////////////////////////////////////////////////////////////////////////////// + +void Snapshot::ensureClipRegion() { +#if STENCIL_BUFFER_SIZE + if (!clipRegion) { + clipRegion = &mClipRegionRoot; + android::Rect tmp(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom); + clipRegion->set(tmp); + } +#endif +} + +void Snapshot::copyClipRectFromRegion() { +#if STENCIL_BUFFER_SIZE + if (!clipRegion->isEmpty()) { + android::Rect bounds(clipRegion->bounds()); + clipRect->set(bounds.left, bounds.top, bounds.right, bounds.bottom); + + if (clipRegion->isRect()) { + clipRegion->clear(); + clipRegion = NULL; + } + } else { + clipRect->setEmpty(); + clipRegion = NULL; + } +#endif +} + +bool Snapshot::clipRegionOr(float left, float top, float right, float bottom) { +#if STENCIL_BUFFER_SIZE + android::Rect tmp(left, top, right, bottom); + clipRegion->orSelf(tmp); + copyClipRectFromRegion(); + return true; +#else + return false; +#endif +} + +bool Snapshot::clipRegionXor(float left, float top, float right, float bottom) { +#if STENCIL_BUFFER_SIZE + android::Rect tmp(left, top, right, bottom); + clipRegion->xorSelf(tmp); + copyClipRectFromRegion(); + return true; +#else + return false; +#endif +} + +bool Snapshot::clipRegionAnd(float left, float top, float right, float bottom) { +#if STENCIL_BUFFER_SIZE + android::Rect tmp(left, top, right, bottom); + clipRegion->andSelf(tmp); + copyClipRectFromRegion(); + return true; +#else + return false; +#endif +} + +bool Snapshot::clipRegionNand(float left, float top, float right, float bottom) { +#if STENCIL_BUFFER_SIZE + android::Rect tmp(left, top, right, bottom); + clipRegion->subtractSelf(tmp); + copyClipRectFromRegion(); + return true; +#else + return false; +#endif +} + +bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) { + Rect r(left, top, right, bottom); + transform->mapRect(r); + return clipTransformed(r, op); +} + +bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) { + bool clipped = false; + + switch (op) { + case SkRegion::kDifference_Op: { + ensureClipRegion(); + clipped = clipRegionNand(r.left, r.top, r.right, r.bottom); + break; + } + case SkRegion::kIntersect_Op: { + if (CC_UNLIKELY(clipRegion)) { + clipped = clipRegionOr(r.left, r.top, r.right, r.bottom); + } else { + clipped = clipRect->intersect(r); + if (!clipped) { + clipRect->setEmpty(); + clipped = true; + } + } + break; + } + case SkRegion::kUnion_Op: { + if (CC_UNLIKELY(clipRegion)) { + clipped = clipRegionAnd(r.left, r.top, r.right, r.bottom); + } else { + clipped = clipRect->unionWith(r); + } + break; + } + case SkRegion::kXOR_Op: { + ensureClipRegion(); + clipped = clipRegionXor(r.left, r.top, r.right, r.bottom); + break; + } + case SkRegion::kReverseDifference_Op: { + // TODO!!!!!!! + break; + } + case SkRegion::kReplace_Op: { + setClip(r.left, r.top, r.right, r.bottom); + clipped = true; + break; + } + } + + if (clipped) { + flags |= Snapshot::kFlagClipSet; + } + + return clipped; +} + +void Snapshot::setClip(float left, float top, float right, float bottom) { + clipRect->set(left, top, right, bottom); +#if STENCIL_BUFFER_SIZE + if (clipRegion) { + clipRegion->clear(); + clipRegion = NULL; + } +#endif + flags |= Snapshot::kFlagClipSet; +} + +const Rect& Snapshot::getLocalClip() { + mat4 inverse; + inverse.loadInverse(*transform); + + mLocalClip.set(*clipRect); + inverse.mapRect(mLocalClip); + + return mLocalClip; +} + +void Snapshot::resetClip(float left, float top, float right, float bottom) { + clipRect = &mClipRectRoot; + setClip(left, top, right, bottom); +} + +/////////////////////////////////////////////////////////////////////////////// +// Transforms +/////////////////////////////////////////////////////////////////////////////// + +void Snapshot::resetTransform(float x, float y, float z) { + transform = &mTransformRoot; + transform->loadTranslate(x, y, z); +} + +/////////////////////////////////////////////////////////////////////////////// +// Queries +/////////////////////////////////////////////////////////////////////////////// + +bool Snapshot::isIgnored() const { + return invisible || empty; +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index aff7b9364a90..b2bc879760e4 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -23,7 +23,7 @@ #include <utils/RefBase.h> #include <ui/Region.h> -#include <SkCanvas.h> +#include <SkRegion.h> #include "Layer.h" #include "Matrix.h" @@ -43,43 +43,12 @@ namespace uirenderer { */ class Snapshot: public LightRefBase<Snapshot> { public: - Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0), invisible(false), empty(false) { - transform = &mTransformRoot; - clipRect = &mClipRectRoot; - region = NULL; - } - /** - * Copies the specified snapshot/ The specified snapshot is stored as - * the previous snapshot. - */ - Snapshot(const sp<Snapshot>& s, int saveFlags): - flags(0), previous(s), layer(NULL), fbo(s->fbo), - invisible(s->invisible), empty(false), viewport(s->viewport), height(s->height) { - if (saveFlags & SkCanvas::kMatrix_SaveFlag) { - mTransformRoot.load(*s->transform); - transform = &mTransformRoot; - } else { - transform = s->transform; - } - - if (saveFlags & SkCanvas::kClip_SaveFlag) { - mClipRectRoot.set(*s->clipRect); - clipRect = &mClipRectRoot; - } else { - clipRect = s->clipRect; - } - - if (s->flags & Snapshot::kFlagFboTarget) { - flags |= Snapshot::kFlagFboTarget; - region = s->region; - } else { - region = NULL; - } - } + Snapshot(); + Snapshot(const sp<Snapshot>& s, int saveFlags); /** - * Various flags set on #flags. + * Various flags set on ::flags. */ enum Flags { /** @@ -115,87 +84,41 @@ public: * by this snapshot's trasnformation. */ bool clip(float left, float top, float right, float bottom, - SkRegion::Op op = SkRegion::kIntersect_Op) { - Rect r(left, top, right, bottom); - transform->mapRect(r); - return clipTransformed(r, op); - } + SkRegion::Op op = SkRegion::kIntersect_Op); /** * Modifies the current clip with the new clip rectangle and * the specified operation. The specified rectangle is considered * already transformed. */ - bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op) { - bool clipped = false; - - // NOTE: The unimplemented operations require support for regions - // Supporting regions would require using a stencil buffer instead - // of the scissor. The stencil buffer itself is not too expensive - // (memory cost excluded) but on fillrate limited devices, managing - // the stencil might have a negative impact on the framerate. - switch (op) { - case SkRegion::kDifference_Op: - break; - case SkRegion::kIntersect_Op: - clipped = clipRect->intersect(r); - if (!clipped) { - clipRect->setEmpty(); - clipped = true; - } - break; - case SkRegion::kUnion_Op: - clipped = clipRect->unionWith(r); - break; - case SkRegion::kXOR_Op: - break; - case SkRegion::kReverseDifference_Op: - break; - case SkRegion::kReplace_Op: - clipRect->set(r); - clipped = true; - break; - } - - if (clipped) { - flags |= Snapshot::kFlagClipSet; - } - - return clipped; - } + bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op); /** * Sets the current clip. */ - void setClip(float left, float top, float right, float bottom) { - clipRect->set(left, top, right, bottom); - flags |= Snapshot::kFlagClipSet; - } - - const Rect& getLocalClip() { - mat4 inverse; - inverse.loadInverse(*transform); + void setClip(float left, float top, float right, float bottom); - mLocalClip.set(*clipRect); - inverse.mapRect(mLocalClip); - - return mLocalClip; - } + /** + * Returns the current clip in local coordinates. The clip rect is + * transformed by the inverse transform matrix. + */ + const Rect& getLocalClip(); - void resetTransform(float x, float y, float z) { - transform = &mTransformRoot; - transform->loadTranslate(x, y, z); - } + /** + * Resets the clip to the specified rect. + */ + void resetClip(float left, float top, float right, float bottom); - void resetClip(float left, float top, float right, float bottom) { - clipRect = &mClipRectRoot; - clipRect->set(left, top, right, bottom); - flags |= Snapshot::kFlagClipSet; - } + /** + * Resets the current transform to a pure 3D translation. + */ + void resetTransform(float x, float y, float z); - bool isIgnored() const { - return invisible || empty; - } + /** + * Indicates whether this snapshot should be ignored. A snapshot + * is typicalled ignored if its layer is invisible or empty. + */ + bool isIgnored() const; /** * Dirty flags. @@ -209,6 +132,8 @@ public: /** * Only set when the flag kFlagIsLayer is set. + * + * This snapshot does not own the layer, this pointer must not be freed. */ Layer* layer; @@ -249,25 +174,57 @@ public: /** * Local transformation. Holds the current translation, scale and * rotation values. + * + * This is a reference to a matrix owned by this snapshot or another + * snapshot. This pointer must not be freed. See ::mTransformRoot. */ mat4* transform; /** - * Current clip region. The clip is stored in canvas-space coordinates, + * Current clip rect. The clip is stored in canvas-space coordinates, * (screen-space coordinates in the regular case.) + * + * This is a reference to a rect owned by this snapshot or another + * snapshot. This pointer must not be freed. See ::mClipRectRoot. */ Rect* clipRect; /** + * Current clip region. The clip is stored in canvas-space coordinates, + * (screen-space coordinates in the regular case.) + * + * This is a reference to a region owned by this snapshot or another + * snapshot. This pointer must not be freed. See ::mClipRegionRoot. + * + * This field is used only if STENCIL_BUFFER_SIZE is > 0. + */ + Region* clipRegion; + + /** * The ancestor layer's dirty region. + * + * This is a reference to a region owned by a layer. This pointer must + * not be freed. */ Region* region; private: + void ensureClipRegion(); + void copyClipRectFromRegion(); + + bool clipRegionOr(float left, float top, float right, float bottom); + bool clipRegionXor(float left, float top, float right, float bottom); + bool clipRegionAnd(float left, float top, float right, float bottom); + bool clipRegionNand(float left, float top, float right, float bottom); + mat4 mTransformRoot; Rect mClipRectRoot; Rect mLocalClip; +#if STENCIL_BUFFER_SIZE + Region mClipRegionRoot; +#endif + }; // class Snapshot }; // namespace uirenderer diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk index 58d3e5c3bd09..2166ce7dd01f 100644 --- a/libs/rs/Android.mk +++ b/libs/rs/Android.mk @@ -89,7 +89,6 @@ LOCAL_SRC_FILES:= \ rsFifoSocket.cpp \ rsFileA3D.cpp \ rsFont.cpp \ - rsLocklessFifo.cpp \ rsObjectBase.cpp \ rsMatrix2x2.cpp \ rsMatrix3x3.cpp \ @@ -128,7 +127,7 @@ LOCAL_SRC_FILES:= \ driver/rsdShaderCache.cpp \ driver/rsdVertexArray.cpp -LOCAL_SHARED_LIBRARIES += libz libcutils libutils libEGL libGLESv1_CM libGLESv2 libui libbcc libbcinfo +LOCAL_SHARED_LIBRARIES += libz libcutils libutils libEGL libGLESv1_CM libGLESv2 libui libbcc libbcinfo libgui LOCAL_STATIC_LIBRARIES := libdex libft2 @@ -196,7 +195,6 @@ LOCAL_SRC_FILES:= \ rsFifoSocket.cpp \ rsFileA3D.cpp \ rsFont.cpp \ - rsLocklessFifo.cpp \ rsObjectBase.cpp \ rsMatrix2x2.cpp \ rsMatrix3x3.cpp \ diff --git a/libs/rs/driver/rsdGL.cpp b/libs/rs/driver/rsdGL.cpp index 7acc05487f82..368dd7107268 100644 --- a/libs/rs/driver/rsdGL.cpp +++ b/libs/rs/driver/rsdGL.cpp @@ -215,6 +215,8 @@ bool rsdGLInit(const Context *rsc) { ret = eglChooseConfig(dc->gl.egl.display, configAttribs, 0, 0, &numConfigs); checkEglError("eglGetConfigs", ret); + eglSwapInterval(dc->gl.egl.display, 0); + if (numConfigs) { EGLConfig* const configs = new EGLConfig[numConfigs]; diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec index 20b1f52104da..ffb119692efd 100644 --- a/libs/rs/rs.spec +++ b/libs/rs/rs.spec @@ -115,6 +115,7 @@ ContextSetPriority { } ContextDestroyWorker { + sync } AssignName { @@ -162,7 +163,7 @@ Allocation1DElementData { param uint32_t x param uint32_t lod param const void *data - param uint32_t comp_offset + param size_t comp_offset } Allocation2DData { @@ -183,7 +184,7 @@ Allocation2DElementData { param uint32_t lod param RsAllocationCubemapFace face param const void *data - param uint32_t element_offset + param size_t element_offset } AllocationGenerateMipmaps { diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp index 2773d5c661c5..fd85b077a493 100644 --- a/libs/rs/rsAllocation.cpp +++ b/libs/rs/rsAllocation.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2009-2012 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. @@ -71,11 +71,11 @@ void Allocation::read(void *data) { } void Allocation::data(Context *rsc, uint32_t xoff, uint32_t lod, - uint32_t count, const void *data, uint32_t sizeBytes) { - const uint32_t eSize = mHal.state.type->getElementSizeBytes(); + uint32_t count, const void *data, size_t sizeBytes) { + const size_t eSize = mHal.state.type->getElementSizeBytes(); if ((count * eSize) != sizeBytes) { - ALOGE("Allocation::subData called with mismatched size expected %i, got %i", + ALOGE("Allocation::subData called with mismatched size expected %zu, got %zu", (count * eSize), sizeBytes); mHal.state.type->dumpLOGV("type info"); return; @@ -86,14 +86,14 @@ void Allocation::data(Context *rsc, uint32_t xoff, uint32_t lod, } void Allocation::data(Context *rsc, uint32_t xoff, uint32_t yoff, uint32_t lod, RsAllocationCubemapFace face, - uint32_t w, uint32_t h, const void *data, uint32_t sizeBytes) { - const uint32_t eSize = mHal.state.elementSizeBytes; - const uint32_t lineSize = eSize * w; + uint32_t w, uint32_t h, const void *data, size_t sizeBytes) { + const size_t eSize = mHal.state.elementSizeBytes; + const size_t lineSize = eSize * w; //ALOGE("data2d %p, %i %i %i %i %i %i %p %i", this, xoff, yoff, lod, face, w, h, data, sizeBytes); if ((lineSize * h) != sizeBytes) { - ALOGE("Allocation size mismatch, expected %i, got %i", (lineSize * h), sizeBytes); + ALOGE("Allocation size mismatch, expected %zu, got %zu", (lineSize * h), sizeBytes); rsAssert(!"Allocation::subData called with mismatched size"); return; } @@ -104,12 +104,12 @@ void Allocation::data(Context *rsc, uint32_t xoff, uint32_t yoff, uint32_t lod, void Allocation::data(Context *rsc, uint32_t xoff, uint32_t yoff, uint32_t zoff, uint32_t lod, RsAllocationCubemapFace face, - uint32_t w, uint32_t h, uint32_t d, const void *data, uint32_t sizeBytes) { + uint32_t w, uint32_t h, uint32_t d, const void *data, size_t sizeBytes) { } void Allocation::elementData(Context *rsc, uint32_t x, const void *data, - uint32_t cIdx, uint32_t sizeBytes) { - uint32_t eSize = mHal.state.elementSizeBytes; + uint32_t cIdx, size_t sizeBytes) { + size_t eSize = mHal.state.elementSizeBytes; if (cIdx >= mHal.state.type->getElement()->getFieldCount()) { ALOGE("Error Allocation::subElementData component %i out of range.", cIdx); @@ -124,8 +124,9 @@ void Allocation::elementData(Context *rsc, uint32_t x, const void *data, } const Element * e = mHal.state.type->getElement()->getField(cIdx); - if (sizeBytes != e->getSizeBytes()) { - ALOGE("Error Allocation::subElementData data size %i does not match field size %zu.", sizeBytes, e->getSizeBytes()); + uint32_t elemArraySize = mHal.state.type->getElement()->getFieldArraySize(cIdx); + if (sizeBytes != e->getSizeBytes() * elemArraySize) { + ALOGE("Error Allocation::subElementData data size %zu does not match field size %zu.", sizeBytes, e->getSizeBytes()); rsc->setError(RS_ERROR_BAD_VALUE, "subElementData bad size."); return; } @@ -135,8 +136,8 @@ void Allocation::elementData(Context *rsc, uint32_t x, const void *data, } void Allocation::elementData(Context *rsc, uint32_t x, uint32_t y, - const void *data, uint32_t cIdx, uint32_t sizeBytes) { - uint32_t eSize = mHal.state.elementSizeBytes; + const void *data, uint32_t cIdx, size_t sizeBytes) { + size_t eSize = mHal.state.elementSizeBytes; if (x >= mHal.state.dimensionX) { ALOGE("Error Allocation::subElementData X offset %i out of range.", x); @@ -157,9 +158,9 @@ void Allocation::elementData(Context *rsc, uint32_t x, uint32_t y, } const Element * e = mHal.state.type->getElement()->getField(cIdx); - - if (sizeBytes != e->getSizeBytes()) { - ALOGE("Error Allocation::subElementData data size %i does not match field size %zu.", sizeBytes, e->getSizeBytes()); + uint32_t elemArraySize = mHal.state.type->getElement()->getFieldArraySize(cIdx); + if (sizeBytes != e->getSizeBytes() * elemArraySize) { + ALOGE("Error Allocation::subElementData data size %zu does not match field size %zu.", sizeBytes, e->getSizeBytes()); rsc->setError(RS_ERROR_BAD_VALUE, "subElementData bad size."); return; } @@ -249,7 +250,7 @@ void Allocation::writePackedData(const Type *type, delete[] sizeUnpadded; } -void Allocation::unpackVec3Allocation(const void *data, uint32_t dataSize) { +void Allocation::unpackVec3Allocation(const void *data, size_t dataSize) { const uint8_t *src = (const uint8_t*)data; uint8_t *dst = (uint8_t*)getPtr(); @@ -519,13 +520,13 @@ void rsi_Allocation1DData(Context *rsc, RsAllocation va, uint32_t xoff, uint32_t } void rsi_Allocation2DElementData(Context *rsc, RsAllocation va, uint32_t x, uint32_t y, uint32_t lod, RsAllocationCubemapFace face, - const void *data, size_t eoff, uint32_t sizeBytes) { // TODO: this seems wrong, eoff and sizeBytes may be swapped + const void *data, size_t sizeBytes, size_t eoff) { Allocation *a = static_cast<Allocation *>(va); a->elementData(rsc, x, y, data, eoff, sizeBytes); } void rsi_Allocation1DElementData(Context *rsc, RsAllocation va, uint32_t x, uint32_t lod, - const void *data, size_t eoff, uint32_t sizeBytes) { // TODO: this seems wrong, eoff and sizeBytes may be swapped + const void *data, size_t sizeBytes, size_t eoff) { Allocation *a = static_cast<Allocation *>(va); a->elementData(rsc, x, data, eoff, sizeBytes); } diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h index 4ce863ab927f..20201ca793ea 100644 --- a/libs/rs/rsAllocation.h +++ b/libs/rs/rsAllocation.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2009-2012 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. @@ -80,16 +80,16 @@ public: void resize1D(Context *rsc, uint32_t dimX); void resize2D(Context *rsc, uint32_t dimX, uint32_t dimY); - void data(Context *rsc, uint32_t xoff, uint32_t lod, uint32_t count, const void *data, uint32_t sizeBytes); + void data(Context *rsc, uint32_t xoff, uint32_t lod, uint32_t count, const void *data, size_t sizeBytes); void data(Context *rsc, uint32_t xoff, uint32_t yoff, uint32_t lod, RsAllocationCubemapFace face, - uint32_t w, uint32_t h, const void *data, uint32_t sizeBytes); + uint32_t w, uint32_t h, const void *data, size_t sizeBytes); void data(Context *rsc, uint32_t xoff, uint32_t yoff, uint32_t zoff, uint32_t lod, RsAllocationCubemapFace face, - uint32_t w, uint32_t h, uint32_t d, const void *data, uint32_t sizeBytes); + uint32_t w, uint32_t h, uint32_t d, const void *data, size_t sizeBytes); void elementData(Context *rsc, uint32_t x, - const void *data, uint32_t elementOff, uint32_t sizeBytes); + const void *data, uint32_t elementOff, size_t sizeBytes); void elementData(Context *rsc, uint32_t x, uint32_t y, - const void *data, uint32_t elementOff, uint32_t sizeBytes); + const void *data, uint32_t elementOff, size_t sizeBytes); void read(void *data); @@ -138,7 +138,7 @@ private: uint32_t getPackedSize() const; static void writePackedData(const Type *type, uint8_t *dst, const uint8_t *src, bool dstPadded); - void unpackVec3Allocation(const void *data, uint32_t dataSize); + void unpackVec3Allocation(const void *data, size_t dataSize); void packVec3Allocation(OStream *stream) const; }; diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp index ad2ff0ff0dac..04284dd964b0 100644 --- a/libs/rs/rsContext.cpp +++ b/libs/rs/rsContext.cpp @@ -18,6 +18,7 @@ #include "rsContext.h" #include "rsThreadIO.h" #include <ui/FramebufferNativeWindow.h> +#include <gui/DisplayEventReceiver.h> #include <sys/types.h> #include <sys/resource.h> @@ -245,42 +246,55 @@ void * Context::threadProc(void *vrsc) { } rsc->mRunning = true; - bool mDraw = true; - bool doWait = true; - - uint64_t targetTime = rsc->getTime(); - while (!rsc->mExit) { - uint64_t waitTime = 0; - uint64_t now = rsc->getTime(); - if (!doWait) { - if (now < targetTime) { - waitTime = targetTime - now; - doWait = true; - } + if (!rsc->mIsGraphicsContext) { + while (!rsc->mExit) { + rsc->mIO.playCoreCommands(rsc, true, -1); } + } else { +#ifndef ANDROID_RS_SERIALIZE + DisplayEventReceiver displayEvent; + DisplayEventReceiver::Event eventBuffer[1]; +#endif + int vsyncRate = 0; + int targetRate = 0; - mDraw |= rsc->mIO.playCoreCommands(rsc, doWait, waitTime); - mDraw &= (rsc->mRootScript.get() != NULL); - mDraw &= rsc->mHasSurface; - - if (mDraw && rsc->mIsGraphicsContext) { - uint64_t delay = rsc->runRootScript() * 1000000; - targetTime = rsc->getTime() + delay; - doWait = (delay == 0); + bool drawOnce = false; + while (!rsc->mExit) { + rsc->timerSet(RS_TIMER_IDLE); - if (rsc->props.mLogVisual) { - rsc->displayDebugStats(); +#ifndef ANDROID_RS_SERIALIZE + if (vsyncRate != targetRate) { + displayEvent.setVsyncRate(targetRate); + vsyncRate = targetRate; + } + if (targetRate) { + drawOnce |= rsc->mIO.playCoreCommands(rsc, true, displayEvent.getFd()); + while (displayEvent.getEvents(eventBuffer, 1) != 0) { + //ALOGE("vs2 time past %lld", (rsc->getTime() - eventBuffer[0].header.timestamp) / 1000000); + } + } else +#endif + { + drawOnce |= rsc->mIO.playCoreCommands(rsc, true, -1); } - mDraw = !rsc->mPaused; - rsc->timerSet(RS_TIMER_CLEAR_SWAP); - rsc->mHal.funcs.swap(rsc); - rsc->timerFrame(); - rsc->timerSet(RS_TIMER_INTERNAL); - rsc->timerPrint(); - rsc->timerReset(); - } else { - doWait = true; + if ((rsc->mRootScript.get() != NULL) && rsc->mHasSurface && + (targetRate || drawOnce) && !rsc->mPaused) { + + drawOnce = false; + targetRate = ((rsc->runRootScript() + 15) / 16); + + if (rsc->props.mLogVisual) { + rsc->displayDebugStats(); + } + + rsc->timerSet(RS_TIMER_CLEAR_SWAP); + rsc->mHal.funcs.swap(rsc); + rsc->timerFrame(); + rsc->timerSet(RS_TIMER_INTERNAL); + rsc->timerPrint(); + rsc->timerReset(); + } } } @@ -315,8 +329,8 @@ void Context::destroyWorkerThreadResources() { mFBOCache.deinit(this); } ObjectBase::freeAllChildren(this); - //ALOGV("destroyWorkerThreadResources 2"); mExit = true; + //ALOGV("destroyWorkerThreadResources 2"); } void Context::printWatchdogInfo(void *ctx) { @@ -382,7 +396,7 @@ bool Context::initContext(Device *dev, const RsSurfaceConfig *sc) { pthread_mutex_lock(&gInitMutex); mIO.init(); - mIO.setTimoutCallback(printWatchdogInfo, this, 2e9); + mIO.setTimeoutCallback(printWatchdogInfo, this, 2e9); dev->addContext(this); mDev = dev; @@ -434,14 +448,12 @@ Context::~Context() { ALOGV("%p Context::~Context", this); if (!mIsContextLite) { - mIO.coreFlush(); - rsAssert(mExit); - mExit = true; mPaused = false; void *res; mIO.shutdown(); int status = pthread_join(mThreadId, &res); + rsAssert(mExit); if (mHal.funcs.shutdownDriver) { mHal.funcs.shutdownDriver(this); diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h index 61c29f952f5f..a844a20cfc6e 100644 --- a/libs/rs/rsContext.h +++ b/libs/rs/rsContext.h @@ -39,7 +39,6 @@ #include "rsFBOCache.h" #include "rsgApiStructs.h" -#include "rsLocklessFifo.h" // --------------------------------------------------------------------------- namespace android { diff --git a/libs/rs/rsFifo.h b/libs/rs/rsFifo.h index f924b958f5aa..911f4467b418 100644 --- a/libs/rs/rsFifo.h +++ b/libs/rs/rsFifo.h @@ -35,9 +35,9 @@ protected: virtual ~Fifo(); public: - void virtual writeAsync(const void *data, size_t bytes) = 0; + bool virtual writeAsync(const void *data, size_t bytes, bool waitForSpace = true) = 0; void virtual writeWaitReturn(void *ret, size_t retSize) = 0; - size_t virtual read(void *data, size_t bytes) = 0; + size_t virtual read(void *data, size_t bytes, bool doWait = true, uint64_t timeToWait = 0) = 0; void virtual readReturn(const void *data, size_t bytes) = 0; void virtual flush() = 0; diff --git a/libs/rs/rsFifoSocket.cpp b/libs/rs/rsFifoSocket.cpp index 163a44be0992..bd511cf9008a 100644 --- a/libs/rs/rsFifoSocket.cpp +++ b/libs/rs/rsFifoSocket.cpp @@ -22,6 +22,7 @@ #include <stdlib.h> #include <ctype.h> #include <unistd.h> +#include <poll.h> #include <sys/types.h> #include <sys/socket.h> @@ -29,55 +30,79 @@ using namespace android; using namespace android::renderscript; FifoSocket::FifoSocket() { - sequence = 1; + mShutdown = false; } FifoSocket::~FifoSocket() { } -bool FifoSocket::init() { +bool FifoSocket::init(bool supportNonBlocking, bool supportReturnValues, size_t maxDataSize) { int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); return false; } void FifoSocket::shutdown() { + mShutdown = true; + uint64_t d = 0; + ::send(sv[0], &d, sizeof(d), 0); + ::send(sv[1], &d, sizeof(d), 0); + close(sv[0]); + close(sv[1]); } -void FifoSocket::writeAsync(const void *data, size_t bytes) { +bool FifoSocket::writeAsync(const void *data, size_t bytes, bool waitForSpace) { if (bytes == 0) { - return; + return true; } //ALOGE("writeAsync %p %i", data, bytes); size_t ret = ::send(sv[0], data, bytes, 0); //ALOGE("writeAsync ret %i", ret); rsAssert(ret == bytes); + return true; } void FifoSocket::writeWaitReturn(void *retData, size_t retBytes) { + if (mShutdown) { + return; + } + //ALOGE("writeWaitReturn %p %i", retData, retBytes); - size_t ret = ::recv(sv[0], retData, retBytes, 0); + size_t ret = ::recv(sv[0], retData, retBytes, MSG_WAITALL); //ALOGE("writeWaitReturn %i", ret); rsAssert(ret == retBytes); } size_t FifoSocket::read(void *data, size_t bytes) { + if (mShutdown) { + return 0; + } + //ALOGE("read %p %i", data, bytes); - size_t ret = ::recv(sv[1], data, bytes, 0); - rsAssert(ret == bytes); - //ALOGE("read ret %i", ret); + size_t ret = ::recv(sv[1], data, bytes, MSG_WAITALL); + rsAssert(ret == bytes || mShutdown); + //ALOGE("read ret %i bytes %i", ret, bytes); + if (mShutdown) { + ret = 0; + } return ret; } -void FifoSocket::readReturn(const void *data, size_t bytes) { - ALOGE("readReturn %p %Zu", data, bytes); - size_t ret = ::send(sv[1], data, bytes, 0); - ALOGE("readReturn %Zu", ret); - rsAssert(ret == bytes); +bool FifoSocket::isEmpty() { + struct pollfd p; + p.fd = sv[1]; + p.events = POLLIN; + int r = poll(&p, 1, 0); + //ALOGE("poll r=%i", r); + return r == 0; } -void FifoSocket::flush() { +void FifoSocket::readReturn(const void *data, size_t bytes) { + //ALOGE("readReturn %p %Zu", data, bytes); + size_t ret = ::send(sv[1], data, bytes, 0); + //ALOGE("readReturn %Zu", ret); + //rsAssert(ret == bytes); } diff --git a/libs/rs/rsFifoSocket.h b/libs/rs/rsFifoSocket.h index 7df2b67d2e9f..cac0a75d70d6 100644 --- a/libs/rs/rsFifoSocket.h +++ b/libs/rs/rsFifoSocket.h @@ -29,23 +29,23 @@ public: FifoSocket(); virtual ~FifoSocket(); - bool init(); + bool init(bool supportNonBlocking = true, + bool supportReturnValues = true, + size_t maxDataSize = 0); void shutdown(); + bool writeAsync(const void *data, size_t bytes, bool waitForSpace = true); + void writeWaitReturn(void *ret, size_t retSize); + size_t read(void *data, size_t bytes); + void readReturn(const void *data, size_t bytes); + bool isEmpty(); - - void virtual writeAsync(const void *data, size_t bytes); - void virtual writeWaitReturn(void *ret, size_t retSize); - size_t virtual read(void *data, size_t bytes); - void virtual readReturn(const void *data, size_t bytes); - - void virtual flush(); + int getWriteFd() {return sv[0];} + int getReadFd() {return sv[1];} protected: int sv[2]; - uint32_t sequence; - - + bool mShutdown; }; } diff --git a/libs/rs/rsLocklessFifo.cpp b/libs/rs/rsLocklessFifo.cpp deleted file mode 100644 index 0466d8bbf04e..000000000000 --- a/libs/rs/rsLocklessFifo.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "rsLocklessFifo.h" -#include "utils/Timers.h" -#include "utils/StopWatch.h" - -using namespace android; -using namespace android::renderscript; - -LocklessCommandFifo::LocklessCommandFifo() : mBuffer(0), mInitialized(false) { - mTimeoutCallback = NULL; - mTimeoutCallbackData = NULL; - mTimeoutWait = 0; -} - -LocklessCommandFifo::~LocklessCommandFifo() { - if (!mInShutdown && mInitialized) { - shutdown(); - } - if (mBuffer) { - free(mBuffer); - } -} - -void LocklessCommandFifo::shutdown() { - mInShutdown = true; - mSignalToWorker.set(); -} - -bool LocklessCommandFifo::init(uint32_t sizeInBytes) { - // Add room for a buffer reset command - mBuffer = static_cast<uint8_t *>(malloc(sizeInBytes + 4)); - if (!mBuffer) { - ALOGE("LocklessFifo allocation failure"); - return false; - } - - if (!mSignalToControl.init() || !mSignalToWorker.init()) { - ALOGE("Signal setup failed"); - free(mBuffer); - return false; - } - - mInShutdown = false; - mSize = sizeInBytes; - mPut = mBuffer; - mGet = mBuffer; - mEnd = mBuffer + (sizeInBytes) - 1; - //dumpState("init"); - mInitialized = true; - return true; -} - -uint32_t LocklessCommandFifo::getFreeSpace() const { - int32_t freeSpace = 0; - //dumpState("getFreeSpace"); - - if (mPut >= mGet) { - freeSpace = mEnd - mPut; - } else { - freeSpace = mGet - mPut; - } - - if (freeSpace < 0) { - freeSpace = 0; - } - return freeSpace; -} - -bool LocklessCommandFifo::isEmpty() const { - uint32_t p = android_atomic_acquire_load((int32_t *)&mPut); - return ((uint8_t *)p) == mGet; -} - - -void * LocklessCommandFifo::reserve(uint32_t sizeInBytes) { - // Add space for command header and loop token; - sizeInBytes += 8; - - //dumpState("reserve"); - if (getFreeSpace() < sizeInBytes) { - makeSpace(sizeInBytes); - } - - return mPut + 4; -} - -void LocklessCommandFifo::commit(uint32_t command, uint32_t sizeInBytes) { - if (mInShutdown) { - return; - } - //dumpState("commit 1"); - reinterpret_cast<uint16_t *>(mPut)[0] = command; - reinterpret_cast<uint16_t *>(mPut)[1] = sizeInBytes; - - int32_t s = ((sizeInBytes + 3) & ~3) + 4; - android_atomic_add(s, (int32_t *)&mPut); - //dumpState("commit 2"); - mSignalToWorker.set(); -} - -void LocklessCommandFifo::commitSync(uint32_t command, uint32_t sizeInBytes) { - if (mInShutdown) { - return; - } - - //char buf[1024]; - //sprintf(buf, "RenderScript LocklessCommandFifo::commitSync %p %i %i", this, command, sizeInBytes); - //StopWatch compileTimer(buf); - commit(command, sizeInBytes); - flush(); -} - -void LocklessCommandFifo::flush() { - //dumpState("flush 1"); - while (mPut != mGet) { - while (!mSignalToControl.wait(mTimeoutWait)) { - if (mTimeoutCallback) { - mTimeoutCallback(mTimeoutCallbackData); - } - } - } - //dumpState("flush 2"); -} - -void LocklessCommandFifo::setTimoutCallback(void (*cbk)(void *), void *data, uint64_t timeout) { - mTimeoutCallback = cbk; - mTimeoutCallbackData = data; - mTimeoutWait = timeout; -} - -bool LocklessCommandFifo::wait(uint64_t timeout) { - while (isEmpty() && !mInShutdown) { - mSignalToControl.set(); - return mSignalToWorker.wait(timeout); - } - return true; -} - -const void * LocklessCommandFifo::get(uint32_t *command, uint32_t *bytesData, uint64_t timeout) { - while (1) { - //dumpState("get"); - wait(timeout); - - if (isEmpty() || mInShutdown) { - *command = 0; - *bytesData = 0; - return NULL; - } - - *command = reinterpret_cast<const uint16_t *>(mGet)[0]; - *bytesData = reinterpret_cast<const uint16_t *>(mGet)[1]; - if (*command) { - // non-zero command is valid - return mGet+4; - } - - // zero command means reset to beginning. - mGet = mBuffer; - } -} - -void LocklessCommandFifo::next() { - uint32_t bytes = reinterpret_cast<const uint16_t *>(mGet)[1]; - - android_atomic_add(((bytes + 3) & ~3) + 4, (int32_t *)&mGet); - //mGet += ((bytes + 3) & ~3) + 4; - if (isEmpty()) { - mSignalToControl.set(); - } - //dumpState("next"); -} - -bool LocklessCommandFifo::makeSpaceNonBlocking(uint32_t bytes) { - //dumpState("make space non-blocking"); - if ((mPut+bytes) > mEnd) { - // Need to loop regardless of where get is. - if ((mGet > mPut) || (mBuffer+4 >= mGet)) { - return false; - } - - // Toss in a reset then the normal wait for space will do the rest. - reinterpret_cast<uint16_t *>(mPut)[0] = 0; - reinterpret_cast<uint16_t *>(mPut)[1] = 0; - mPut = mBuffer; - mSignalToWorker.set(); - } - - // it will fit here so we just need to wait for space. - if (getFreeSpace() < bytes) { - return false; - } - - return true; -} - -void LocklessCommandFifo::makeSpace(uint32_t bytes) { - //dumpState("make space"); - if ((mPut+bytes) > mEnd) { - // Need to loop regardless of where get is. - while ((mGet > mPut) || (mBuffer+4 >= mGet)) { - usleep(100); - } - - // Toss in a reset then the normal wait for space will do the rest. - reinterpret_cast<uint16_t *>(mPut)[0] = 0; - reinterpret_cast<uint16_t *>(mPut)[1] = 0; - mPut = mBuffer; - mSignalToWorker.set(); - } - - // it will fit here so we just need to wait for space. - while (getFreeSpace() < bytes) { - usleep(100); - } - -} - -void LocklessCommandFifo::dumpState(const char *s) const { - ALOGV("%s %p put %p, get %p, buf %p, end %p", s, this, mPut, mGet, mBuffer, mEnd); -} - -void LocklessCommandFifo::printDebugData() const { - dumpState("printing fifo debug"); - const uint32_t *pptr = (const uint32_t *)mGet; - pptr -= 8 * 4; - if (mGet < mBuffer) { - pptr = (const uint32_t *)mBuffer; - } - - - for (int ct=0; ct < 16; ct++) { - ALOGV("fifo %p = 0x%08x 0x%08x 0x%08x 0x%08x", pptr, pptr[0], pptr[1], pptr[2], pptr[3]); - pptr += 4; - } - -} diff --git a/libs/rs/rsLocklessFifo.h b/libs/rs/rsLocklessFifo.h deleted file mode 100644 index dafc512f118a..000000000000 --- a/libs/rs/rsLocklessFifo.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_RS_LOCKLESS_FIFO_H -#define ANDROID_RS_LOCKLESS_FIFO_H - - -#include "rsUtils.h" -#include "rsSignal.h" - -namespace android { -namespace renderscript { - - -// A simple FIFO to be used as a producer / consumer between two -// threads. One is writer and one is reader. The common cases -// will not require locking. It is not threadsafe for multiple -// readers or writers by design. - -class LocklessCommandFifo { -public: - bool init(uint32_t size); - void shutdown(); - void setTimoutCallback(void (*)(void *), void *, uint64_t timeout); - - void printDebugData() const; - - LocklessCommandFifo(); - ~LocklessCommandFifo(); - -protected: - uint8_t * volatile mPut; - uint8_t * volatile mGet; - uint8_t * mBuffer; - uint8_t * mEnd; - uint8_t mSize; - bool mInShutdown; - bool mInitialized; - - Signal mSignalToWorker; - Signal mSignalToControl; - -public: - void * reserve(uint32_t bytes); - void commit(uint32_t command, uint32_t bytes); - void commitSync(uint32_t command, uint32_t bytes); - - void flush(); - bool wait(uint64_t timeout = 0); - - const void * get(uint32_t *command, uint32_t *bytesData, uint64_t timeout = 0); - void next(); - - void makeSpace(uint32_t bytes); - bool makeSpaceNonBlocking(uint32_t bytes); - - bool isEmpty() const; - uint32_t getFreeSpace() const; - -private: - void dumpState(const char *) const; - - void (*mTimeoutCallback)(void *); - void * mTimeoutCallbackData; - uint64_t mTimeoutWait; -}; - - -} -} -#endif diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp index afc8ba01129e..b4eb995aed2f 100644 --- a/libs/rs/rsScriptC.cpp +++ b/libs/rs/rsScriptC.cpp @@ -206,7 +206,6 @@ bool ScriptC::runCompiler(Context *rsc, return false; } - rsAssert(bcWrapper.getHeaderVersion() == 0); if (bcWrapper.getBCFileType() == bcinfo::BC_WRAPPER) { sdkVersion = bcWrapper.getTargetAPI(); } @@ -323,7 +322,7 @@ RsScript rsi_ScriptCCreate(Context *rsc, if (!s->runCompiler(rsc, resName, cacheDir, (uint8_t *)text, text_length)) { // Error during compile, destroy s and return null. - delete s; + ObjectBase::checkDelete(s); return NULL; } diff --git a/libs/rs/rsThreadIO.cpp b/libs/rs/rsThreadIO.cpp index 191777428712..8e4b9883a353 100644 --- a/libs/rs/rsThreadIO.cpp +++ b/libs/rs/rsThreadIO.cpp @@ -18,227 +18,189 @@ #include "rsThreadIO.h" +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <fcntl.h> +#include <poll.h> + + using namespace android; using namespace android::renderscript; -ThreadIO::ThreadIO() : mUsingSocket(false) { +ThreadIO::ThreadIO() { + mRunning = true; } ThreadIO::~ThreadIO() { } -void ThreadIO::init(bool useSocket) { - mUsingSocket = useSocket; - mToCore.init(16 * 1024); - - if (mUsingSocket) { - mToClientSocket.init(); - mToCoreSocket.init(); - } else { - mToClient.init(1024); - } +void ThreadIO::init() { + mToClient.init(); + mToCore.init(); } void ThreadIO::shutdown() { - //ALOGE("shutdown 1"); + mRunning = false; mToCore.shutdown(); - //ALOGE("shutdown 2"); -} - -void ThreadIO::coreFlush() { - //ALOGE("coreFlush 1"); - if (mUsingSocket) { - } else { - mToCore.flush(); - } - //ALOGE("coreFlush 2"); } void * ThreadIO::coreHeader(uint32_t cmdID, size_t dataLen) { //ALOGE("coreHeader %i %i", cmdID, dataLen); - if (mUsingSocket) { - CoreCmdHeader hdr; - hdr.bytes = dataLen; - hdr.cmdID = cmdID; - mToCoreSocket.writeAsync(&hdr, sizeof(hdr)); - } else { - mCoreCommandSize = dataLen; - mCoreCommandID = cmdID; - mCoreDataPtr = (uint8_t *)mToCore.reserve(dataLen); - mCoreDataBasePtr = mCoreDataPtr; - } - //ALOGE("coreHeader ret %p", mCoreDataPtr); - return mCoreDataPtr; -} - -void ThreadIO::coreData(const void *data, size_t dataLen) { - //ALOGE("coreData %p %i", data, dataLen); - mToCoreSocket.writeAsync(data, dataLen); - //ALOGE("coreData ret %p", mCoreDataPtr); + CoreCmdHeader *hdr = (CoreCmdHeader *)&mSendBuffer[0]; + hdr->bytes = dataLen; + hdr->cmdID = cmdID; + mSendLen = dataLen + sizeof(CoreCmdHeader); + //mToCoreSocket.writeAsync(&hdr, sizeof(hdr)); + //ALOGE("coreHeader ret "); + return &mSendBuffer[sizeof(CoreCmdHeader)]; } void ThreadIO::coreCommit() { - //ALOGE("coreCommit %p %p %i", mCoreDataPtr, mCoreDataBasePtr, mCoreCommandSize); - if (mUsingSocket) { - } else { - rsAssert((size_t)(mCoreDataPtr - mCoreDataBasePtr) <= mCoreCommandSize); - mToCore.commit(mCoreCommandID, mCoreCommandSize); - } - //ALOGE("coreCommit ret"); -} - -void ThreadIO::coreCommitSync() { - //ALOGE("coreCommitSync %p %p %i", mCoreDataPtr, mCoreDataBasePtr, mCoreCommandSize); - if (mUsingSocket) { - } else { - rsAssert((size_t)(mCoreDataPtr - mCoreDataBasePtr) <= mCoreCommandSize); - mToCore.commitSync(mCoreCommandID, mCoreCommandSize); - } - //ALOGE("coreCommitSync ret"); + mToCore.writeAsync(&mSendBuffer, mSendLen); } void ThreadIO::clientShutdown() { - //ALOGE("coreShutdown 1"); mToClient.shutdown(); - //ALOGE("coreShutdown 2"); } void ThreadIO::coreSetReturn(const void *data, size_t dataLen) { - rsAssert(dataLen <= sizeof(mToCoreRet)); - memcpy(&mToCoreRet, data, dataLen); + uint32_t buf; + if (data == NULL) { + data = &buf; + dataLen = sizeof(buf); + } + + mToCore.readReturn(data, dataLen); } void ThreadIO::coreGetReturn(void *data, size_t dataLen) { - memcpy(data, &mToCoreRet, dataLen); -} + uint32_t buf; + if (data == NULL) { + data = &buf; + dataLen = sizeof(buf); + } -void ThreadIO::setTimoutCallback(void (*cb)(void *), void *dat, uint64_t timeout) { - mToCore.setTimoutCallback(cb, dat, timeout); + mToCore.writeWaitReturn(data, dataLen); } +void ThreadIO::setTimeoutCallback(void (*cb)(void *), void *dat, uint64_t timeout) { + //mToCore.setTimeoutCallback(cb, dat, timeout); +} -bool ThreadIO::playCoreCommands(Context *con, bool waitForCommand, uint64_t timeToWait) { +bool ThreadIO::playCoreCommands(Context *con, bool waitForCommand, int waitFd) { bool ret = false; - uint64_t startTime = con->getTime(); - while (!mToCore.isEmpty() || waitForCommand) { - uint32_t cmdID = 0; - uint32_t cmdSize = 0; - if (con->props.mLogTimes) { - con->timerSet(Context::RS_TIMER_IDLE); - } + uint8_t buf[2 * 1024]; + const CoreCmdHeader *cmd = (const CoreCmdHeader *)&buf[0]; + const void * data = (const void *)&buf[sizeof(CoreCmdHeader)]; + + struct pollfd p[2]; + p[0].fd = mToCore.getReadFd(); + p[0].events = POLLIN; + p[0].revents = 0; + p[1].fd = waitFd; + p[1].events = POLLIN; + p[1].revents = 0; + int pollCount = 1; + if (waitFd >= 0) { + pollCount = 2; + } - uint64_t delay = 0; - if (waitForCommand) { - delay = timeToWait - (con->getTime() - startTime); - if (delay > timeToWait) { - delay = 0; - } - } + if (con->props.mLogTimes) { + con->timerSet(Context::RS_TIMER_IDLE); + } - if (delay == 0 && timeToWait != 0 && mToCore.isEmpty()) { + int waitTime = -1; + while (mRunning) { + int pr = poll(p, pollCount, waitTime); + if (pr <= 0) { break; } - const void * data = mToCore.get(&cmdID, &cmdSize, delay); - if (!cmdSize) { - // exception or timeout occurred. - break; - } - ret = true; - if (con->props.mLogTimes) { - con->timerSet(Context::RS_TIMER_INTERNAL); + if (p[0].revents) { + size_t r = mToCore.read(&buf[0], sizeof(CoreCmdHeader)); + mToCore.read(&buf[sizeof(CoreCmdHeader)], cmd->bytes); + + if (r != sizeof(CoreCmdHeader)) { + // exception or timeout occurred. + break; + } + + ret = true; + if (con->props.mLogTimes) { + con->timerSet(Context::RS_TIMER_INTERNAL); + } + waitForCommand = false; + //ALOGV("playCoreCommands 3 %i %i", cmd->cmdID, cmd->bytes); + + if (cmd->cmdID >= (sizeof(gPlaybackFuncs) / sizeof(void *))) { + rsAssert(cmd->cmdID < (sizeof(gPlaybackFuncs) / sizeof(void *))); + ALOGE("playCoreCommands error con %p, cmd %i", con, cmd->cmdID); + } + gPlaybackFuncs[cmd->cmdID](con, data, cmd->bytes); + + if (con->props.mLogTimes) { + con->timerSet(Context::RS_TIMER_IDLE); + } + + if (waitFd < 0) { + // If we don't have a secondary wait object we should stop blocking now + // that at least one command has been processed. + waitTime = 0; + } } - waitForCommand = false; - //ALOGV("playCoreCommands 3 %i %i", cmdID, cmdSize); - if (cmdID >= (sizeof(gPlaybackFuncs) / sizeof(void *))) { - rsAssert(cmdID < (sizeof(gPlaybackFuncs) / sizeof(void *))); - ALOGE("playCoreCommands error con %p, cmd %i", con, cmdID); - mToCore.printDebugData(); + if (p[1].revents && !p[0].revents) { + // We want to finish processing fifo events before processing the vsync. + // Otherwise we can end up falling behind and having tremendous lag. + break; } - gPlaybackFuncs[cmdID](con, data, cmdSize << 2); - mToCore.next(); } return ret; } RsMessageToClientType ThreadIO::getClientHeader(size_t *receiveLen, uint32_t *usrID) { - if (mUsingSocket) { - mToClientSocket.read(&mLastClientHeader, sizeof(mLastClientHeader)); - } else { - size_t bytesData = 0; - const uint32_t *d = (const uint32_t *)mToClient.get(&mLastClientHeader.cmdID, (uint32_t*)&bytesData); - if (bytesData >= sizeof(uint32_t)) { - mLastClientHeader.userID = d[0]; - mLastClientHeader.bytes = bytesData - sizeof(uint32_t); - } else { - mLastClientHeader.userID = 0; - mLastClientHeader.bytes = 0; - } - } + //ALOGE("getClientHeader"); + mToClient.read(&mLastClientHeader, sizeof(mLastClientHeader)); + receiveLen[0] = mLastClientHeader.bytes; usrID[0] = mLastClientHeader.userID; + //ALOGE("getClientHeader %i %i %i", mLastClientHeader.cmdID, usrID[0], receiveLen[0]); return (RsMessageToClientType)mLastClientHeader.cmdID; } RsMessageToClientType ThreadIO::getClientPayload(void *data, size_t *receiveLen, uint32_t *usrID, size_t bufferLen) { + //ALOGE("getClientPayload"); receiveLen[0] = mLastClientHeader.bytes; usrID[0] = mLastClientHeader.userID; if (bufferLen < mLastClientHeader.bytes) { return RS_MESSAGE_TO_CLIENT_RESIZE; } - if (mUsingSocket) { - if (receiveLen[0]) { - mToClientSocket.read(data, receiveLen[0]); - } - return (RsMessageToClientType)mLastClientHeader.cmdID; - } else { - uint32_t bytesData = 0; - uint32_t commandID = 0; - const uint32_t *d = (const uint32_t *)mToClient.get(&commandID, &bytesData); - //ALOGE("getMessageToClient 3 %i %i", commandID, bytesData); - //ALOGE("getMessageToClient %i %i", commandID, *subID); - if (bufferLen >= receiveLen[0]) { - memcpy(data, d+1, receiveLen[0]); - mToClient.next(); - return (RsMessageToClientType)commandID; - } + if (receiveLen[0]) { + mToClient.read(data, receiveLen[0]); } - return RS_MESSAGE_TO_CLIENT_RESIZE; + //ALOGE("getClientPayload x"); + return (RsMessageToClientType)mLastClientHeader.cmdID; } bool ThreadIO::sendToClient(RsMessageToClientType cmdID, uint32_t usrID, const void *data, size_t dataLen, bool waitForSpace) { + + //ALOGE("sendToClient %i %i %i", cmdID, usrID, (int)dataLen); ClientCmdHeader hdr; hdr.bytes = dataLen; hdr.cmdID = cmdID; hdr.userID = usrID; - if (mUsingSocket) { - mToClientSocket.writeAsync(&hdr, sizeof(hdr)); - if (dataLen) { - mToClientSocket.writeAsync(data, dataLen); - } - return true; - } else { - if (!waitForSpace) { - if (!mToClient.makeSpaceNonBlocking(dataLen + sizeof(hdr))) { - // Not enough room, and not waiting. - return false; - } - } - //ALOGE("sendMessageToClient 2"); - uint32_t *p = (uint32_t *)mToClient.reserve(dataLen + sizeof(usrID)); - p[0] = usrID; - if (dataLen > 0) { - memcpy(p+1, data, dataLen); - } - mToClient.commit(cmdID, dataLen + sizeof(usrID)); - //ALOGE("sendMessageToClient 3"); - return true; + mToClient.writeAsync(&hdr, sizeof(hdr)); + if (dataLen) { + mToClient.writeAsync(data, dataLen); } - return false; + + //ALOGE("sendToClient x"); + return true; } diff --git a/libs/rs/rsThreadIO.h b/libs/rs/rsThreadIO.h index ebce0abf1bea..d56a1c99a1e1 100644 --- a/libs/rs/rsThreadIO.h +++ b/libs/rs/rsThreadIO.h @@ -18,7 +18,6 @@ #define ANDROID_RS_THREAD_IO_H #include "rsUtils.h" -#include "rsLocklessFifo.h" #include "rsFifoSocket.h" // --------------------------------------------------------------------------- @@ -32,23 +31,17 @@ public: ThreadIO(); ~ThreadIO(); - void init(bool useSocket = false); + void init(); void shutdown(); // Plays back commands from the client. // Returns true if any commands were processed. - bool playCoreCommands(Context *con, bool waitForCommand, uint64_t timeToWait); + bool playCoreCommands(Context *con, bool waitForCommand, int waitFd); - void setTimoutCallback(void (*)(void *), void *, uint64_t timeout); - //LocklessCommandFifo mToCore; + void setTimeoutCallback(void (*)(void *), void *, uint64_t timeout); - - - void coreFlush(); void * coreHeader(uint32_t, size_t dataLen); - void coreData(const void *data, size_t dataLen); void coreCommit(); - void coreCommitSync(); void coreSetReturn(const void *data, size_t dataLen); void coreGetReturn(void *data, size_t dataLen); @@ -71,20 +64,16 @@ protected: } ClientCmdHeader; ClientCmdHeader mLastClientHeader; - size_t mCoreCommandSize; - uint32_t mCoreCommandID; - uint8_t * mCoreDataPtr; - uint8_t * mCoreDataBasePtr; + bool mRunning; - bool mUsingSocket; - LocklessCommandFifo mToClient; - LocklessCommandFifo mToCore; - - FifoSocket mToClientSocket; - FifoSocket mToCoreSocket; + FifoSocket mToClient; + FifoSocket mToCore; intptr_t mToCoreRet; + size_t mSendLen; + uint8_t mSendBuffer[2 * 1024]; + }; diff --git a/libs/rs/rsg_generator.c b/libs/rs/rsg_generator.c index 6b84e561bbfe..385c8b58935b 100644 --- a/libs/rs/rsg_generator.c +++ b/libs/rs/rsg_generator.c @@ -256,7 +256,7 @@ void printApiCpp(FILE *f) { fprintf(f, " memcpy(payload, %s, %s_length);\n", vt->name, vt->name); fprintf(f, " cmd->%s = (", vt->name); printVarType(f, vt); - fprintf(f, ")payload;\n"); + fprintf(f, ")(payload - ((uint8_t *)&cmd[1]));\n"); fprintf(f, " payload += %s_length;\n", vt->name); fprintf(f, " } else {\n"); fprintf(f, " cmd->%s = %s;\n", vt->name, vt->name); @@ -270,26 +270,19 @@ void printApiCpp(FILE *f) { needFlush = 1; } + fprintf(f, " io->coreCommit();\n"); if (hasInlineDataPointers(api)) { - fprintf(f, " if (dataSize < 1024) {\n"); - fprintf(f, " io->coreCommit();\n"); - fprintf(f, " } else {\n"); - fprintf(f, " io->coreCommitSync();\n"); + fprintf(f, " if (dataSize >= 1024) {\n"); + fprintf(f, " io->coreGetReturn(NULL, 0);\n"); fprintf(f, " }\n"); - } else { - fprintf(f, " io->coreCommit"); - if (needFlush) { - fprintf(f, "Sync"); - } - fprintf(f, "();\n"); - } - - if (api->ret.typeName[0]) { + } else if (api->ret.typeName[0]) { fprintf(f, "\n "); printVarType(f, &api->ret); fprintf(f, " ret;\n"); fprintf(f, " io->coreGetReturn(&ret, sizeof(ret));\n"); fprintf(f, " return ret;\n"); + } else if (needFlush) { + fprintf(f, " io->coreGetReturn(NULL, 0);\n"); } } fprintf(f, "};\n\n"); @@ -434,6 +427,7 @@ void printPlaybackCpp(FILE *f) { for (ct=0; ct < apiCount; ct++) { const ApiEntry * api = &apis[ct]; + int needFlush = 0; if (api->direct) { continue; @@ -444,6 +438,13 @@ void printPlaybackCpp(FILE *f) { //fprintf(f, " ALOGE(\"play command %s\\n\");\n", api->name); fprintf(f, " const RS_CMD_%s *cmd = static_cast<const RS_CMD_%s *>(vp);\n", api->name, api->name); + if (hasInlineDataPointers(api)) { + fprintf(f, " const uint8_t *baseData = 0;\n"); + fprintf(f, " if (cmdSizeBytes != sizeof(RS_CMD_%s)) {\n", api->name); + fprintf(f, " baseData = &((const uint8_t *)vp)[sizeof(*cmd)];\n"); + fprintf(f, " }\n"); + } + fprintf(f, " "); if (api->ret.typeName[0]) { fprintf(f, "\n "); @@ -453,12 +454,31 @@ void printPlaybackCpp(FILE *f) { fprintf(f, "rsi_%s(con", api->name); for (ct2=0; ct2 < api->paramCount; ct2++) { const VarType *vt = &api->params[ct2]; - fprintf(f, ",\n cmd->%s", vt->name); + needFlush += vt->ptrLevel; + + if (hasInlineDataPointers(api) && vt->ptrLevel) { + fprintf(f, ",\n (const %s *)&baseData[(intptr_t)cmd->%s]", vt->typeName, vt->name); + } else { + fprintf(f, ",\n cmd->%s", vt->name); + } } fprintf(f, ");\n"); - if (api->ret.typeName[0]) { + if (hasInlineDataPointers(api)) { + fprintf(f, " size_t totalSize = 0;\n"); + for (ct2=0; ct2 < api->paramCount; ct2++) { + if (api->params[ct2].ptrLevel) { + fprintf(f, " totalSize += cmd->%s_length;\n", api->params[ct2].name); + } + } + + fprintf(f, " if ((totalSize != 0) && (cmdSizeBytes == sizeof(RS_CMD_%s))) {\n", api->name); + fprintf(f, " con->mIO.coreSetReturn(NULL, 0);\n"); + fprintf(f, " }\n"); + } else if (api->ret.typeName[0]) { fprintf(f, " con->mIO.coreSetReturn(&ret, sizeof(ret));\n"); + } else if (api->sync || needFlush) { + fprintf(f, " con->mIO.coreSetReturn(NULL, 0);\n"); } fprintf(f, "};\n\n"); @@ -466,6 +486,7 @@ void printPlaybackCpp(FILE *f) { for (ct=0; ct < apiCount; ct++) { const ApiEntry * api = &apis[ct]; + int needFlush = 0; fprintf(f, "void rspr_%s(Context *con, Fifo *f, uint8_t *scratch, size_t scratchSize) {\n", api->name); @@ -475,6 +496,7 @@ void printPlaybackCpp(FILE *f) { for (ct2=0; ct2 < api->paramCount; ct2++) { const VarType *vt = &api->params[ct2]; + needFlush += vt->ptrLevel; if (vt->ptrLevel == 1) { fprintf(f, " cmd.%s = (", vt->name); printVarType(f, vt); @@ -515,6 +537,8 @@ void printPlaybackCpp(FILE *f) { if (api->ret.typeName[0]) { fprintf(f, " f->readReturn(&ret, sizeof(ret));\n"); + } else if (needFlush) { + fprintf(f, " f->readReturn(NULL, 0);\n"); } fprintf(f, "};\n\n"); diff --git a/libs/ui/Region.cpp b/libs/ui/Region.cpp index 8cd047afb8db..6e2e731e3d5a 100644 --- a/libs/ui/Region.cpp +++ b/libs/ui/Region.cpp @@ -126,6 +126,9 @@ void Region::addRectUnchecked(int l, int t, int r, int b) Region& Region::orSelf(const Rect& r) { return operationSelf(r, op_or); } +Region& Region::xorSelf(const Rect& r) { + return operationSelf(r, op_xor); +} Region& Region::andSelf(const Rect& r) { return operationSelf(r, op_and); } @@ -143,6 +146,9 @@ Region& Region::operationSelf(const Rect& r, int op) { Region& Region::orSelf(const Region& rhs) { return operationSelf(rhs, op_or); } +Region& Region::xorSelf(const Region& rhs) { + return operationSelf(rhs, op_xor); +} Region& Region::andSelf(const Region& rhs) { return operationSelf(rhs, op_and); } @@ -165,6 +171,9 @@ Region& Region::translateSelf(int x, int y) { const Region Region::merge(const Rect& rhs) const { return operation(rhs, op_or); } +const Region Region::mergeExclusive(const Rect& rhs) const { + return operation(rhs, op_xor); +} const Region Region::intersect(const Rect& rhs) const { return operation(rhs, op_and); } @@ -182,6 +191,9 @@ const Region Region::operation(const Rect& rhs, int op) const { const Region Region::merge(const Region& rhs) const { return operation(rhs, op_or); } +const Region Region::mergeExclusive(const Region& rhs) const { + return operation(rhs, op_xor); +} const Region Region::intersect(const Region& rhs) const { return operation(rhs, op_and); } @@ -205,6 +217,9 @@ const Region Region::translate(int x, int y) const { Region& Region::orSelf(const Region& rhs, int dx, int dy) { return operationSelf(rhs, dx, dy, op_or); } +Region& Region::xorSelf(const Region& rhs, int dx, int dy) { + return operationSelf(rhs, dx, dy, op_xor); +} Region& Region::andSelf(const Region& rhs, int dx, int dy) { return operationSelf(rhs, dx, dy, op_and); } @@ -222,6 +237,9 @@ Region& Region::operationSelf(const Region& rhs, int dx, int dy, int op) { const Region Region::merge(const Region& rhs, int dx, int dy) const { return operation(rhs, dx, dy, op_or); } +const Region Region::mergeExclusive(const Region& rhs, int dx, int dy) const { + return operation(rhs, dx, dy, op_xor); +} const Region Region::intersect(const Region& rhs, int dx, int dy) const { return operation(rhs, dx, dy, op_and); } @@ -421,6 +439,7 @@ void Region::boolean_operation(int op, Region& dst, SkRegion::Op sk_op; switch (op) { case op_or: sk_op = SkRegion::kUnion_Op; name="OR"; break; + case op_xor: sk_op = SkRegion::kUnion_XOR; name="XOR"; break; case op_and: sk_op = SkRegion::kIntersect_Op; name="AND"; break; case op_nand: sk_op = SkRegion::kDifference_Op; name="NAND"; break; } diff --git a/libs/usb/tests/AccessoryChat/Android.mk b/libs/usb/tests/AccessoryChat/Android.mk index 77b84244805a..ecb455a90e29 100644 --- a/libs/usb/tests/AccessoryChat/Android.mk +++ b/libs/usb/tests/AccessoryChat/Android.mk @@ -23,9 +23,4 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := AccessoryChat -LOCAL_JAVA_LIBRARIES := com.android.future.usb.accessory - -# Force an old SDK version to make sure we aren't using newer UsbManager APIs -LOCAL_SDK_VERSION := 8 - include $(BUILD_PACKAGE) diff --git a/libs/usb/tests/AccessoryChat/AndroidManifest.xml b/libs/usb/tests/AccessoryChat/AndroidManifest.xml index 802b715d3bd1..6667ebaa4d49 100644 --- a/libs/usb/tests/AccessoryChat/AndroidManifest.xml +++ b/libs/usb/tests/AccessoryChat/AndroidManifest.xml @@ -17,8 +17,9 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.accessorychat"> + <uses-feature android:name="android.hardware.usb.accessory" /> + <application android:label="Accessory Chat"> - <uses-library android:name="com.android.future.usb.accessory" /> <activity android:name="AccessoryChat" android:label="Accessory Chat"> <intent-filter> @@ -35,5 +36,5 @@ android:resource="@xml/accessory_filter" /> </activity> </application> - <uses-sdk android:minSdkVersion="10" /> + <uses-sdk android:minSdkVersion="12" /> </manifest> diff --git a/libs/usb/tests/AccessoryChat/accessorychat/accessorychat.c b/libs/usb/tests/AccessoryChat/accessorychat/accessorychat.c index 85b52dd49698..06b477f5a62a 100644 --- a/libs/usb/tests/AccessoryChat/accessorychat/accessorychat.c +++ b/libs/usb/tests/AccessoryChat/accessorychat/accessorychat.c @@ -98,7 +98,7 @@ static int usb_device_added(const char *devname, void* client_data) { vendorId = usb_device_get_vendor_id(device); productId = usb_device_get_product_id(device); - if (vendorId == 0x18D1 || vendorId == 0x22B8) { + if (vendorId == 0x18D1 || vendorId == 0x22B8 || vendorId == 0x04e8) { if (!sDevice && (productId == 0x2D00 || productId == 0x2D01)) { struct usb_descriptor_header* desc; struct usb_descriptor_iter iter; diff --git a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java index c3f4fa3d1d0e..bf0cef01ac7b 100644 --- a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java +++ b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java @@ -33,8 +33,8 @@ import android.util.Log; import android.widget.EditText; import android.widget.TextView; -import com.android.future.usb.UsbAccessory; -import com.android.future.usb.UsbManager; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbAccessory; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -64,9 +64,11 @@ public class AccessoryChat extends Activity implements Runnable, TextView.OnEdit public void onReceive(Context context, Intent intent) { if (ACTION_USB_PERMISSION.equals(intent.getAction())) { synchronized (this) { - UsbAccessory accessory = UsbManager.getAccessory(intent); + UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { - openAccessory(accessory); + if (accessory != null) { + openAccessory(accessory); + } } else { Log.d(TAG, "permission denied for accessory " + accessory); } @@ -80,7 +82,7 @@ public class AccessoryChat extends Activity implements Runnable, TextView.OnEdit public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mUsbManager = UsbManager.getInstance(this); + mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(mUsbReceiver, filter); diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index 544ab744e33a..24cf5048fa70 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -98,7 +98,8 @@ endif LOCAL_C_INCLUDES += \ external/zlib \ - external/icu4c/common + external/icu4c/common \ + bionic/libc/private LOCAL_LDLIBS += -lpthread @@ -114,7 +115,10 @@ include $(BUILD_SHARED_LIBRARY) ifeq ($(TARGET_OS),linux) include $(CLEAR_VARS) -LOCAL_C_INCLUDES += external/zlib external/icu4c/common +LOCAL_C_INCLUDES += \ + external/zlib \ + external/icu4c/common \ + bionic/libc/private LOCAL_LDLIBS := -lrt -ldl -lpthread LOCAL_MODULE := libutils LOCAL_SRC_FILES := $(commonSources) BackupData.cpp BackupHelpers.cpp diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index 15b83bbd2cfa..3fa562ec6074 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -221,7 +221,7 @@ static void deserializeInternal(const void* inData, Res_png_9patch* outData) { static bool assertIdmapHeader(const uint32_t* map, size_t sizeBytes) { if (sizeBytes < ResTable::IDMAP_HEADER_SIZE_BYTES) { - ALOGW("idmap assertion failed: size=%d bytes\n", sizeBytes); + ALOGW("idmap assertion failed: size=%d bytes\n", (int)sizeBytes); return false; } if (*map != htodl(IDMAP_MAGIC)) { // htodl: map data expected to be in correct endianess @@ -250,7 +250,7 @@ static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, return UNKNOWN_ERROR; } if (typeCount > size) { - ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, size); + ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, (int)size); return UNKNOWN_ERROR; } const uint32_t typeOffset = map[type]; @@ -260,7 +260,7 @@ static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, } if (typeOffset + 1 > size) { ALOGW("Resource ID map: type offset=%d exceeds reasonable value, size of map=%d\n", - typeOffset, size); + typeOffset, (int)size); return UNKNOWN_ERROR; } const uint32_t entryCount = map[typeOffset]; @@ -271,7 +271,7 @@ static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, } const uint32_t index = typeOffset + 2 + entry - entryOffset; if (index > size) { - ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, size); + ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, (int)size); *outValue = 0; return NO_ERROR; } @@ -659,6 +659,16 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const return NULL; } +const String8 ResStringPool::string8ObjectAt(size_t idx) const +{ + size_t len; + const char *str = (const char*)string8At(idx, &len); + if (str != NULL) { + return String8(str); + } + return String8(stringAt(idx, &len)); +} + const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const { return styleAt(ref.index); @@ -738,12 +748,25 @@ size_t ResStringPool::size() const return (mError == NO_ERROR) ? mHeader->stringCount : 0; } -#ifndef HAVE_ANDROID_OS +size_t ResStringPool::styleCount() const +{ + return (mError == NO_ERROR) ? mHeader->styleCount : 0; +} + +size_t ResStringPool::bytes() const +{ + return (mError == NO_ERROR) ? mHeader->header.size : 0; +} + +bool ResStringPool::isSorted() const +{ + return (mHeader->flags&ResStringPool_header::SORTED_FLAG)!=0; +} + bool ResStringPool::isUTF8() const { return (mHeader->flags&ResStringPool_header::UTF8_FLAG)!=0; } -#endif // -------------------------------------------------------------------- // -------------------------------------------------------------------- @@ -1367,6 +1390,873 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const // -------------------------------------------------------------------- // -------------------------------------------------------------------- +void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) { + const size_t size = dtohl(o.size); + if (size >= sizeof(ResTable_config)) { + *this = o; + } else { + memcpy(this, &o, size); + memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size); + } +} + +void ResTable_config::copyFromDtoH(const ResTable_config& o) { + copyFromDeviceNoSwap(o); + size = sizeof(ResTable_config); + mcc = dtohs(mcc); + mnc = dtohs(mnc); + density = dtohs(density); + screenWidth = dtohs(screenWidth); + screenHeight = dtohs(screenHeight); + sdkVersion = dtohs(sdkVersion); + minorVersion = dtohs(minorVersion); + smallestScreenWidthDp = dtohs(smallestScreenWidthDp); + screenWidthDp = dtohs(screenWidthDp); + screenHeightDp = dtohs(screenHeightDp); +} + +void ResTable_config::swapHtoD() { + size = htodl(size); + mcc = htods(mcc); + mnc = htods(mnc); + density = htods(density); + screenWidth = htods(screenWidth); + screenHeight = htods(screenHeight); + sdkVersion = htods(sdkVersion); + minorVersion = htods(minorVersion); + smallestScreenWidthDp = htods(smallestScreenWidthDp); + screenWidthDp = htods(screenWidthDp); + screenHeightDp = htods(screenHeightDp); +} + +int ResTable_config::compare(const ResTable_config& o) const { + int32_t diff = (int32_t)(imsi - o.imsi); + if (diff != 0) return diff; + diff = (int32_t)(locale - o.locale); + if (diff != 0) return diff; + diff = (int32_t)(screenType - o.screenType); + if (diff != 0) return diff; + diff = (int32_t)(input - o.input); + if (diff != 0) return diff; + diff = (int32_t)(screenSize - o.screenSize); + if (diff != 0) return diff; + diff = (int32_t)(version - o.version); + if (diff != 0) return diff; + diff = (int32_t)(screenLayout - o.screenLayout); + if (diff != 0) return diff; + diff = (int32_t)(uiMode - o.uiMode); + if (diff != 0) return diff; + diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp); + if (diff != 0) return diff; + diff = (int32_t)(screenSizeDp - o.screenSizeDp); + return (int)diff; +} + +int ResTable_config::compareLogical(const ResTable_config& o) const { + if (mcc != o.mcc) { + return mcc < o.mcc ? -1 : 1; + } + if (mnc != o.mnc) { + return mnc < o.mnc ? -1 : 1; + } + if (language[0] != o.language[0]) { + return language[0] < o.language[0] ? -1 : 1; + } + if (language[1] != o.language[1]) { + return language[1] < o.language[1] ? -1 : 1; + } + if (country[0] != o.country[0]) { + return country[0] < o.country[0] ? -1 : 1; + } + if (country[1] != o.country[1]) { + return country[1] < o.country[1] ? -1 : 1; + } + if (smallestScreenWidthDp != o.smallestScreenWidthDp) { + return smallestScreenWidthDp < o.smallestScreenWidthDp ? -1 : 1; + } + if (screenWidthDp != o.screenWidthDp) { + return screenWidthDp < o.screenWidthDp ? -1 : 1; + } + if (screenHeightDp != o.screenHeightDp) { + return screenHeightDp < o.screenHeightDp ? -1 : 1; + } + if (screenWidth != o.screenWidth) { + return screenWidth < o.screenWidth ? -1 : 1; + } + if (screenHeight != o.screenHeight) { + return screenHeight < o.screenHeight ? -1 : 1; + } + if (density != o.density) { + return density < o.density ? -1 : 1; + } + if (orientation != o.orientation) { + return orientation < o.orientation ? -1 : 1; + } + if (touchscreen != o.touchscreen) { + return touchscreen < o.touchscreen ? -1 : 1; + } + if (input != o.input) { + return input < o.input ? -1 : 1; + } + if (screenLayout != o.screenLayout) { + return screenLayout < o.screenLayout ? -1 : 1; + } + if (uiMode != o.uiMode) { + return uiMode < o.uiMode ? -1 : 1; + } + if (version != o.version) { + return version < o.version ? -1 : 1; + } + return 0; +} + +int ResTable_config::diff(const ResTable_config& o) const { + int diffs = 0; + if (mcc != o.mcc) diffs |= CONFIG_MCC; + if (mnc != o.mnc) diffs |= CONFIG_MNC; + if (locale != o.locale) diffs |= CONFIG_LOCALE; + if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION; + if (density != o.density) diffs |= CONFIG_DENSITY; + if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN; + if (((inputFlags^o.inputFlags)&(MASK_KEYSHIDDEN|MASK_NAVHIDDEN)) != 0) + diffs |= CONFIG_KEYBOARD_HIDDEN; + if (keyboard != o.keyboard) diffs |= CONFIG_KEYBOARD; + if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION; + if (screenSize != o.screenSize) diffs |= CONFIG_SCREEN_SIZE; + if (version != o.version) diffs |= CONFIG_VERSION; + if (screenLayout != o.screenLayout) diffs |= CONFIG_SCREEN_LAYOUT; + if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE; + if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE; + if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE; + return diffs; +} + +bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const { + // The order of the following tests defines the importance of one + // configuration parameter over another. Those tests first are more + // important, trumping any values in those following them. + if (imsi || o.imsi) { + if (mcc != o.mcc) { + if (!mcc) return false; + if (!o.mcc) return true; + } + + if (mnc != o.mnc) { + if (!mnc) return false; + if (!o.mnc) return true; + } + } + + if (locale || o.locale) { + if (language[0] != o.language[0]) { + if (!language[0]) return false; + if (!o.language[0]) return true; + } + + if (country[0] != o.country[0]) { + if (!country[0]) return false; + if (!o.country[0]) return true; + } + } + + if (smallestScreenWidthDp || o.smallestScreenWidthDp) { + if (smallestScreenWidthDp != o.smallestScreenWidthDp) { + if (!smallestScreenWidthDp) return false; + if (!o.smallestScreenWidthDp) return true; + } + } + + if (screenSizeDp || o.screenSizeDp) { + if (screenWidthDp != o.screenWidthDp) { + if (!screenWidthDp) return false; + if (!o.screenWidthDp) return true; + } + + if (screenHeightDp != o.screenHeightDp) { + if (!screenHeightDp) return false; + if (!o.screenHeightDp) return true; + } + } + + if (screenLayout || o.screenLayout) { + if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0) { + if (!(screenLayout & MASK_SCREENSIZE)) return false; + if (!(o.screenLayout & MASK_SCREENSIZE)) return true; + } + if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0) { + if (!(screenLayout & MASK_SCREENLONG)) return false; + if (!(o.screenLayout & MASK_SCREENLONG)) return true; + } + } + + if (orientation != o.orientation) { + if (!orientation) return false; + if (!o.orientation) return true; + } + + if (uiMode || o.uiMode) { + if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0) { + if (!(uiMode & MASK_UI_MODE_TYPE)) return false; + if (!(o.uiMode & MASK_UI_MODE_TYPE)) return true; + } + if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0) { + if (!(uiMode & MASK_UI_MODE_NIGHT)) return false; + if (!(o.uiMode & MASK_UI_MODE_NIGHT)) return true; + } + } + + // density is never 'more specific' + // as the default just equals 160 + + if (touchscreen != o.touchscreen) { + if (!touchscreen) return false; + if (!o.touchscreen) return true; + } + + if (input || o.input) { + if (((inputFlags^o.inputFlags) & MASK_KEYSHIDDEN) != 0) { + if (!(inputFlags & MASK_KEYSHIDDEN)) return false; + if (!(o.inputFlags & MASK_KEYSHIDDEN)) return true; + } + + if (((inputFlags^o.inputFlags) & MASK_NAVHIDDEN) != 0) { + if (!(inputFlags & MASK_NAVHIDDEN)) return false; + if (!(o.inputFlags & MASK_NAVHIDDEN)) return true; + } + + if (keyboard != o.keyboard) { + if (!keyboard) return false; + if (!o.keyboard) return true; + } + + if (navigation != o.navigation) { + if (!navigation) return false; + if (!o.navigation) return true; + } + } + + if (screenSize || o.screenSize) { + if (screenWidth != o.screenWidth) { + if (!screenWidth) return false; + if (!o.screenWidth) return true; + } + + if (screenHeight != o.screenHeight) { + if (!screenHeight) return false; + if (!o.screenHeight) return true; + } + } + + if (version || o.version) { + if (sdkVersion != o.sdkVersion) { + if (!sdkVersion) return false; + if (!o.sdkVersion) return true; + } + + if (minorVersion != o.minorVersion) { + if (!minorVersion) return false; + if (!o.minorVersion) return true; + } + } + return false; +} + +bool ResTable_config::isBetterThan(const ResTable_config& o, + const ResTable_config* requested) const { + if (requested) { + if (imsi || o.imsi) { + if ((mcc != o.mcc) && requested->mcc) { + return (mcc); + } + + if ((mnc != o.mnc) && requested->mnc) { + return (mnc); + } + } + + if (locale || o.locale) { + if ((language[0] != o.language[0]) && requested->language[0]) { + return (language[0]); + } + + if ((country[0] != o.country[0]) && requested->country[0]) { + return (country[0]); + } + } + + if (smallestScreenWidthDp || o.smallestScreenWidthDp) { + // The configuration closest to the actual size is best. + // We assume that larger configs have already been filtered + // out at this point. That means we just want the largest one. + return smallestScreenWidthDp >= o.smallestScreenWidthDp; + } + + if (screenSizeDp || o.screenSizeDp) { + // "Better" is based on the sum of the difference between both + // width and height from the requested dimensions. We are + // assuming the invalid configs (with smaller dimens) have + // already been filtered. Note that if a particular dimension + // is unspecified, we will end up with a large value (the + // difference between 0 and the requested dimension), which is + // good since we will prefer a config that has specified a + // dimension value. + int myDelta = 0, otherDelta = 0; + if (requested->screenWidthDp) { + myDelta += requested->screenWidthDp - screenWidthDp; + otherDelta += requested->screenWidthDp - o.screenWidthDp; + } + if (requested->screenHeightDp) { + myDelta += requested->screenHeightDp - screenHeightDp; + otherDelta += requested->screenHeightDp - o.screenHeightDp; + } + //ALOGI("Comparing this %dx%d to other %dx%d in %dx%d: myDelta=%d otherDelta=%d", + // screenWidthDp, screenHeightDp, o.screenWidthDp, o.screenHeightDp, + // requested->screenWidthDp, requested->screenHeightDp, myDelta, otherDelta); + return (myDelta <= otherDelta); + } + + if (screenLayout || o.screenLayout) { + if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0 + && (requested->screenLayout & MASK_SCREENSIZE)) { + // A little backwards compatibility here: undefined is + // considered equivalent to normal. But only if the + // requested size is at least normal; otherwise, small + // is better than the default. + int mySL = (screenLayout & MASK_SCREENSIZE); + int oSL = (o.screenLayout & MASK_SCREENSIZE); + int fixedMySL = mySL; + int fixedOSL = oSL; + if ((requested->screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) { + if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL; + if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL; + } + // For screen size, the best match is the one that is + // closest to the requested screen size, but not over + // (the not over part is dealt with in match() below). + if (fixedMySL == fixedOSL) { + // If the two are the same, but 'this' is actually + // undefined, then the other is really a better match. + if (mySL == 0) return false; + return true; + } + return fixedMySL >= fixedOSL; + } + if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0 + && (requested->screenLayout & MASK_SCREENLONG)) { + return (screenLayout & MASK_SCREENLONG); + } + } + + if ((orientation != o.orientation) && requested->orientation) { + return (orientation); + } + + if (uiMode || o.uiMode) { + if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0 + && (requested->uiMode & MASK_UI_MODE_TYPE)) { + return (uiMode & MASK_UI_MODE_TYPE); + } + if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0 + && (requested->uiMode & MASK_UI_MODE_NIGHT)) { + return (uiMode & MASK_UI_MODE_NIGHT); + } + } + + if (screenType || o.screenType) { + if (density != o.density) { + // density is tough. Any density is potentially useful + // because the system will scale it. Scaling down + // is generally better than scaling up. + // Default density counts as 160dpi (the system default) + // TODO - remove 160 constants + int h = (density?density:160); + int l = (o.density?o.density:160); + bool bImBigger = true; + if (l > h) { + int t = h; + h = l; + l = t; + bImBigger = false; + } + + int reqValue = (requested->density?requested->density:160); + if (reqValue >= h) { + // requested value higher than both l and h, give h + return bImBigger; + } + if (l >= reqValue) { + // requested value lower than both l and h, give l + return !bImBigger; + } + // saying that scaling down is 2x better than up + if (((2 * l) - reqValue) * h > reqValue * reqValue) { + return !bImBigger; + } else { + return bImBigger; + } + } + + if ((touchscreen != o.touchscreen) && requested->touchscreen) { + return (touchscreen); + } + } + + if (input || o.input) { + const int keysHidden = inputFlags & MASK_KEYSHIDDEN; + const int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN; + if (keysHidden != oKeysHidden) { + const int reqKeysHidden = + requested->inputFlags & MASK_KEYSHIDDEN; + if (reqKeysHidden) { + + if (!keysHidden) return false; + if (!oKeysHidden) return true; + // For compatibility, we count KEYSHIDDEN_NO as being + // the same as KEYSHIDDEN_SOFT. Here we disambiguate + // these by making an exact match more specific. + if (reqKeysHidden == keysHidden) return true; + if (reqKeysHidden == oKeysHidden) return false; + } + } + + const int navHidden = inputFlags & MASK_NAVHIDDEN; + const int oNavHidden = o.inputFlags & MASK_NAVHIDDEN; + if (navHidden != oNavHidden) { + const int reqNavHidden = + requested->inputFlags & MASK_NAVHIDDEN; + if (reqNavHidden) { + + if (!navHidden) return false; + if (!oNavHidden) return true; + } + } + + if ((keyboard != o.keyboard) && requested->keyboard) { + return (keyboard); + } + + if ((navigation != o.navigation) && requested->navigation) { + return (navigation); + } + } + + if (screenSize || o.screenSize) { + // "Better" is based on the sum of the difference between both + // width and height from the requested dimensions. We are + // assuming the invalid configs (with smaller sizes) have + // already been filtered. Note that if a particular dimension + // is unspecified, we will end up with a large value (the + // difference between 0 and the requested dimension), which is + // good since we will prefer a config that has specified a + // size value. + int myDelta = 0, otherDelta = 0; + if (requested->screenWidth) { + myDelta += requested->screenWidth - screenWidth; + otherDelta += requested->screenWidth - o.screenWidth; + } + if (requested->screenHeight) { + myDelta += requested->screenHeight - screenHeight; + otherDelta += requested->screenHeight - o.screenHeight; + } + return (myDelta <= otherDelta); + } + + if (version || o.version) { + if ((sdkVersion != o.sdkVersion) && requested->sdkVersion) { + return (sdkVersion > o.sdkVersion); + } + + if ((minorVersion != o.minorVersion) && + requested->minorVersion) { + return (minorVersion); + } + } + + return false; + } + return isMoreSpecificThan(o); +} + +bool ResTable_config::match(const ResTable_config& settings) const { + if (imsi != 0) { + if (mcc != 0 && mcc != settings.mcc) { + return false; + } + if (mnc != 0 && mnc != settings.mnc) { + return false; + } + } + if (locale != 0) { + if (language[0] != 0 + && (language[0] != settings.language[0] + || language[1] != settings.language[1])) { + return false; + } + if (country[0] != 0 + && (country[0] != settings.country[0] + || country[1] != settings.country[1])) { + return false; + } + } + if (screenConfig != 0) { + const int screenSize = screenLayout&MASK_SCREENSIZE; + const int setScreenSize = settings.screenLayout&MASK_SCREENSIZE; + // Any screen sizes for larger screens than the setting do not + // match. + if (screenSize != 0 && screenSize > setScreenSize) { + return false; + } + + const int screenLong = screenLayout&MASK_SCREENLONG; + const int setScreenLong = settings.screenLayout&MASK_SCREENLONG; + if (screenLong != 0 && screenLong != setScreenLong) { + return false; + } + + const int uiModeType = uiMode&MASK_UI_MODE_TYPE; + const int setUiModeType = settings.uiMode&MASK_UI_MODE_TYPE; + if (uiModeType != 0 && uiModeType != setUiModeType) { + return false; + } + + const int uiModeNight = uiMode&MASK_UI_MODE_NIGHT; + const int setUiModeNight = settings.uiMode&MASK_UI_MODE_NIGHT; + if (uiModeNight != 0 && uiModeNight != setUiModeNight) { + return false; + } + + if (smallestScreenWidthDp != 0 + && smallestScreenWidthDp > settings.smallestScreenWidthDp) { + return false; + } + } + if (screenSizeDp != 0) { + if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) { + //ALOGI("Filtering out width %d in requested %d", screenWidthDp, settings.screenWidthDp); + return false; + } + if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) { + //ALOGI("Filtering out height %d in requested %d", screenHeightDp, settings.screenHeightDp); + return false; + } + } + if (screenType != 0) { + if (orientation != 0 && orientation != settings.orientation) { + return false; + } + // density always matches - we can scale it. See isBetterThan + if (touchscreen != 0 && touchscreen != settings.touchscreen) { + return false; + } + } + if (input != 0) { + const int keysHidden = inputFlags&MASK_KEYSHIDDEN; + const int setKeysHidden = settings.inputFlags&MASK_KEYSHIDDEN; + if (keysHidden != 0 && keysHidden != setKeysHidden) { + // For compatibility, we count a request for KEYSHIDDEN_NO as also + // matching the more recent KEYSHIDDEN_SOFT. Basically + // KEYSHIDDEN_NO means there is some kind of keyboard available. + //ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden); + if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) { + //ALOGI("No match!"); + return false; + } + } + const int navHidden = inputFlags&MASK_NAVHIDDEN; + const int setNavHidden = settings.inputFlags&MASK_NAVHIDDEN; + if (navHidden != 0 && navHidden != setNavHidden) { + return false; + } + if (keyboard != 0 && keyboard != settings.keyboard) { + return false; + } + if (navigation != 0 && navigation != settings.navigation) { + return false; + } + } + if (screenSize != 0) { + if (screenWidth != 0 && screenWidth > settings.screenWidth) { + return false; + } + if (screenHeight != 0 && screenHeight > settings.screenHeight) { + return false; + } + } + if (version != 0) { + if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) { + return false; + } + if (minorVersion != 0 && minorVersion != settings.minorVersion) { + return false; + } + } + return true; +} + +void ResTable_config::getLocale(char str[6]) const { + memset(str, 0, 6); + if (language[0]) { + str[0] = language[0]; + str[1] = language[1]; + if (country[0]) { + str[2] = '_'; + str[3] = country[0]; + str[4] = country[1]; + } + } +} + +String8 ResTable_config::toString() const { + String8 res; + + if (mcc != 0) { + if (res.size() > 0) res.append("-"); + res.appendFormat("%dmcc", dtohs(mcc)); + } + if (mnc != 0) { + if (res.size() > 0) res.append("-"); + res.appendFormat("%dmnc", dtohs(mnc)); + } + if (language[0] != 0) { + if (res.size() > 0) res.append("-"); + res.append(language, 2); + } + if (country[0] != 0) { + if (res.size() > 0) res.append("-"); + res.append(country, 2); + } + if (smallestScreenWidthDp != 0) { + if (res.size() > 0) res.append("-"); + res.appendFormat("sw%ddp", dtohs(smallestScreenWidthDp)); + } + if (screenWidthDp != 0) { + if (res.size() > 0) res.append("-"); + res.appendFormat("w%ddp", dtohs(screenWidthDp)); + } + if (screenHeightDp != 0) { + if (res.size() > 0) res.append("-"); + res.appendFormat("h%ddp", dtohs(screenHeightDp)); + } + if ((screenLayout&MASK_SCREENSIZE) != SCREENSIZE_ANY) { + if (res.size() > 0) res.append("-"); + switch (screenLayout&ResTable_config::MASK_SCREENSIZE) { + case ResTable_config::SCREENSIZE_SMALL: + res.append("small"); + break; + case ResTable_config::SCREENSIZE_NORMAL: + res.append("normal"); + break; + case ResTable_config::SCREENSIZE_LARGE: + res.append("large"); + break; + case ResTable_config::SCREENSIZE_XLARGE: + res.append("xlarge"); + break; + default: + res.appendFormat("screenLayoutSize=%d", + dtohs(screenLayout&ResTable_config::MASK_SCREENSIZE)); + break; + } + } + if ((screenLayout&MASK_SCREENLONG) != 0) { + if (res.size() > 0) res.append("-"); + switch (screenLayout&ResTable_config::MASK_SCREENLONG) { + case ResTable_config::SCREENLONG_NO: + res.append("notlong"); + break; + case ResTable_config::SCREENLONG_YES: + res.append("long"); + break; + default: + res.appendFormat("screenLayoutLong=%d", + dtohs(screenLayout&ResTable_config::MASK_SCREENLONG)); + break; + } + } + if (orientation != ORIENTATION_ANY) { + if (res.size() > 0) res.append("-"); + switch (orientation) { + case ResTable_config::ORIENTATION_PORT: + res.append("port"); + break; + case ResTable_config::ORIENTATION_LAND: + res.append("land"); + break; + case ResTable_config::ORIENTATION_SQUARE: + res.append("square"); + break; + default: + res.appendFormat("orientation=%d", dtohs(orientation)); + break; + } + } + if ((uiMode&MASK_UI_MODE_TYPE) != UI_MODE_TYPE_ANY) { + if (res.size() > 0) res.append("-"); + switch (uiMode&ResTable_config::MASK_UI_MODE_TYPE) { + case ResTable_config::UI_MODE_TYPE_DESK: + res.append("desk"); + break; + case ResTable_config::UI_MODE_TYPE_CAR: + res.append("car"); + break; + case ResTable_config::UI_MODE_TYPE_TELEVISION: + res.append("television"); + break; + case ResTable_config::UI_MODE_TYPE_APPLIANCE: + res.append("appliance"); + break; + default: + res.appendFormat("uiModeType=%d", + dtohs(screenLayout&ResTable_config::MASK_UI_MODE_TYPE)); + break; + } + } + if ((uiMode&MASK_UI_MODE_NIGHT) != 0) { + if (res.size() > 0) res.append("-"); + switch (uiMode&ResTable_config::MASK_UI_MODE_NIGHT) { + case ResTable_config::UI_MODE_NIGHT_NO: + res.append("notnight"); + break; + case ResTable_config::UI_MODE_NIGHT_YES: + res.append("night"); + break; + default: + res.appendFormat("uiModeNight=%d", + dtohs(uiMode&MASK_UI_MODE_NIGHT)); + break; + } + } + if (density != DENSITY_DEFAULT) { + if (res.size() > 0) res.append("-"); + switch (density) { + case ResTable_config::DENSITY_LOW: + res.append("ldpi"); + break; + case ResTable_config::DENSITY_MEDIUM: + res.append("mdpi"); + break; + case ResTable_config::DENSITY_TV: + res.append("tvdpi"); + break; + case ResTable_config::DENSITY_HIGH: + res.append("hdpi"); + break; + case ResTable_config::DENSITY_XHIGH: + res.append("xhdpi"); + break; + case ResTable_config::DENSITY_XXHIGH: + res.append("xxhdpi"); + break; + case ResTable_config::DENSITY_NONE: + res.append("nodpi"); + break; + default: + res.appendFormat("density=%d", dtohs(density)); + break; + } + } + if (touchscreen != TOUCHSCREEN_ANY) { + if (res.size() > 0) res.append("-"); + switch (touchscreen) { + case ResTable_config::TOUCHSCREEN_NOTOUCH: + res.append("notouch"); + break; + case ResTable_config::TOUCHSCREEN_FINGER: + res.append("finger"); + break; + case ResTable_config::TOUCHSCREEN_STYLUS: + res.append("stylus"); + break; + default: + res.appendFormat("touchscreen=%d", dtohs(touchscreen)); + break; + } + } + if (keyboard != KEYBOARD_ANY) { + if (res.size() > 0) res.append("-"); + switch (keyboard) { + case ResTable_config::KEYBOARD_NOKEYS: + res.append("nokeys"); + break; + case ResTable_config::KEYBOARD_QWERTY: + res.append("qwerty"); + break; + case ResTable_config::KEYBOARD_12KEY: + res.append("12key"); + break; + default: + res.appendFormat("keyboard=%d", dtohs(keyboard)); + break; + } + } + if ((inputFlags&MASK_KEYSHIDDEN) != 0) { + if (res.size() > 0) res.append("-"); + switch (inputFlags&MASK_KEYSHIDDEN) { + case ResTable_config::KEYSHIDDEN_NO: + res.append("keysexposed"); + break; + case ResTable_config::KEYSHIDDEN_YES: + res.append("keyshidden"); + break; + case ResTable_config::KEYSHIDDEN_SOFT: + res.append("keyssoft"); + break; + } + } + if (navigation != NAVIGATION_ANY) { + if (res.size() > 0) res.append("-"); + switch (navigation) { + case ResTable_config::NAVIGATION_NONAV: + res.append("nonav"); + break; + case ResTable_config::NAVIGATION_DPAD: + res.append("dpad"); + break; + case ResTable_config::NAVIGATION_TRACKBALL: + res.append("trackball"); + break; + case ResTable_config::NAVIGATION_WHEEL: + res.append("wheel"); + break; + default: + res.appendFormat("navigation=%d", dtohs(navigation)); + break; + } + } + if ((inputFlags&MASK_NAVHIDDEN) != 0) { + if (res.size() > 0) res.append("-"); + switch (inputFlags&MASK_NAVHIDDEN) { + case ResTable_config::NAVHIDDEN_NO: + res.append("navsexposed"); + break; + case ResTable_config::NAVHIDDEN_YES: + res.append("navhidden"); + break; + default: + res.appendFormat("inputFlagsNavHidden=%d", + dtohs(inputFlags&MASK_NAVHIDDEN)); + break; + } + } + if (screenSize != 0) { + if (res.size() > 0) res.append("-"); + res.appendFormat("%dx%d", dtohs(screenWidth), dtohs(screenHeight)); + } + if (version != 0) { + if (res.size() > 0) res.append("-"); + res.appendFormat("v%d", dtohs(sdkVersion)); + if (minorVersion != 0) { + res.appendFormat(".%d", dtohs(minorVersion)); + } + } + + return res; +} + +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- + struct ResTable::Header { Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL), @@ -3953,43 +4843,9 @@ ssize_t ResTable::getEntry( ResTable_config thisConfig; thisConfig.copyFromDtoH(thisType->config); - TABLE_GETENTRY(LOGI("Match entry 0x%x in type 0x%x (sz 0x%x): imsi:%d/%d=%d/%d " - "lang:%c%c=%c%c cnt:%c%c=%c%c orien:%d=%d touch:%d=%d " - "density:%d=%d key:%d=%d inp:%d=%d nav:%d=%d w:%d=%d h:%d=%d " - "swdp:%d=%d wdp:%d=%d hdp:%d=%d\n", + TABLE_GETENTRY(LOGI("Match entry 0x%x in type 0x%x (sz 0x%x): %s\n", entryIndex, typeIndex+1, dtohl(thisType->config.size), - thisConfig.mcc, thisConfig.mnc, - config ? config->mcc : 0, config ? config->mnc : 0, - thisConfig.language[0] ? thisConfig.language[0] : '-', - thisConfig.language[1] ? thisConfig.language[1] : '-', - config && config->language[0] ? config->language[0] : '-', - config && config->language[1] ? config->language[1] : '-', - thisConfig.country[0] ? thisConfig.country[0] : '-', - thisConfig.country[1] ? thisConfig.country[1] : '-', - config && config->country[0] ? config->country[0] : '-', - config && config->country[1] ? config->country[1] : '-', - thisConfig.orientation, - config ? config->orientation : 0, - thisConfig.touchscreen, - config ? config->touchscreen : 0, - thisConfig.density, - config ? config->density : 0, - thisConfig.keyboard, - config ? config->keyboard : 0, - thisConfig.inputFlags, - config ? config->inputFlags : 0, - thisConfig.navigation, - config ? config->navigation : 0, - thisConfig.screenWidth, - config ? config->screenWidth : 0, - thisConfig.screenHeight, - config ? config->screenHeight : 0, - thisConfig.smallestScreenWidthDp, - config ? config->smallestScreenWidthDp : 0, - thisConfig.screenWidthDp, - config ? config->screenWidthDp : 0, - thisConfig.screenHeightDp, - config ? config->screenHeightDp : 0)); + thisConfig.toString().string())); // Check to make sure this one is valid for the current parameters. if (config && !thisConfig.match(*config)) { @@ -4273,26 +5129,8 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, TABLE_GETENTRY( ResTable_config thisConfig; thisConfig.copyFromDtoH(type->config); - ALOGI("Adding config to type %d: imsi:%d/%d lang:%c%c cnt:%c%c " - "orien:%d touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%d " - "swdp:%d wdp:%d hdp:%d\n", - type->id, - thisConfig.mcc, thisConfig.mnc, - thisConfig.language[0] ? thisConfig.language[0] : '-', - thisConfig.language[1] ? thisConfig.language[1] : '-', - thisConfig.country[0] ? thisConfig.country[0] : '-', - thisConfig.country[1] ? thisConfig.country[1] : '-', - thisConfig.orientation, - thisConfig.touchscreen, - thisConfig.density, - thisConfig.keyboard, - thisConfig.inputFlags, - thisConfig.navigation, - thisConfig.screenWidth, - thisConfig.screenHeight, - thisConfig.smallestScreenWidthDp, - thisConfig.screenWidthDp, - thisConfig.screenHeightDp)); + ALOGI("Adding config to type %d: %s\n", + type->id, thisConfig.toString().string())); t->configs.add(type); } else { status_t err = validate_chunk(chunk, sizeof(ResChunk_header), @@ -4622,186 +5460,9 @@ void ResTable::print(bool inclValues) const printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); continue; } - char density[16]; - uint16_t dval = dtohs(type->config.density); - if (dval == ResTable_config::DENSITY_DEFAULT) { - strcpy(density, "def"); - } else if (dval == ResTable_config::DENSITY_NONE) { - strcpy(density, "no"); - } else { - sprintf(density, "%d", (int)dval); - } - printf(" config %d", (int)configIndex); - if (type->config.mcc != 0) { - printf(" mcc=%d", dtohs(type->config.mcc)); - } - if (type->config.mnc != 0) { - printf(" mnc=%d", dtohs(type->config.mnc)); - } - if (type->config.locale != 0) { - printf(" lang=%c%c cnt=%c%c", - type->config.language[0] ? type->config.language[0] : '-', - type->config.language[1] ? type->config.language[1] : '-', - type->config.country[0] ? type->config.country[0] : '-', - type->config.country[1] ? type->config.country[1] : '-'); - } - if (type->config.screenLayout != 0) { - printf(" sz=%d", - type->config.screenLayout&ResTable_config::MASK_SCREENSIZE); - switch (type->config.screenLayout&ResTable_config::MASK_SCREENSIZE) { - case ResTable_config::SCREENSIZE_SMALL: - printf(" (small)"); - break; - case ResTable_config::SCREENSIZE_NORMAL: - printf(" (normal)"); - break; - case ResTable_config::SCREENSIZE_LARGE: - printf(" (large)"); - break; - case ResTable_config::SCREENSIZE_XLARGE: - printf(" (xlarge)"); - break; - } - printf(" lng=%d", - type->config.screenLayout&ResTable_config::MASK_SCREENLONG); - switch (type->config.screenLayout&ResTable_config::MASK_SCREENLONG) { - case ResTable_config::SCREENLONG_NO: - printf(" (notlong)"); - break; - case ResTable_config::SCREENLONG_YES: - printf(" (long)"); - break; - } - } - if (type->config.orientation != 0) { - printf(" orient=%d", type->config.orientation); - switch (type->config.orientation) { - case ResTable_config::ORIENTATION_PORT: - printf(" (port)"); - break; - case ResTable_config::ORIENTATION_LAND: - printf(" (land)"); - break; - case ResTable_config::ORIENTATION_SQUARE: - printf(" (square)"); - break; - } - } - if (type->config.uiMode != 0) { - printf(" type=%d", - type->config.uiMode&ResTable_config::MASK_UI_MODE_TYPE); - switch (type->config.uiMode&ResTable_config::MASK_UI_MODE_TYPE) { - case ResTable_config::UI_MODE_TYPE_NORMAL: - printf(" (normal)"); - break; - case ResTable_config::UI_MODE_TYPE_CAR: - printf(" (car)"); - break; - } - printf(" night=%d", - type->config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT); - switch (type->config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT) { - case ResTable_config::UI_MODE_NIGHT_NO: - printf(" (no)"); - break; - case ResTable_config::UI_MODE_NIGHT_YES: - printf(" (yes)"); - break; - } - } - if (dval != 0) { - printf(" density=%s", density); - } - if (type->config.touchscreen != 0) { - printf(" touch=%d", type->config.touchscreen); - switch (type->config.touchscreen) { - case ResTable_config::TOUCHSCREEN_NOTOUCH: - printf(" (notouch)"); - break; - case ResTable_config::TOUCHSCREEN_STYLUS: - printf(" (stylus)"); - break; - case ResTable_config::TOUCHSCREEN_FINGER: - printf(" (finger)"); - break; - } - } - if (type->config.inputFlags != 0) { - printf(" keyhid=%d", type->config.inputFlags&ResTable_config::MASK_KEYSHIDDEN); - switch (type->config.inputFlags&ResTable_config::MASK_KEYSHIDDEN) { - case ResTable_config::KEYSHIDDEN_NO: - printf(" (no)"); - break; - case ResTable_config::KEYSHIDDEN_YES: - printf(" (yes)"); - break; - case ResTable_config::KEYSHIDDEN_SOFT: - printf(" (soft)"); - break; - } - printf(" navhid=%d", type->config.inputFlags&ResTable_config::MASK_NAVHIDDEN); - switch (type->config.inputFlags&ResTable_config::MASK_NAVHIDDEN) { - case ResTable_config::NAVHIDDEN_NO: - printf(" (no)"); - break; - case ResTable_config::NAVHIDDEN_YES: - printf(" (yes)"); - break; - } - } - if (type->config.keyboard != 0) { - printf(" kbd=%d", type->config.keyboard); - switch (type->config.keyboard) { - case ResTable_config::KEYBOARD_NOKEYS: - printf(" (nokeys)"); - break; - case ResTable_config::KEYBOARD_QWERTY: - printf(" (qwerty)"); - break; - case ResTable_config::KEYBOARD_12KEY: - printf(" (12key)"); - break; - } - } - if (type->config.navigation != 0) { - printf(" nav=%d", type->config.navigation); - switch (type->config.navigation) { - case ResTable_config::NAVIGATION_NONAV: - printf(" (nonav)"); - break; - case ResTable_config::NAVIGATION_DPAD: - printf(" (dpad)"); - break; - case ResTable_config::NAVIGATION_TRACKBALL: - printf(" (trackball)"); - break; - case ResTable_config::NAVIGATION_WHEEL: - printf(" (wheel)"); - break; - } - } - if (type->config.screenWidth != 0) { - printf(" w=%d", dtohs(type->config.screenWidth)); - } - if (type->config.screenHeight != 0) { - printf(" h=%d", dtohs(type->config.screenHeight)); - } - if (type->config.smallestScreenWidthDp != 0) { - printf(" swdp=%d", dtohs(type->config.smallestScreenWidthDp)); - } - if (type->config.screenWidthDp != 0) { - printf(" wdp=%d", dtohs(type->config.screenWidthDp)); - } - if (type->config.screenHeightDp != 0) { - printf(" hdp=%d", dtohs(type->config.screenHeightDp)); - } - if (type->config.sdkVersion != 0) { - printf(" sdk=%d", dtohs(type->config.sdkVersion)); - } - if (type->config.minorVersion != 0) { - printf(" mver=%d", dtohs(type->config.minorVersion)); - } - printf("\n"); + String8 configStr = type->config.toString(); + printf(" config %s:\n", configStr.size() > 0 + ? configStr.string() : "(default)"); size_t entryCount = dtohl(type->entryCount); uint32_t entriesStart = dtohl(type->entriesStart); if ((entriesStart&0x3) != 0) { diff --git a/libs/utils/Threads.cpp b/libs/utils/Threads.cpp index e343c623521b..ab207f5655c5 100644 --- a/libs/utils/Threads.cpp +++ b/libs/utils/Threads.cpp @@ -34,6 +34,9 @@ # include <pthread.h> # include <sched.h> # include <sys/resource.h> +#ifdef HAVE_ANDROID_OS +# include <bionic_pthread.h> +#endif #elif defined(HAVE_WIN32_THREADS) # include <windows.h> # include <stdint.h> @@ -86,7 +89,7 @@ struct thread_data_t { char * threadName; // we use this trampoline when we need to set the priority with - // nice/setpriority. + // nice/setpriority, and name with prctl. static int trampoline(const thread_data_t* t) { thread_func_t f = t->entryFunction; void* u = t->userData; @@ -141,8 +144,13 @@ int androidCreateRawThreadEtc(android_thread_func_t entryFunction, #ifdef HAVE_ANDROID_OS /* valgrind is rejecting RT-priority create reqs */ if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) { - // We could avoid the trampoline if there was a way to get to the - // android_thread_id_t (pid) from pthread_t + // Now that the pthread_t has a method to find the associated + // android_thread_id_t (pid) from pthread_t, it would be possible to avoid + // this trampoline in some cases as the parent could set the properties + // for the child. However, there would be a race condition because the + // child becomes ready immediately, and it doesn't work for the name. + // prctl(PR_SET_NAME) only works for self; prctl(PR_SET_THREAD_NAME) was + // proposed but not yet accepted. thread_data_t* t = new thread_data_t; t->priority = threadPriority; t->threadName = threadName ? strdup(threadName) : NULL; @@ -178,6 +186,13 @@ int androidCreateRawThreadEtc(android_thread_func_t entryFunction, return 1; } +#ifdef HAVE_ANDROID_OS +static pthread_t android_thread_id_t_to_pthread(android_thread_id_t thread) +{ + return (pthread_t) thread; +} +#endif + android_thread_id_t androidGetThreadId() { return (android_thread_id_t)pthread_self(); @@ -909,6 +924,23 @@ status_t Thread::join() return mStatus; } +#ifdef HAVE_ANDROID_OS +pid_t Thread::getTid() const +{ + // mTid is not defined until the child initializes it, and the caller may need it earlier + Mutex::Autolock _l(mLock); + pid_t tid; + if (mRunning) { + pthread_t pthread = android_thread_id_t_to_pthread(mThread); + tid = __pthread_gettid(pthread); + } else { + ALOGW("Thread (this=%p): getTid() is undefined before run()", this); + tid = -1; + } + return tid; +} +#endif + bool Thread::exitPending() const { Mutex::Autolock _l(mLock); diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index a720c0a398ad..85d99c153298 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -204,18 +204,24 @@ public class MediaRecorder /** MPEG4 media file format*/ public static final int MPEG_4 = 2; - /** The following formats are audio only .aac or .amr formats **/ - /** @deprecated Deprecated in favor of AMR_NB */ - /** Deprecated in favor of MediaRecorder.OutputFormat.AMR_NB */ - /** AMR NB file format */ + /** The following formats are audio only .aac or .amr formats */ + + /** + * AMR NB file format + * @deprecated Deprecated in favor of MediaRecorder.OutputFormat.AMR_NB + */ public static final int RAW_AMR = 3; + /** AMR NB file format */ public static final int AMR_NB = 3; + /** AMR WB file format */ public static final int AMR_WB = 4; + /** @hide AAC ADIF file format */ public static final int AAC_ADIF = 5; - /** @hide AAC ADTS file format */ + + /** AAC ADTS file format */ public static final int AAC_ADTS = 6; /** @hide Stream over a socket, limited to a single stream */ diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index cfd6eea7c869..9dc9cef133b2 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -35,6 +35,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; import android.provider.MediaStore; +import android.provider.MediaStore.Files.FileColumns; import android.provider.Settings; import android.provider.MediaStore.Audio; import android.provider.MediaStore.Files; @@ -58,6 +59,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Locale; /** * Internal service helper that no-one should use directly. @@ -312,10 +314,8 @@ public class MediaScanner private final String mExternalStoragePath; - // WARNING: Bulk inserts sounded like a great idea and gave us a good performance improvement, - // but unfortunately it also introduced a number of bugs. All the known bugs were fixed, - // but we need more testing before enabling. - private static final boolean ENABLE_BULK_INSERTS = false; + /** whether to use bulk inserts or individual inserts for each item */ + private static final boolean ENABLE_BULK_INSERTS = true; // used when scanning the image database so we know whether we have to prune // old thumbnail files @@ -946,6 +946,22 @@ public class MediaScanner // path should never change, and we want to avoid replacing mixed cased paths // with squashed lower case paths values.remove(MediaStore.MediaColumns.DATA); + + int mediaType = 0; + if (!MediaScanner.isNoMediaPath(entry.mPath)) { + int fileType = MediaFile.getFileTypeForMimeType(mMimeType); + if (MediaFile.isAudioFileType(fileType)) { + mediaType = FileColumns.MEDIA_TYPE_AUDIO; + } else if (MediaFile.isVideoFileType(fileType)) { + mediaType = FileColumns.MEDIA_TYPE_VIDEO; + } else if (MediaFile.isImageFileType(fileType)) { + mediaType = FileColumns.MEDIA_TYPE_IMAGE; + } else if (MediaFile.isPlayListFileType(fileType)) { + mediaType = FileColumns.MEDIA_TYPE_PLAYLIST; + } + values.put(FileColumns.MEDIA_TYPE, mediaType); + } + mMediaProvider.update(result, values, null, null); } @@ -1160,15 +1176,14 @@ public class MediaScanner } if (fileMissing) { - // Clear the file path to prevent the _DELETE_FILE database hook - // in the media provider from deleting the file. + // Tell the provider to not delete the file. // If the file is truly gone the delete is unnecessary, and we want to avoid - // accidentally deleting files that are really there. - ContentValues values = new ContentValues(); - values.put(Files.FileColumns.DATA, ""); - values.put(Files.FileColumns.DATE_MODIFIED, 0); - mMediaProvider.update(ContentUris.withAppendedId(mFilesUri, entry.mRowId), - values, null, null); + // accidentally deleting files that are really there (this may happen if the + // filesystem is mounted and unmounted while the scanner is running). + Uri.Builder builder = mFilesUri.buildUpon(); + builder.appendEncodedPath(String.valueOf(entry.mRowId)); + builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false"); + Uri missingUri = builder.build(); // do not delete missing playlists, since they may have been modified by the user. // the user can delete them in the media player instead. @@ -1177,9 +1192,12 @@ public class MediaScanner int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType); if (!MediaFile.isPlayListFileType(fileType)) { - mMediaProvider.delete(ContentUris.withAppendedId(mFilesUri, entry.mRowId), - null, null); + mMediaProvider.delete(missingUri, null, null); iterator.remove(); + if (entry.mPath.toLowerCase(Locale.US).endsWith("/.nomedia")) { + File f = new File(path); + mMediaProvider.call(MediaStore.UNHIDE_CALL, f.getParent(), null); + } } } } diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index 268c9fcca1e7..18aa4b3b394b 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -752,6 +752,29 @@ public class MtpDatabase { return MtpConstants.RESPONSE_GENERAL_ERROR; } + // check if nomedia status changed + if (newFile.isDirectory()) { + // for directories, check if renamed from something hidden to something non-hidden + if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) { + // directory was unhidden + try { + mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null); + } catch (RemoteException e) { + Log.e(TAG, "failed to unhide/rescan for " + newPath); + } + } + } else { + // for files, check if renamed from .nomedia to something else + if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia") + && !newPath.toLowerCase(Locale.US).equals(".nomedia")) { + try { + mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null); + } catch (RemoteException e) { + Log.e(TAG, "failed to unhide/rescan for " + newPath); + } + } + } + return MtpConstants.RESPONSE_OK; } @@ -915,6 +938,15 @@ public class MtpDatabase { Uri uri = Files.getMtpObjectsUri(mVolumeName, handle); if (mMediaProvider.delete(uri, null, null) > 0) { + if (format != MtpConstants.FORMAT_ASSOCIATION + && path.toLowerCase(Locale.US).endsWith("/.nomedia")) { + try { + String parentPath = path.substring(0, path.lastIndexOf("/")); + mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null); + } catch (RemoteException e) { + Log.e(TAG, "failed to unhide/rescan for " + path); + } + } return MtpConstants.RESPONSE_OK; } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; diff --git a/media/jni/Android.mk b/media/jni/Android.mk index ee96a959eeae..23cc0e2f3228 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -28,7 +28,11 @@ LOCAL_SHARED_LIBRARIES := \ libcamera_client \ libmtp \ libusbhost \ - libexif + libexif \ + libstagefright_amrnb_common \ + +LOCAL_STATIC_LIBRARIES := \ + libstagefright_amrnbenc LOCAL_C_INCLUDES += \ external/jhead \ diff --git a/media/libeffects/preprocessing/Android.mk b/media/libeffects/preprocessing/Android.mk index 77d40b6ca591..7f7c7e1ee892 100755 --- a/media/libeffects/preprocessing/Android.mk +++ b/media/libeffects/preprocessing/Android.mk @@ -13,7 +13,7 @@ LOCAL_SRC_FILES:= \ LOCAL_C_INCLUDES += \ external/webrtc/src \ external/webrtc/src/modules/interface \ - external/webrtc/src/modules/audio_processing/main/interface \ + external/webrtc/src/modules/audio_processing/interface \ system/media/audio_effects/include LOCAL_C_INCLUDES += $(call include-path-for, speex) diff --git a/media/libeffects/preprocessing/PreProcessing.cpp b/media/libeffects/preprocessing/PreProcessing.cpp index e988e06b22d5..9fd6764552e2 100755 --- a/media/libeffects/preprocessing/PreProcessing.cpp +++ b/media/libeffects/preprocessing/PreProcessing.cpp @@ -24,8 +24,8 @@ #include <audio_effects/effect_aec.h> #include <audio_effects/effect_agc.h> #include <audio_effects/effect_ns.h> -#include "modules/interface/module_common_types.h" -#include "modules/audio_processing/main/interface/audio_processing.h" +#include <module_common_types.h> +#include <audio_processing.h> #include "speex/speex_resampler.h" @@ -220,8 +220,8 @@ bool HasReverseStream(uint32_t procId) // Automatic Gain Control (AGC) //------------------------------------------------------------------------------ -static const int kAgcDefaultTargetLevel = 0; -static const int kAgcDefaultCompGain = 90; +static const int kAgcDefaultTargetLevel = 3; +static const int kAgcDefaultCompGain = 9; static const bool kAgcDefaultLimiter = true; int AgcInit (preproc_effect_t *effect) diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp index 6639d06369f2..bfa093d3e5b8 100644 --- a/media/libmedia/AudioEffect.cpp +++ b/media/libmedia/AudioEffect.cpp @@ -342,7 +342,7 @@ void AudioEffect::binderDied() { ALOGW("IEffect died"); mStatus = NO_INIT; - if (mCbf) { + if (mCbf != NULL) { status_t status = DEAD_OBJECT; mCbf(EVENT_ERROR, mUserData, &status); } @@ -363,7 +363,7 @@ void AudioEffect::controlStatusChanged(bool controlGranted) mStatus = ALREADY_EXISTS; } } - if (mCbf) { + if (mCbf != NULL) { mCbf(EVENT_CONTROL_STATUS_CHANGED, mUserData, &controlGranted); } } @@ -373,7 +373,7 @@ void AudioEffect::enableStatusChanged(bool enabled) ALOGV("enableStatusChanged %p enabled %d mCbf %p", this, enabled, mCbf); if (mStatus == ALREADY_EXISTS) { mEnabled = enabled; - if (mCbf) { + if (mCbf != NULL) { mCbf(EVENT_ENABLE_STATUS_CHANGED, mUserData, &enabled); } } @@ -389,7 +389,7 @@ void AudioEffect::commandExecuted(uint32_t cmdCode, return; } - if (mCbf && cmdCode == EFFECT_CMD_SET_PARAM) { + if (mCbf != NULL && cmdCode == EFFECT_CMD_SET_PARAM) { effect_param_t *cmd = (effect_param_t *)cmdData; cmd->status = *(int32_t *)replyData; mCbf(EVENT_PARAMETER_CHANGED, mUserData, cmd); @@ -412,7 +412,8 @@ status_t AudioEffect::queryEffect(uint32_t index, effect_descriptor_t *descripto return af->queryEffect(index, descriptor); } -status_t AudioEffect::getEffectDescriptor(effect_uuid_t *uuid, effect_descriptor_t *descriptor) +status_t AudioEffect::getEffectDescriptor(effect_uuid_t *uuid, + effect_descriptor_t *descriptor) /*const*/ { const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp index 2b3ea386c55b..c96bc762b371 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -206,7 +206,7 @@ status_t AudioRecord::set( return status; } - if (cbf != 0) { + if (cbf != NULL) { mClientRecordThread = new ClientRecordThread(*this, threadCanCallJava); } @@ -387,7 +387,7 @@ uint32_t AudioRecord::getSampleRate() status_t AudioRecord::setMarkerPosition(uint32_t marker) { - if (mCbf == 0) return INVALID_OPERATION; + if (mCbf == NULL) return INVALID_OPERATION; mMarkerPosition = marker; mMarkerReached = false; @@ -397,7 +397,7 @@ status_t AudioRecord::setMarkerPosition(uint32_t marker) status_t AudioRecord::getMarkerPosition(uint32_t *marker) { - if (marker == 0) return BAD_VALUE; + if (marker == NULL) return BAD_VALUE; *marker = mMarkerPosition; @@ -406,7 +406,7 @@ status_t AudioRecord::getMarkerPosition(uint32_t *marker) status_t AudioRecord::setPositionUpdatePeriod(uint32_t updatePeriod) { - if (mCbf == 0) return INVALID_OPERATION; + if (mCbf == NULL) return INVALID_OPERATION; uint32_t curPosition; getPosition(&curPosition); @@ -418,7 +418,7 @@ status_t AudioRecord::setPositionUpdatePeriod(uint32_t updatePeriod) status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) { - if (updatePeriod == 0) return BAD_VALUE; + if (updatePeriod == NULL) return BAD_VALUE; *updatePeriod = mUpdatePeriod; @@ -427,7 +427,7 @@ status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) status_t AudioRecord::getPosition(uint32_t *position) { - if (position == 0) return BAD_VALUE; + if (position == NULL) return BAD_VALUE; AutoMutex lock(mLock); *position = mCblk->user; diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp index 124032b36949..110a2948d82a 100644 --- a/media/libmedia/AudioSystem.cpp +++ b/media/libmedia/AudioSystem.cpp @@ -35,7 +35,8 @@ sp<IAudioFlinger> AudioSystem::gAudioFlinger; sp<AudioSystem::AudioFlingerClient> AudioSystem::gAudioFlingerClient; audio_error_callback AudioSystem::gAudioErrorCallback = NULL; // Cached values -DefaultKeyedVector<int, audio_io_handle_t> AudioSystem::gStreamOutputMap(0); + +DefaultKeyedVector<audio_stream_type_t, audio_io_handle_t> AudioSystem::gStreamOutputMap(0); DefaultKeyedVector<audio_io_handle_t, AudioSystem::OutputDescriptor *> AudioSystem::gOutputs(0); // Cached values for recording queries, all protected by gLock @@ -224,7 +225,7 @@ status_t AudioSystem::getOutputSamplingRate(int* samplingRate, audio_stream_type gLock.lock(); outputDesc = AudioSystem::gOutputs.valueFor(output); - if (outputDesc == 0) { + if (outputDesc == NULL) { ALOGV("getOutputSamplingRate() no output descriptor for output %d in gOutputs", output); gLock.unlock(); const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger(); @@ -262,7 +263,7 @@ status_t AudioSystem::getOutputFrameCount(int* frameCount, audio_stream_type_t s gLock.lock(); outputDesc = AudioSystem::gOutputs.valueFor(output); - if (outputDesc == 0) { + if (outputDesc == NULL) { gLock.unlock(); const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; @@ -293,7 +294,7 @@ status_t AudioSystem::getOutputLatency(uint32_t* latency, audio_stream_type_t st gLock.lock(); outputDesc = AudioSystem::gOutputs.valueFor(output); - if (outputDesc == 0) { + if (outputDesc == NULL) { gLock.unlock(); const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; @@ -404,7 +405,7 @@ void AudioSystem::AudioFlingerClient::binderDied(const wp<IBinder>& who) { void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, int ioHandle, void *param2) { ALOGV("ioConfigChanged() event %d", event); OutputDescriptor *desc; - uint32_t stream; + audio_stream_type_t stream; if (ioHandle == 0) return; @@ -412,8 +413,8 @@ void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, int ioHandle, v switch (event) { case STREAM_CONFIG_CHANGED: - if (param2 == 0) break; - stream = *(uint32_t *)param2; + if (param2 == NULL) break; + stream = *(audio_stream_type_t *)param2; ALOGV("ioConfigChanged() STREAM_CONFIG_CHANGED stream %d, output %d", stream, ioHandle); if (gStreamOutputMap.indexOfKey(stream) >= 0) { gStreamOutputMap.replaceValueFor(stream, ioHandle); @@ -424,7 +425,7 @@ void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, int ioHandle, v ALOGV("ioConfigChanged() opening already existing output! %d", ioHandle); break; } - if (param2 == 0) break; + if (param2 == NULL) break; desc = (OutputDescriptor *)param2; OutputDescriptor *outputDesc = new OutputDescriptor(*desc); @@ -453,7 +454,7 @@ void AudioSystem::AudioFlingerClient::ioConfigChanged(int event, int ioHandle, v ALOGW("ioConfigChanged() modifying unknow output! %d", ioHandle); break; } - if (param2 == 0) break; + if (param2 == NULL) break; desc = (OutputDescriptor *)param2; ALOGV("ioConfigChanged() new config for output %d samplingRate %d, format %d channels %d frameCount %d latency %d", diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index 17e3d4b440aa..233cf7582050 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -257,7 +257,7 @@ status_t AudioTrack::set( return status; } - if (cbf != 0) { + if (cbf != NULL) { mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava); } @@ -501,12 +501,12 @@ status_t AudioTrack::setVolume(float left, float right) mVolume[LEFT] = left; mVolume[RIGHT] = right; - mCblk->volumeLR = (uint32_t(uint16_t(right * 0x1000)) << 16) | uint16_t(left * 0x1000); + mCblk->setVolumeLR((uint32_t(uint16_t(right * 0x1000)) << 16) | uint16_t(left * 0x1000)); return NO_ERROR; } -void AudioTrack::getVolume(float* left, float* right) +void AudioTrack::getVolume(float* left, float* right) const { if (left != NULL) { *left = mVolume[LEFT]; @@ -531,7 +531,7 @@ status_t AudioTrack::setAuxEffectSendLevel(float level) return NO_ERROR; } -void AudioTrack::getAuxEffectSendLevel(float* level) +void AudioTrack::getAuxEffectSendLevel(float* level) const { if (level != NULL) { *level = mSendLevel; @@ -553,7 +553,7 @@ status_t AudioTrack::setSampleRate(int rate) return NO_ERROR; } -uint32_t AudioTrack::getSampleRate() +uint32_t AudioTrack::getSampleRate() const { AutoMutex lock(mLock); return mCblk->sampleRate; @@ -601,16 +601,16 @@ status_t AudioTrack::setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCou return NO_ERROR; } -status_t AudioTrack::getLoop(uint32_t *loopStart, uint32_t *loopEnd, int *loopCount) +status_t AudioTrack::getLoop(uint32_t *loopStart, uint32_t *loopEnd, int *loopCount) const { AutoMutex lock(mLock); - if (loopStart != 0) { + if (loopStart != NULL) { *loopStart = mCblk->loopStart; } - if (loopEnd != 0) { + if (loopEnd != NULL) { *loopEnd = mCblk->loopEnd; } - if (loopCount != 0) { + if (loopCount != NULL) { if (mCblk->loopCount < 0) { *loopCount = -1; } else { @@ -623,7 +623,7 @@ status_t AudioTrack::getLoop(uint32_t *loopStart, uint32_t *loopEnd, int *loopCo status_t AudioTrack::setMarkerPosition(uint32_t marker) { - if (mCbf == 0) return INVALID_OPERATION; + if (mCbf == NULL) return INVALID_OPERATION; mMarkerPosition = marker; mMarkerReached = false; @@ -631,9 +631,9 @@ status_t AudioTrack::setMarkerPosition(uint32_t marker) return NO_ERROR; } -status_t AudioTrack::getMarkerPosition(uint32_t *marker) +status_t AudioTrack::getMarkerPosition(uint32_t *marker) const { - if (marker == 0) return BAD_VALUE; + if (marker == NULL) return BAD_VALUE; *marker = mMarkerPosition; @@ -642,7 +642,7 @@ status_t AudioTrack::getMarkerPosition(uint32_t *marker) status_t AudioTrack::setPositionUpdatePeriod(uint32_t updatePeriod) { - if (mCbf == 0) return INVALID_OPERATION; + if (mCbf == NULL) return INVALID_OPERATION; uint32_t curPosition; getPosition(&curPosition); @@ -652,9 +652,9 @@ status_t AudioTrack::setPositionUpdatePeriod(uint32_t updatePeriod) return NO_ERROR; } -status_t AudioTrack::getPositionUpdatePeriod(uint32_t *updatePeriod) +status_t AudioTrack::getPositionUpdatePeriod(uint32_t *updatePeriod) const { - if (updatePeriod == 0) return BAD_VALUE; + if (updatePeriod == NULL) return BAD_VALUE; *updatePeriod = mUpdatePeriod; @@ -679,7 +679,7 @@ status_t AudioTrack::setPosition(uint32_t position) status_t AudioTrack::getPosition(uint32_t *position) { - if (position == 0) return BAD_VALUE; + if (position == NULL) return BAD_VALUE; AutoMutex lock(mLock); *position = mFlushed ? 0 : mCblk->server; @@ -712,7 +712,7 @@ audio_io_handle_t AudioTrack::getOutput_l() mCblk->sampleRate, mFormat, mChannelMask, (audio_policy_output_flags_t)mFlags); } -int AudioTrack::getSessionId() +int AudioTrack::getSessionId() const { return mSessionId; } @@ -837,7 +837,7 @@ status_t AudioTrack::createTrack_l( mCblk->stepUser(mCblk->frameCount); } - mCblk->volumeLR = (uint32_t(uint16_t(mVolume[RIGHT] * 0x1000)) << 16) | uint16_t(mVolume[LEFT] * 0x1000); + mCblk->setVolumeLR((uint32_t(uint16_t(mVolume[RIGHT] * 0x1000)) << 16) | uint16_t(mVolume[LEFT] * 0x1000)); mCblk->setSendLevel(mSendLevel); mAudioTrack->attachAuxEffect(mAuxEffectId); mCblk->bufferTimeoutMs = MAX_STARTUP_TIMEOUT_MS; @@ -1319,8 +1319,8 @@ void AudioTrack::AudioTrackThread::onFirstRef() audio_track_cblk_t::audio_track_cblk_t() : lock(Mutex::SHARED), cv(Condition::SHARED), user(0), server(0), - userBase(0), serverBase(0), buffers(0), frameCount(0), - loopStart(UINT_MAX), loopEnd(UINT_MAX), loopCount(0), volumeLR(0), + userBase(0), serverBase(0), buffers(NULL), frameCount(0), + loopStart(UINT_MAX), loopEnd(UINT_MAX), loopCount(0), mVolumeLR(0x10001000), mSendLevel(0), flags(0) { } diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp index 0d442eff8570..29b61df1a684 100644 --- a/media/libmedia/IAudioFlinger.cpp +++ b/media/libmedia/IAudioFlinger.cpp @@ -325,7 +325,7 @@ public: return reply.readInt32(); } - virtual String8 getParameters(int ioHandle, const String8& keys) + virtual String8 getParameters(int ioHandle, const String8& keys) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); @@ -343,7 +343,7 @@ public: remote()->transact(REGISTER_CLIENT, data, &reply); } - virtual size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount) + virtual size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); @@ -432,7 +432,7 @@ public: uint32_t *pSamplingRate, audio_format_t *pFormat, uint32_t *pChannels, - uint32_t acoustics) + audio_in_acoustics_t acoustics) { Parcel data, reply; uint32_t devices = pDevices ? *pDevices : 0; @@ -445,7 +445,7 @@ public: data.writeInt32(samplingRate); data.writeInt32(format); data.writeInt32(channels); - data.writeInt32(acoustics); + data.writeInt32((int32_t) acoustics); remote()->transact(OPEN_INPUT, data, &reply); int input = reply.readInt32(); devices = reply.readInt32(); @@ -487,7 +487,7 @@ public: return reply.readInt32(); } - virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) + virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); @@ -507,7 +507,7 @@ public: return status; } - virtual unsigned int getInputFramesLost(int ioHandle) + virtual unsigned int getInputFramesLost(int ioHandle) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); @@ -544,7 +544,7 @@ public: remote()->transact(RELEASE_AUDIO_SESSION_ID, data, &reply); } - virtual status_t queryNumberEffects(uint32_t *numEffects) + virtual status_t queryNumberEffects(uint32_t *numEffects) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); @@ -562,7 +562,7 @@ public: return NO_ERROR; } - virtual status_t queryEffect(uint32_t index, effect_descriptor_t *pDescriptor) + virtual status_t queryEffect(uint32_t index, effect_descriptor_t *pDescriptor) const { if (pDescriptor == NULL) { return BAD_VALUE; @@ -582,7 +582,8 @@ public: return NO_ERROR; } - virtual status_t getEffectDescriptor(effect_uuid_t *pUuid, effect_descriptor_t *pDescriptor) + virtual status_t getEffectDescriptor(effect_uuid_t *pUuid, + effect_descriptor_t *pDescriptor) const { if (pUuid == NULL || pDescriptor == NULL) { return BAD_VALUE; @@ -640,7 +641,7 @@ public: *id = tmp; } tmp = reply.readInt32(); - if (enabled) { + if (enabled != NULL) { *enabled = tmp; } effect = interface_cast<IEffect>(reply.readStrongBinder()); @@ -881,13 +882,13 @@ status_t BnAudioFlinger::onTransact( uint32_t samplingRate = data.readInt32(); audio_format_t format = (audio_format_t) data.readInt32(); uint32_t channels = data.readInt32(); - uint32_t acoutics = data.readInt32(); + audio_in_acoustics_t acoustics = (audio_in_acoustics_t) data.readInt32(); int input = openInput(&devices, &samplingRate, &format, &channels, - acoutics); + acoustics); reply->writeInt32(input); reply->writeInt32(devices); reply->writeInt32(samplingRate); diff --git a/media/libmedia/IAudioFlingerClient.cpp b/media/libmedia/IAudioFlingerClient.cpp index 5a3f250b9096..9458bc0b86e3 100644 --- a/media/libmedia/IAudioFlingerClient.cpp +++ b/media/libmedia/IAudioFlingerClient.cpp @@ -73,7 +73,7 @@ status_t BnAudioFlingerClient::onTransact( CHECK_INTERFACE(IAudioFlingerClient, data, reply); int event = data.readInt32(); int ioHandle = data.readInt32(); - void *param2 = 0; + void *param2 = NULL; AudioSystem::OutputDescriptor desc; uint32_t stream; if (event == AudioSystem::STREAM_CONFIG_CHANGED) { diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp index d2f5f71b572d..27c7e03b0cd1 100644 --- a/media/libmedia/IOMX.cpp +++ b/media/libmedia/IOMX.cpp @@ -59,9 +59,10 @@ public: : BpInterface<IOMX>(impl) { } - virtual bool livesLocally(pid_t pid) { + virtual bool livesLocally(node_id node, pid_t pid) { Parcel data, reply; data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + data.writeIntPtr((intptr_t)node); data.writeInt32(pid); remote()->transact(LIVES_LOCALLY, data, &reply); @@ -417,7 +418,9 @@ status_t BnOMX::onTransact( case LIVES_LOCALLY: { CHECK_INTERFACE(IOMX, data, reply); - reply->writeInt32(livesLocally((pid_t)data.readInt32())); + node_id node = (void *)data.readIntPtr(); + pid_t pid = (pid_t)data.readInt32(); + reply->writeInt32(livesLocally(node, pid)); return OK; } diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp index 5ceb912503ad..e6e989dbbe80 100644 --- a/media/libmedia/ToneGenerator.cpp +++ b/media/libmedia/ToneGenerator.cpp @@ -751,7 +751,7 @@ const ToneGenerator::ToneDescriptor ToneGenerator::sToneDescriptors[] = { // Used by ToneGenerator::getToneForRegion() to convert user specified supervisory tone type // to actual tone for current region. -const unsigned char ToneGenerator::sToneMappingTable[NUM_REGIONS-1][NUM_SUP_TONES] = { +const unsigned char /*tone_type*/ ToneGenerator::sToneMappingTable[NUM_REGIONS-1][NUM_SUP_TONES] = { { // ANSI TONE_ANSI_DIAL, // TONE_SUP_DIAL TONE_ANSI_BUSY, // TONE_SUP_BUSY @@ -811,9 +811,9 @@ ToneGenerator::ToneGenerator(audio_stream_type_t streamType, float volume, bool mThreadCanCallJava = threadCanCallJava; mStreamType = streamType; mVolume = volume; - mpAudioTrack = 0; - mpToneDesc = 0; - mpNewToneDesc = 0; + mpAudioTrack = NULL; + mpToneDesc = NULL; + mpNewToneDesc = NULL; // Generate tone by chunks of 20 ms to keep cadencing precision mProcessSize = (mSamplingRate * 20) / 1000; @@ -855,7 +855,7 @@ ToneGenerator::ToneGenerator(audio_stream_type_t streamType, float volume, bool ToneGenerator::~ToneGenerator() { ALOGV("ToneGenerator destructor\n"); - if (mpAudioTrack) { + if (mpAudioTrack != NULL) { stopTone(); ALOGV("Delete Track: %p\n", mpAudioTrack); delete mpAudioTrack; @@ -878,7 +878,7 @@ ToneGenerator::~ToneGenerator() { // none // //////////////////////////////////////////////////////////////////////////////// -bool ToneGenerator::startTone(int toneType, int durationMs) { +bool ToneGenerator::startTone(tone_type toneType, int durationMs) { bool lResult = false; status_t lStatus; @@ -1012,7 +1012,7 @@ bool ToneGenerator::initAudioTrack() { if (mpAudioTrack) { delete mpAudioTrack; - mpAudioTrack = 0; + mpAudioTrack = NULL; } // Open audio track in mono, PCM 16bit, default sampling rate, default buffer size @@ -1048,7 +1048,7 @@ initAudioTrack_exit: if (mpAudioTrack) { ALOGV("Delete Track I: %p\n", mpAudioTrack); delete mpAudioTrack; - mpAudioTrack = 0; + mpAudioTrack = NULL; } return false; @@ -1317,7 +1317,7 @@ audioCallback_EndLoop: bool ToneGenerator::prepareWave() { unsigned int segmentIdx = 0; - if (!mpNewToneDesc) { + if (mpNewToneDesc == NULL) { return false; } @@ -1434,13 +1434,13 @@ void ToneGenerator::clearWaveGens() { // none // //////////////////////////////////////////////////////////////////////////////// -int ToneGenerator::getToneForRegion(int toneType) { - int regionTone; +ToneGenerator::tone_type ToneGenerator::getToneForRegion(tone_type toneType) { + tone_type regionTone; if (mRegion == CEPT || toneType < FIRST_SUP_TONE || toneType > LAST_SUP_TONE) { regionTone = toneType; } else { - regionTone = sToneMappingTable[mRegion][toneType - FIRST_SUP_TONE]; + regionTone = (tone_type) sToneMappingTable[mRegion][toneType - FIRST_SUP_TONE]; } ALOGV("getToneForRegion, tone %d, region %d, regionTone %d", toneType, mRegion, regionTone); diff --git a/media/libstagefright/AACWriter.cpp b/media/libstagefright/AACWriter.cpp index 1673ccdb3983..9cdb46360c5a 100644 --- a/media/libstagefright/AACWriter.cpp +++ b/media/libstagefright/AACWriter.cpp @@ -60,7 +60,7 @@ AACWriter::AACWriter(int fd) AACWriter::~AACWriter() { if (mStarted) { - stop(); + reset(); } if (mFd != -1) { @@ -152,7 +152,7 @@ status_t AACWriter::pause() { return OK; } -status_t AACWriter::stop() { +status_t AACWriter::reset() { if (!mStarted) { return OK; } diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp index 6c4e307eb0aa..59b4ca7b4a52 100644 --- a/media/libstagefright/AMRWriter.cpp +++ b/media/libstagefright/AMRWriter.cpp @@ -52,7 +52,7 @@ AMRWriter::AMRWriter(int fd) AMRWriter::~AMRWriter() { if (mStarted) { - stop(); + reset(); } if (mFd != -1) { @@ -152,7 +152,7 @@ status_t AMRWriter::pause() { return OK; } -status_t AMRWriter::stop() { +status_t AMRWriter::reset() { if (!mStarted) { return OK; } diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 4d61067508c8..483e5ab0c160 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -74,11 +74,11 @@ LOCAL_SHARED_LIBRARIES := \ libcrypto \ libssl \ libgui \ + libstagefright_omx \ LOCAL_STATIC_LIBRARIES := \ libstagefright_color_conversion \ - libstagefright_amrnbenc \ - libstagefright_amrwbenc \ + libstagefright_aacenc \ libstagefright_avcenc \ libstagefright_m4vh263enc \ libstagefright_matroska \ @@ -140,7 +140,6 @@ endif # ifeq ($(HTTP_STACK),chrome) ################################################################################ LOCAL_SHARED_LIBRARIES += \ - libstagefright_amrnb_common \ libstagefright_enc_common \ libstagefright_avc_common \ libstagefright_foundation \ diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp index 8bdb7c589d23..fef2a006baa0 100644 --- a/media/libstagefright/AudioSource.cpp +++ b/media/libstagefright/AudioSource.cpp @@ -72,7 +72,7 @@ AudioSource::AudioSource( AudioSource::~AudioSource() { if (mStarted) { - stop(); + reset(); } delete mRecord; @@ -130,7 +130,7 @@ void AudioSource::waitOutstandingEncodingFrames_l() { } } -status_t AudioSource::stop() { +status_t AudioSource::reset() { Mutex::Autolock autoLock(mLock); if (!mStarted) { return UNKNOWN_ERROR; diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 8480b6d7466f..8073af88ce76 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -30,7 +30,7 @@ #include "include/MPEG2TSExtractor.h" #include "include/WVMExtractor.h" -#include "timedtext/TimedTextPlayer.h" +#include "timedtext/TimedTextDriver.h" #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> @@ -192,7 +192,7 @@ AwesomePlayer::AwesomePlayer() mVideoBuffer(NULL), mDecryptHandle(NULL), mLastVideoTimeUs(-1), - mTextPlayer(NULL) { + mTextDriver(NULL) { CHECK_EQ(mClient.connect(), (status_t)OK); DataSource::RegisterDefaultSniffers(); @@ -530,9 +530,9 @@ void AwesomePlayer::reset_l() { delete mAudioPlayer; mAudioPlayer = NULL; - if (mTextPlayer != NULL) { - delete mTextPlayer; - mTextPlayer = NULL; + if (mTextDriver != NULL) { + delete mTextDriver; + mTextDriver = NULL; } mVideoRenderer.clear(); @@ -1118,7 +1118,7 @@ status_t AwesomePlayer::pause_l(bool at_eos) { } if (mFlags & TEXTPLAYER_STARTED) { - mTextPlayer->pause(); + mTextDriver->pause(); modifyFlags(TEXT_RUNNING, CLEAR); } @@ -1272,9 +1272,9 @@ status_t AwesomePlayer::seekTo(int64_t timeUs) { } status_t AwesomePlayer::setTimedTextTrackIndex(int32_t index) { - if (mTextPlayer != NULL) { + if (mTextDriver != NULL) { if (index >= 0) { // to turn on a text track - status_t err = mTextPlayer->setTimedTextTrackIndex(index); + status_t err = mTextDriver->setTimedTextTrackIndex(index); if (err != OK) { return err; } @@ -1290,7 +1290,7 @@ status_t AwesomePlayer::setTimedTextTrackIndex(int32_t index) { modifyFlags(TEXTPLAYER_STARTED, CLEAR); } - return mTextPlayer->setTimedTextTrackIndex(index); + return mTextDriver->setTimedTextTrackIndex(index); } } else { return INVALID_OPERATION; @@ -1319,7 +1319,7 @@ status_t AwesomePlayer::seekTo_l(int64_t timeUs) { seekAudioIfNecessary_l(); if (mFlags & TEXTPLAYER_STARTED) { - mTextPlayer->seekTo(mSeekTimeUs); + mTextDriver->seekToAsync(mSeekTimeUs); } if (!(mFlags & PLAYING)) { @@ -1364,11 +1364,11 @@ void AwesomePlayer::addTextSource(sp<MediaSource> source) { Mutex::Autolock autoLock(mTimedTextLock); CHECK(source != NULL); - if (mTextPlayer == NULL) { - mTextPlayer = new TimedTextPlayer(this, mListener, &mQueue); + if (mTextDriver == NULL) { + mTextDriver = new TimedTextDriver(mListener); } - mTextPlayer->addTextSource(source); + mTextDriver->addInBandTextSource(source); } status_t AwesomePlayer::initAudioDecoder() { @@ -1695,7 +1695,7 @@ void AwesomePlayer::onVideoEvent() { } if ((mFlags & TEXTPLAYER_STARTED) && !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) { - mTextPlayer->resume(); + mTextDriver->resume(); modifyFlags(TEXT_RUNNING, SET); } @@ -2241,11 +2241,11 @@ status_t AwesomePlayer::setParameter(int key, const Parcel &request) { case KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE: { Mutex::Autolock autoLock(mTimedTextLock); - if (mTextPlayer == NULL) { - mTextPlayer = new TimedTextPlayer(this, mListener, &mQueue); + if (mTextDriver == NULL) { + mTextDriver = new TimedTextDriver(mListener); } - return mTextPlayer->setParameter(key, request); + return mTextDriver->addOutOfBandTextSource(request); } case KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS: { diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp index 1850c9caf260..228659c7c1ee 100755 --- a/media/libstagefright/CameraSource.cpp +++ b/media/libstagefright/CameraSource.cpp @@ -548,7 +548,7 @@ status_t CameraSource::initWithCameraAccess( CameraSource::~CameraSource() { if (mStarted) { - stop(); + reset(); } else if (mInitCheck == OK) { // Camera is initialized but because start() is never called, // the lock on Camera is never released(). This makes sure @@ -632,8 +632,8 @@ void CameraSource::releaseCamera() { mCameraFlags = 0; } -status_t CameraSource::stop() { - ALOGD("stop: E"); +status_t CameraSource::reset() { + ALOGD("reset: E"); Mutex::Autolock autoLock(mLock); mStarted = false; mFrameAvailableCondition.signal(); @@ -670,7 +670,7 @@ status_t CameraSource::stop() { } CHECK_EQ(mNumFramesReceived, mNumFramesEncoded + mNumFramesDropped); - ALOGD("stop: X"); + ALOGD("reset: X"); return OK; } diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp index 263ab5059896..83d67b93748b 100644 --- a/media/libstagefright/CameraSourceTimeLapse.cpp +++ b/media/libstagefright/CameraSourceTimeLapse.cpp @@ -87,6 +87,10 @@ CameraSourceTimeLapse::CameraSourceTimeLapse( } CameraSourceTimeLapse::~CameraSourceTimeLapse() { + if (mLastReadBufferCopy) { + mLastReadBufferCopy->release(); + mLastReadBufferCopy = NULL; + } } void CameraSourceTimeLapse::startQuickReadReturns() { @@ -204,15 +208,6 @@ status_t CameraSourceTimeLapse::read( } } -void CameraSourceTimeLapse::stopCameraRecording() { - ALOGV("stopCameraRecording"); - CameraSource::stopCameraRecording(); - if (mLastReadBufferCopy) { - mLastReadBufferCopy->release(); - mLastReadBufferCopy = NULL; - } -} - sp<IMemory> CameraSourceTimeLapse::createIMemoryCopy( const sp<IMemory> &source_data) { diff --git a/media/libstagefright/MPEG2TSWriter.cpp b/media/libstagefright/MPEG2TSWriter.cpp index 36009abf990c..0b4ecbea4e6c 100644 --- a/media/libstagefright/MPEG2TSWriter.cpp +++ b/media/libstagefright/MPEG2TSWriter.cpp @@ -513,7 +513,7 @@ void MPEG2TSWriter::init() { MPEG2TSWriter::~MPEG2TSWriter() { if (mStarted) { - stop(); + reset(); } mLooper->unregisterHandler(mReflector->id()); @@ -564,7 +564,7 @@ status_t MPEG2TSWriter::start(MetaData *param) { return OK; } -status_t MPEG2TSWriter::stop() { +status_t MPEG2TSWriter::reset() { CHECK(mStarted); for (size_t i = 0; i < mSources.size(); ++i) { diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index bc8801514346..6c95d4e5d351 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -20,7 +20,6 @@ #include "include/MPEG4Extractor.h" #include "include/SampleTable.h" #include "include/ESDS.h" -#include "timedtext/TimedTextPlayer.h" #include <arpa/inet.h> @@ -2430,4 +2429,3 @@ bool SniffMPEG4( } } // namespace android - diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index 06dd875289b7..068660bb971a 100755 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -282,7 +282,7 @@ MPEG4Writer::MPEG4Writer(int fd) } MPEG4Writer::~MPEG4Writer() { - stop(); + reset(); while (!mTracks.empty()) { List<Track *>::iterator it = mTracks.begin(); @@ -616,7 +616,7 @@ void MPEG4Writer::release() { mStarted = false; } -status_t MPEG4Writer::stop() { +status_t MPEG4Writer::reset() { if (mInitCheck != OK) { return OK; } else { diff --git a/media/libstagefright/OMXClient.cpp b/media/libstagefright/OMXClient.cpp index 9de873eceed5..7a805aa73afc 100644 --- a/media/libstagefright/OMXClient.cpp +++ b/media/libstagefright/OMXClient.cpp @@ -20,11 +20,299 @@ #include <binder/IServiceManager.h> #include <media/IMediaPlayerService.h> -#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/OMXClient.h> +#include <utils/KeyedVector.h> + +#include "include/OMX.h" namespace android { +struct MuxOMX : public IOMX { + MuxOMX(const sp<IOMX> &remoteOMX); + virtual ~MuxOMX(); + + virtual IBinder *onAsBinder() { return NULL; } + + virtual bool livesLocally(node_id node, pid_t pid); + + virtual status_t listNodes(List<ComponentInfo> *list); + + virtual status_t allocateNode( + const char *name, const sp<IOMXObserver> &observer, + node_id *node); + + virtual status_t freeNode(node_id node); + + virtual status_t sendCommand( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param); + + virtual status_t getParameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size); + + virtual status_t setParameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size); + + virtual status_t getConfig( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size); + + virtual status_t setConfig( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size); + + virtual status_t getState( + node_id node, OMX_STATETYPE* state); + + virtual status_t storeMetaDataInBuffers( + node_id node, OMX_U32 port_index, OMX_BOOL enable); + + virtual status_t enableGraphicBuffers( + node_id node, OMX_U32 port_index, OMX_BOOL enable); + + virtual status_t getGraphicBufferUsage( + node_id node, OMX_U32 port_index, OMX_U32* usage); + + virtual status_t useBuffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer); + + virtual status_t useGraphicBuffer( + node_id node, OMX_U32 port_index, + const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer); + + virtual status_t allocateBuffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer, void **buffer_data); + + virtual status_t allocateBufferWithBackup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer); + + virtual status_t freeBuffer( + node_id node, OMX_U32 port_index, buffer_id buffer); + + virtual status_t fillBuffer(node_id node, buffer_id buffer); + + virtual status_t emptyBuffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp); + + virtual status_t getExtensionIndex( + node_id node, + const char *parameter_name, + OMX_INDEXTYPE *index); + +private: + mutable Mutex mLock; + + sp<IOMX> mRemoteOMX; + sp<IOMX> mLocalOMX; + + KeyedVector<node_id, bool> mIsLocalNode; + + bool isLocalNode(node_id node) const; + bool isLocalNode_l(node_id node) const; + const sp<IOMX> &getOMX(node_id node) const; + const sp<IOMX> &getOMX_l(node_id node) const; + + static bool IsSoftwareComponent(const char *name); + + DISALLOW_EVIL_CONSTRUCTORS(MuxOMX); +}; + +MuxOMX::MuxOMX(const sp<IOMX> &remoteOMX) + : mRemoteOMX(remoteOMX) { +} + +MuxOMX::~MuxOMX() { +} + +bool MuxOMX::isLocalNode(node_id node) const { + Mutex::Autolock autoLock(mLock); + + return isLocalNode_l(node); +} + +bool MuxOMX::isLocalNode_l(node_id node) const { + return mIsLocalNode.indexOfKey(node) >= 0; +} + +// static +bool MuxOMX::IsSoftwareComponent(const char *name) { + return !strncasecmp(name, "OMX.google.", 11); +} + +const sp<IOMX> &MuxOMX::getOMX(node_id node) const { + return isLocalNode(node) ? mLocalOMX : mRemoteOMX; +} + +const sp<IOMX> &MuxOMX::getOMX_l(node_id node) const { + return isLocalNode_l(node) ? mLocalOMX : mRemoteOMX; +} + +bool MuxOMX::livesLocally(node_id node, pid_t pid) { + return getOMX(node)->livesLocally(node, pid); +} + +status_t MuxOMX::listNodes(List<ComponentInfo> *list) { + Mutex::Autolock autoLock(mLock); + + if (mLocalOMX == NULL) { + mLocalOMX = new OMX; + } + + return mLocalOMX->listNodes(list); +} + +status_t MuxOMX::allocateNode( + const char *name, const sp<IOMXObserver> &observer, + node_id *node) { + Mutex::Autolock autoLock(mLock); + + sp<IOMX> omx; + + if (IsSoftwareComponent(name)) { + if (mLocalOMX == NULL) { + mLocalOMX = new OMX; + } + omx = mLocalOMX; + } else { + omx = mRemoteOMX; + } + + status_t err = omx->allocateNode(name, observer, node); + + if (err != OK) { + return err; + } + + if (omx == mLocalOMX) { + mIsLocalNode.add(*node, true); + } + + return OK; +} + +status_t MuxOMX::freeNode(node_id node) { + Mutex::Autolock autoLock(mLock); + + status_t err = getOMX_l(node)->freeNode(node); + + if (err != OK) { + return err; + } + + mIsLocalNode.removeItem(node); + + return OK; +} + +status_t MuxOMX::sendCommand( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { + return getOMX(node)->sendCommand(node, cmd, param); +} + +status_t MuxOMX::getParameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size) { + return getOMX(node)->getParameter(node, index, params, size); +} + +status_t MuxOMX::setParameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size) { + return getOMX(node)->setParameter(node, index, params, size); +} + +status_t MuxOMX::getConfig( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size) { + return getOMX(node)->getConfig(node, index, params, size); +} + +status_t MuxOMX::setConfig( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size) { + return getOMX(node)->setConfig(node, index, params, size); +} + +status_t MuxOMX::getState( + node_id node, OMX_STATETYPE* state) { + return getOMX(node)->getState(node, state); +} + +status_t MuxOMX::storeMetaDataInBuffers( + node_id node, OMX_U32 port_index, OMX_BOOL enable) { + return getOMX(node)->storeMetaDataInBuffers(node, port_index, enable); +} + +status_t MuxOMX::enableGraphicBuffers( + node_id node, OMX_U32 port_index, OMX_BOOL enable) { + return getOMX(node)->enableGraphicBuffers(node, port_index, enable); +} + +status_t MuxOMX::getGraphicBufferUsage( + node_id node, OMX_U32 port_index, OMX_U32* usage) { + return getOMX(node)->getGraphicBufferUsage(node, port_index, usage); +} + +status_t MuxOMX::useBuffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + return getOMX(node)->useBuffer(node, port_index, params, buffer); +} + +status_t MuxOMX::useGraphicBuffer( + node_id node, OMX_U32 port_index, + const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer) { + return getOMX(node)->useGraphicBuffer( + node, port_index, graphicBuffer, buffer); +} + +status_t MuxOMX::allocateBuffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer, void **buffer_data) { + return getOMX(node)->allocateBuffer( + node, port_index, size, buffer, buffer_data); +} + +status_t MuxOMX::allocateBufferWithBackup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + return getOMX(node)->allocateBufferWithBackup( + node, port_index, params, buffer); +} + +status_t MuxOMX::freeBuffer( + node_id node, OMX_U32 port_index, buffer_id buffer) { + return getOMX(node)->freeBuffer(node, port_index, buffer); +} + +status_t MuxOMX::fillBuffer(node_id node, buffer_id buffer) { + return getOMX(node)->fillBuffer(node, buffer); +} + +status_t MuxOMX::emptyBuffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) { + return getOMX(node)->emptyBuffer( + node, buffer, range_offset, range_length, flags, timestamp); +} + +status_t MuxOMX::getExtensionIndex( + node_id node, + const char *parameter_name, + OMX_INDEXTYPE *index) { + return getOMX(node)->getExtensionIndex(node, parameter_name, index); +} + OMXClient::OMXClient() { } @@ -38,6 +326,11 @@ status_t OMXClient::connect() { mOMX = service->getOMX(); CHECK(mOMX.get() != NULL); + if (!mOMX->livesLocally(NULL /* node */, getpid())) { + ALOGI("Using client-side OMX mux."); + mOMX = new MuxOMX(mOMX); + } + return OK; } diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 7597f642ac50..381320b8fd6e 100755 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -18,8 +18,7 @@ #define LOG_TAG "OMXCodec" #include <utils/Log.h> -#include "include/AMRNBEncoder.h" -#include "include/AMRWBEncoder.h" +#include "include/AACEncoder.h" #include "include/AVCEncoder.h" #include "include/M4vH263Encoder.h" @@ -70,8 +69,7 @@ static sp<MediaSource> Make##name(const sp<MediaSource> &source, const sp<MetaDa #define FACTORY_REF(name) { #name, Make##name }, -FACTORY_CREATE_ENCODER(AMRNBEncoder) -FACTORY_CREATE_ENCODER(AMRWBEncoder) +FACTORY_CREATE_ENCODER(AACEncoder) FACTORY_CREATE_ENCODER(AVCEncoder) FACTORY_CREATE_ENCODER(M4vH263Encoder) @@ -84,8 +82,7 @@ static sp<MediaSource> InstantiateSoftwareEncoder( }; static const FactoryInfo kFactoryInfo[] = { - FACTORY_REF(AMRNBEncoder) - FACTORY_REF(AMRWBEncoder) + FACTORY_REF(AACEncoder) FACTORY_REF(AVCEncoder) FACTORY_REF(M4vH263Encoder) }; @@ -146,11 +143,12 @@ static const CodecInfo kDecoderInfo[] = { static const CodecInfo kEncoderInfo[] = { { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.encode" }, - { MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBEncoder" }, + { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.google.amrnb.encoder" }, { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.encode" }, - { MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBEncoder" }, + { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.google.amrwb.encoder" }, { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.encode" }, { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.google.aac.encoder" }, + { MEDIA_MIMETYPE_AUDIO_AAC, "AACEncoder" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.DUCATI1.VIDEO.MPEG4E" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.encoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.encoder.mpeg4" }, @@ -1479,7 +1477,7 @@ OMXCodec::OMXCodec( const sp<MediaSource> &source, const sp<ANativeWindow> &nativeWindow) : mOMX(omx), - mOMXLivesLocally(omx->livesLocally(getpid())), + mOMXLivesLocally(omx->livesLocally(node, getpid())), mNode(node), mQuirks(quirks), mFlags(flags), diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp index 2233d1bf5d04..d0683814a724 100644 --- a/media/libstagefright/SurfaceMediaSource.cpp +++ b/media/libstagefright/SurfaceMediaSource.cpp @@ -60,7 +60,7 @@ SurfaceMediaSource::SurfaceMediaSource(uint32_t bufW, uint32_t bufH) : SurfaceMediaSource::~SurfaceMediaSource() { ALOGV("SurfaceMediaSource::~SurfaceMediaSource"); if (!mStopped) { - stop(); + reset(); } } @@ -716,9 +716,9 @@ status_t SurfaceMediaSource::start(MetaData *params) } -status_t SurfaceMediaSource::stop() +status_t SurfaceMediaSource::reset() { - ALOGV("Stop"); + ALOGV("Reset"); Mutex::Autolock lock(mMutex); // TODO: Add waiting on mFrameCompletedCondition here? diff --git a/media/libstagefright/codecs/aacdec/SoftAAC.cpp b/media/libstagefright/codecs/aacdec/SoftAAC.cpp index da9d28067793..ea6c3608bbf6 100644 --- a/media/libstagefright/codecs/aacdec/SoftAAC.cpp +++ b/media/libstagefright/codecs/aacdec/SoftAAC.cpp @@ -218,6 +218,18 @@ OMX_ERRORTYPE SoftAAC::internalSetParameter( return OMX_ErrorNone; } + case OMX_IndexParamAudioPcm: + { + const OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = + (OMX_AUDIO_PARAM_PCMMODETYPE *)params; + + if (pcmParams->nPortIndex != 1) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + default: return SimpleSoftOMXComponent::internalSetParameter(index, params); } diff --git a/media/libstagefright/codecs/amrnb/enc/Android.mk b/media/libstagefright/codecs/amrnb/enc/Android.mk index b6aed81f5263..94e87269b6d9 100644 --- a/media/libstagefright/codecs/amrnb/enc/Android.mk +++ b/media/libstagefright/codecs/amrnb/enc/Android.mk @@ -74,3 +74,30 @@ LOCAL_CFLAGS := \ LOCAL_MODULE := libstagefright_amrnbenc include $(BUILD_STATIC_LIBRARY) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + SoftAMRNBEncoder.cpp + +LOCAL_C_INCLUDES := \ + frameworks/base/media/libstagefright/include \ + frameworks/base/include/media/stagefright/openmax \ + $(LOCAL_PATH)/src \ + $(LOCAL_PATH)/include \ + $(LOCAL_PATH)/../common/include \ + $(LOCAL_PATH)/../common + +LOCAL_STATIC_LIBRARIES := \ + libstagefright_amrnbenc + +LOCAL_SHARED_LIBRARIES := \ + libstagefright_omx libstagefright_foundation libutils \ + libstagefright_amrnb_common + +LOCAL_MODULE := libstagefright_soft_amrnbenc +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp new file mode 100644 index 000000000000..07f8b4f8b93d --- /dev/null +++ b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "SoftAMRNBEncoder" +#include <utils/Log.h> + +#include "SoftAMRNBEncoder.h" + +#include "gsmamr_enc.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/hexdump.h> + +namespace android { + +static const int32_t kSampleRate = 8000; + +template<class T> +static void InitOMXParams(T *params) { + params->nSize = sizeof(T); + params->nVersion.s.nVersionMajor = 1; + params->nVersion.s.nVersionMinor = 0; + params->nVersion.s.nRevision = 0; + params->nVersion.s.nStep = 0; +} + +SoftAMRNBEncoder::SoftAMRNBEncoder( + const char *name, + const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, + OMX_COMPONENTTYPE **component) + : SimpleSoftOMXComponent(name, callbacks, appData, component), + mEncState(NULL), + mSidState(NULL), + mBitRate(0), + mMode(MR475), + mInputSize(0), + mInputTimeUs(-1ll), + mSawInputEOS(false), + mSignalledError(false) { + initPorts(); + CHECK_EQ(initEncoder(), (status_t)OK); +} + +SoftAMRNBEncoder::~SoftAMRNBEncoder() { + if (mEncState != NULL) { + AMREncodeExit(&mEncState, &mSidState); + mEncState = mSidState = NULL; + } +} + +void SoftAMRNBEncoder::initPorts() { + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + + def.nPortIndex = 0; + def.eDir = OMX_DirInput; + def.nBufferCountMin = kNumBuffers; + def.nBufferCountActual = def.nBufferCountMin; + def.nBufferSize = kNumSamplesPerFrame * sizeof(int16_t); + def.bEnabled = OMX_TRUE; + def.bPopulated = OMX_FALSE; + def.eDomain = OMX_PortDomainAudio; + def.bBuffersContiguous = OMX_FALSE; + def.nBufferAlignment = 1; + + def.format.audio.cMIMEType = const_cast<char *>("audio/raw"); + def.format.audio.pNativeRender = NULL; + def.format.audio.bFlagErrorConcealment = OMX_FALSE; + def.format.audio.eEncoding = OMX_AUDIO_CodingPCM; + + addPort(def); + + def.nPortIndex = 1; + def.eDir = OMX_DirOutput; + def.nBufferCountMin = kNumBuffers; + def.nBufferCountActual = def.nBufferCountMin; + def.nBufferSize = 8192; + def.bEnabled = OMX_TRUE; + def.bPopulated = OMX_FALSE; + def.eDomain = OMX_PortDomainAudio; + def.bBuffersContiguous = OMX_FALSE; + def.nBufferAlignment = 2; + + def.format.audio.cMIMEType = const_cast<char *>("audio/3gpp"); + def.format.audio.pNativeRender = NULL; + def.format.audio.bFlagErrorConcealment = OMX_FALSE; + def.format.audio.eEncoding = OMX_AUDIO_CodingAMR; + + addPort(def); +} + +status_t SoftAMRNBEncoder::initEncoder() { + if (AMREncodeInit(&mEncState, &mSidState, false /* dtx_enable */) != 0) { + return UNKNOWN_ERROR; + } + + return OK; +} + +OMX_ERRORTYPE SoftAMRNBEncoder::internalGetParameter( + OMX_INDEXTYPE index, OMX_PTR params) { + switch (index) { + case OMX_IndexParamAudioPortFormat: + { + OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams = + (OMX_AUDIO_PARAM_PORTFORMATTYPE *)params; + + if (formatParams->nPortIndex > 1) { + return OMX_ErrorUndefined; + } + + if (formatParams->nIndex > 0) { + return OMX_ErrorNoMore; + } + + formatParams->eEncoding = + (formatParams->nPortIndex == 0) + ? OMX_AUDIO_CodingPCM : OMX_AUDIO_CodingAMR; + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioAmr: + { + OMX_AUDIO_PARAM_AMRTYPE *amrParams = + (OMX_AUDIO_PARAM_AMRTYPE *)params; + + if (amrParams->nPortIndex != 1) { + return OMX_ErrorUndefined; + } + + amrParams->nChannels = 1; + amrParams->nBitRate = mBitRate; + amrParams->eAMRBandMode = (OMX_AUDIO_AMRBANDMODETYPE)(mMode + 1); + amrParams->eAMRDTXMode = OMX_AUDIO_AMRDTXModeOff; + amrParams->eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPcm: + { + OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = + (OMX_AUDIO_PARAM_PCMMODETYPE *)params; + + if (pcmParams->nPortIndex != 0) { + return OMX_ErrorUndefined; + } + + pcmParams->eNumData = OMX_NumericalDataSigned; + pcmParams->eEndian = OMX_EndianBig; + pcmParams->bInterleaved = OMX_TRUE; + pcmParams->nBitPerSample = 16; + pcmParams->ePCMMode = OMX_AUDIO_PCMModeLinear; + pcmParams->eChannelMapping[0] = OMX_AUDIO_ChannelCF; + + pcmParams->nChannels = 1; + pcmParams->nSamplingRate = kSampleRate; + + return OMX_ErrorNone; + } + + default: + return SimpleSoftOMXComponent::internalGetParameter(index, params); + } +} + +OMX_ERRORTYPE SoftAMRNBEncoder::internalSetParameter( + OMX_INDEXTYPE index, const OMX_PTR params) { + switch (index) { + case OMX_IndexParamStandardComponentRole: + { + const OMX_PARAM_COMPONENTROLETYPE *roleParams = + (const OMX_PARAM_COMPONENTROLETYPE *)params; + + if (strncmp((const char *)roleParams->cRole, + "audio_encoder.amrnb", + OMX_MAX_STRINGNAME_SIZE - 1)) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPortFormat: + { + const OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams = + (const OMX_AUDIO_PARAM_PORTFORMATTYPE *)params; + + if (formatParams->nPortIndex > 1) { + return OMX_ErrorUndefined; + } + + if (formatParams->nIndex > 0) { + return OMX_ErrorNoMore; + } + + if ((formatParams->nPortIndex == 0 + && formatParams->eEncoding != OMX_AUDIO_CodingPCM) + || (formatParams->nPortIndex == 1 + && formatParams->eEncoding != OMX_AUDIO_CodingAMR)) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioAmr: + { + OMX_AUDIO_PARAM_AMRTYPE *amrParams = + (OMX_AUDIO_PARAM_AMRTYPE *)params; + + if (amrParams->nPortIndex != 1) { + return OMX_ErrorUndefined; + } + + if (amrParams->nChannels != 1 + || amrParams->eAMRDTXMode != OMX_AUDIO_AMRDTXModeOff + || amrParams->eAMRFrameFormat + != OMX_AUDIO_AMRFrameFormatFSF + || amrParams->eAMRBandMode < OMX_AUDIO_AMRBandModeNB0 + || amrParams->eAMRBandMode > OMX_AUDIO_AMRBandModeNB7) { + return OMX_ErrorUndefined; + } + + mBitRate = amrParams->nBitRate; + mMode = amrParams->eAMRBandMode - 1; + + amrParams->eAMRDTXMode = OMX_AUDIO_AMRDTXModeOff; + amrParams->eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPcm: + { + OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = + (OMX_AUDIO_PARAM_PCMMODETYPE *)params; + + if (pcmParams->nPortIndex != 0) { + return OMX_ErrorUndefined; + } + + if (pcmParams->nChannels != 1 + || pcmParams->nSamplingRate != kSampleRate) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + + default: + return SimpleSoftOMXComponent::internalSetParameter(index, params); + } +} + +void SoftAMRNBEncoder::onQueueFilled(OMX_U32 portIndex) { + if (mSignalledError) { + return; + } + + List<BufferInfo *> &inQueue = getPortQueue(0); + List<BufferInfo *> &outQueue = getPortQueue(1); + + size_t numBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t); + + for (;;) { + // We do the following until we run out of buffers. + + while (mInputSize < numBytesPerInputFrame) { + // As long as there's still input data to be read we + // will drain "kNumSamplesPerFrame" samples + // into the "mInputFrame" buffer and then encode those + // as a unit into an output buffer. + + if (mSawInputEOS || inQueue.empty()) { + return; + } + + BufferInfo *inInfo = *inQueue.begin(); + OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader; + + const void *inData = inHeader->pBuffer + inHeader->nOffset; + + size_t copy = numBytesPerInputFrame - mInputSize; + if (copy > inHeader->nFilledLen) { + copy = inHeader->nFilledLen; + } + + if (mInputSize == 0) { + mInputTimeUs = inHeader->nTimeStamp; + } + + memcpy((uint8_t *)mInputFrame + mInputSize, inData, copy); + mInputSize += copy; + + inHeader->nOffset += copy; + inHeader->nFilledLen -= copy; + + // "Time" on the input buffer has in effect advanced by the + // number of audio frames we just advanced nOffset by. + inHeader->nTimeStamp += + (copy * 1000000ll / kSampleRate) / sizeof(int16_t); + + if (inHeader->nFilledLen == 0) { + if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { + ALOGV("saw input EOS"); + mSawInputEOS = true; + + // Pad any remaining data with zeroes. + memset((uint8_t *)mInputFrame + mInputSize, + 0, + numBytesPerInputFrame - mInputSize); + + mInputSize = numBytesPerInputFrame; + } + + inQueue.erase(inQueue.begin()); + inInfo->mOwnedByUs = false; + notifyEmptyBufferDone(inHeader); + + inData = NULL; + inHeader = NULL; + inInfo = NULL; + } + } + + // At this point we have all the input data necessary to encode + // a single frame, all we need is an output buffer to store the result + // in. + + if (outQueue.empty()) { + return; + } + + BufferInfo *outInfo = *outQueue.begin(); + OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; + + uint8_t *outPtr = outHeader->pBuffer + outHeader->nOffset; + size_t outAvailable = outHeader->nAllocLen - outHeader->nOffset; + + Frame_Type_3GPP frameType; + int res = AMREncode( + mEncState, mSidState, (Mode)mMode, + mInputFrame, outPtr, &frameType, AMR_TX_WMF); + + CHECK_GE(res, 0); + CHECK_LE((size_t)res, outAvailable); + + // Convert header byte from WMF to IETF format. + outPtr[0] = ((outPtr[0] << 3) | 4) & 0x7c; + + outHeader->nFilledLen = res; + outHeader->nFlags = OMX_BUFFERFLAG_ENDOFFRAME; + + if (mSawInputEOS) { + // We also tag this output buffer with EOS if it corresponds + // to the final input buffer. + outHeader->nFlags = OMX_BUFFERFLAG_EOS; + } + + outHeader->nTimeStamp = mInputTimeUs; + +#if 0 + ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)", + nOutputBytes, mInputTimeUs, outHeader->nFlags); + + hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen); +#endif + + outQueue.erase(outQueue.begin()); + outInfo->mOwnedByUs = false; + notifyFillBufferDone(outHeader); + + outHeader = NULL; + outInfo = NULL; + + mInputSize = 0; + } +} + +} // namespace android + +android::SoftOMXComponent *createSoftOMXComponent( + const char *name, const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, OMX_COMPONENTTYPE **component) { + return new android::SoftAMRNBEncoder(name, callbacks, appData, component); +} diff --git a/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.h b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.h new file mode 100644 index 000000000000..50178c412976 --- /dev/null +++ b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SOFT_AMRNB_ENCODER_H_ + +#define SOFT_AMRNB_ENCODER_H_ + +#include "SimpleSoftOMXComponent.h" + +namespace android { + +struct SoftAMRNBEncoder : public SimpleSoftOMXComponent { + SoftAMRNBEncoder( + const char *name, + const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, + OMX_COMPONENTTYPE **component); + +protected: + virtual ~SoftAMRNBEncoder(); + + virtual OMX_ERRORTYPE internalGetParameter( + OMX_INDEXTYPE index, OMX_PTR params); + + virtual OMX_ERRORTYPE internalSetParameter( + OMX_INDEXTYPE index, const OMX_PTR params); + + virtual void onQueueFilled(OMX_U32 portIndex); + +private: + enum { + kNumBuffers = 4, + kNumSamplesPerFrame = 160, + }; + + void *mEncState; + void *mSidState; + + OMX_U32 mBitRate; + int mMode; + + size_t mInputSize; + int16_t mInputFrame[kNumSamplesPerFrame]; + int64_t mInputTimeUs; + + bool mSawInputEOS; + bool mSignalledError; + + void initPorts(); + status_t initEncoder(); + + status_t setAudioParams(); + + DISALLOW_EVIL_CONSTRUCTORS(SoftAMRNBEncoder); +}; + +} // namespace android + +#endif // SOFT_AMRNB_ENCODER_H_ diff --git a/media/libstagefright/codecs/amrwbenc/Android.mk b/media/libstagefright/codecs/amrwbenc/Android.mk index ae43870b5461..6ce61713d6df 100644 --- a/media/libstagefright/codecs/amrwbenc/Android.mk +++ b/media/libstagefright/codecs/amrwbenc/Android.mk @@ -117,4 +117,26 @@ endif include $(BUILD_STATIC_LIBRARY) +################################################################################ +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + SoftAMRWBEncoder.cpp + +LOCAL_C_INCLUDES := \ + frameworks/base/media/libstagefright/include \ + frameworks/base/include/media/stagefright/openmax \ + frameworks/base/media/libstagefright/codecs/common/include \ + +LOCAL_STATIC_LIBRARIES := \ + libstagefright_amrwbenc + +LOCAL_SHARED_LIBRARIES := \ + libstagefright_omx libstagefright_foundation libutils \ + libstagefright_enc_common + +LOCAL_MODULE := libstagefright_soft_amrwbenc +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.cpp b/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.cpp new file mode 100644 index 000000000000..9ccb49c6b8b8 --- /dev/null +++ b/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.cpp @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "SoftAMRWBEncoder" +#include <utils/Log.h> + +#include "SoftAMRWBEncoder.h" + +#include "cmnMemory.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/hexdump.h> + +namespace android { + +static const int32_t kSampleRate = 16000; + +template<class T> +static void InitOMXParams(T *params) { + params->nSize = sizeof(T); + params->nVersion.s.nVersionMajor = 1; + params->nVersion.s.nVersionMinor = 0; + params->nVersion.s.nRevision = 0; + params->nVersion.s.nStep = 0; +} + +SoftAMRWBEncoder::SoftAMRWBEncoder( + const char *name, + const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, + OMX_COMPONENTTYPE **component) + : SimpleSoftOMXComponent(name, callbacks, appData, component), + mEncoderHandle(NULL), + mApiHandle(NULL), + mMemOperator(NULL), + mBitRate(0), + mMode(VOAMRWB_MD66), + mInputSize(0), + mInputTimeUs(-1ll), + mSawInputEOS(false), + mSignalledError(false) { + initPorts(); + CHECK_EQ(initEncoder(), (status_t)OK); +} + +SoftAMRWBEncoder::~SoftAMRWBEncoder() { + if (mEncoderHandle != NULL) { + CHECK_EQ(VO_ERR_NONE, mApiHandle->Uninit(mEncoderHandle)); + mEncoderHandle = NULL; + } + + delete mApiHandle; + mApiHandle = NULL; + + delete mMemOperator; + mMemOperator = NULL; +} + +void SoftAMRWBEncoder::initPorts() { + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + + def.nPortIndex = 0; + def.eDir = OMX_DirInput; + def.nBufferCountMin = kNumBuffers; + def.nBufferCountActual = def.nBufferCountMin; + def.nBufferSize = kNumSamplesPerFrame * sizeof(int16_t); + def.bEnabled = OMX_TRUE; + def.bPopulated = OMX_FALSE; + def.eDomain = OMX_PortDomainAudio; + def.bBuffersContiguous = OMX_FALSE; + def.nBufferAlignment = 1; + + def.format.audio.cMIMEType = const_cast<char *>("audio/raw"); + def.format.audio.pNativeRender = NULL; + def.format.audio.bFlagErrorConcealment = OMX_FALSE; + def.format.audio.eEncoding = OMX_AUDIO_CodingPCM; + + addPort(def); + + def.nPortIndex = 1; + def.eDir = OMX_DirOutput; + def.nBufferCountMin = kNumBuffers; + def.nBufferCountActual = def.nBufferCountMin; + def.nBufferSize = 8192; + def.bEnabled = OMX_TRUE; + def.bPopulated = OMX_FALSE; + def.eDomain = OMX_PortDomainAudio; + def.bBuffersContiguous = OMX_FALSE; + def.nBufferAlignment = 2; + + def.format.audio.cMIMEType = const_cast<char *>("audio/amr-wb"); + def.format.audio.pNativeRender = NULL; + def.format.audio.bFlagErrorConcealment = OMX_FALSE; + def.format.audio.eEncoding = OMX_AUDIO_CodingAMR; + + addPort(def); +} + +status_t SoftAMRWBEncoder::initEncoder() { + mApiHandle = new VO_AUDIO_CODECAPI; + + if (VO_ERR_NONE != voGetAMRWBEncAPI(mApiHandle)) { + ALOGE("Failed to get api handle"); + return UNKNOWN_ERROR; + } + + mMemOperator = new VO_MEM_OPERATOR; + mMemOperator->Alloc = cmnMemAlloc; + mMemOperator->Copy = cmnMemCopy; + mMemOperator->Free = cmnMemFree; + mMemOperator->Set = cmnMemSet; + mMemOperator->Check = cmnMemCheck; + + VO_CODEC_INIT_USERDATA userData; + memset(&userData, 0, sizeof(userData)); + userData.memflag = VO_IMF_USERMEMOPERATOR; + userData.memData = (VO_PTR) mMemOperator; + + if (VO_ERR_NONE != mApiHandle->Init( + &mEncoderHandle, VO_AUDIO_CodingAMRWB, &userData)) { + ALOGE("Failed to init AMRWB encoder"); + return UNKNOWN_ERROR; + } + + VOAMRWBFRAMETYPE type = VOAMRWB_RFC3267; + if (VO_ERR_NONE != mApiHandle->SetParam( + mEncoderHandle, VO_PID_AMRWB_FRAMETYPE, &type)) { + ALOGE("Failed to set AMRWB encoder frame type to %d", type); + return UNKNOWN_ERROR; + } + + return OK; +} + +OMX_ERRORTYPE SoftAMRWBEncoder::internalGetParameter( + OMX_INDEXTYPE index, OMX_PTR params) { + switch (index) { + case OMX_IndexParamAudioPortFormat: + { + OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams = + (OMX_AUDIO_PARAM_PORTFORMATTYPE *)params; + + if (formatParams->nPortIndex > 1) { + return OMX_ErrorUndefined; + } + + if (formatParams->nIndex > 0) { + return OMX_ErrorNoMore; + } + + formatParams->eEncoding = + (formatParams->nPortIndex == 0) + ? OMX_AUDIO_CodingPCM : OMX_AUDIO_CodingAMR; + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioAmr: + { + OMX_AUDIO_PARAM_AMRTYPE *amrParams = + (OMX_AUDIO_PARAM_AMRTYPE *)params; + + if (amrParams->nPortIndex != 1) { + return OMX_ErrorUndefined; + } + + amrParams->nChannels = 1; + amrParams->nBitRate = mBitRate; + + amrParams->eAMRBandMode = + (OMX_AUDIO_AMRBANDMODETYPE)(mMode + OMX_AUDIO_AMRBandModeWB0); + + amrParams->eAMRDTXMode = OMX_AUDIO_AMRDTXModeOff; + amrParams->eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPcm: + { + OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = + (OMX_AUDIO_PARAM_PCMMODETYPE *)params; + + if (pcmParams->nPortIndex != 0) { + return OMX_ErrorUndefined; + } + + pcmParams->eNumData = OMX_NumericalDataSigned; + pcmParams->eEndian = OMX_EndianBig; + pcmParams->bInterleaved = OMX_TRUE; + pcmParams->nBitPerSample = 16; + pcmParams->ePCMMode = OMX_AUDIO_PCMModeLinear; + pcmParams->eChannelMapping[0] = OMX_AUDIO_ChannelCF; + + pcmParams->nChannels = 1; + pcmParams->nSamplingRate = kSampleRate; + + return OMX_ErrorNone; + } + + default: + return SimpleSoftOMXComponent::internalGetParameter(index, params); + } +} + +OMX_ERRORTYPE SoftAMRWBEncoder::internalSetParameter( + OMX_INDEXTYPE index, const OMX_PTR params) { + switch (index) { + case OMX_IndexParamStandardComponentRole: + { + const OMX_PARAM_COMPONENTROLETYPE *roleParams = + (const OMX_PARAM_COMPONENTROLETYPE *)params; + + if (strncmp((const char *)roleParams->cRole, + "audio_encoder.amrwb", + OMX_MAX_STRINGNAME_SIZE - 1)) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPortFormat: + { + const OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams = + (const OMX_AUDIO_PARAM_PORTFORMATTYPE *)params; + + if (formatParams->nPortIndex > 1) { + return OMX_ErrorUndefined; + } + + if (formatParams->nIndex > 0) { + return OMX_ErrorNoMore; + } + + if ((formatParams->nPortIndex == 0 + && formatParams->eEncoding != OMX_AUDIO_CodingPCM) + || (formatParams->nPortIndex == 1 + && formatParams->eEncoding != OMX_AUDIO_CodingAMR)) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioAmr: + { + OMX_AUDIO_PARAM_AMRTYPE *amrParams = + (OMX_AUDIO_PARAM_AMRTYPE *)params; + + if (amrParams->nPortIndex != 1) { + return OMX_ErrorUndefined; + } + + if (amrParams->nChannels != 1 + || amrParams->eAMRDTXMode != OMX_AUDIO_AMRDTXModeOff + || amrParams->eAMRFrameFormat + != OMX_AUDIO_AMRFrameFormatFSF + || amrParams->eAMRBandMode < OMX_AUDIO_AMRBandModeWB0 + || amrParams->eAMRBandMode > OMX_AUDIO_AMRBandModeWB8) { + return OMX_ErrorUndefined; + } + + mBitRate = amrParams->nBitRate; + + mMode = (VOAMRWBMODE)( + amrParams->eAMRBandMode - OMX_AUDIO_AMRBandModeWB0); + + amrParams->eAMRDTXMode = OMX_AUDIO_AMRDTXModeOff; + amrParams->eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; + + if (VO_ERR_NONE != + mApiHandle->SetParam( + mEncoderHandle, VO_PID_AMRWB_MODE, &mMode)) { + ALOGE("Failed to set AMRWB encoder mode to %d", mMode); + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPcm: + { + OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = + (OMX_AUDIO_PARAM_PCMMODETYPE *)params; + + if (pcmParams->nPortIndex != 0) { + return OMX_ErrorUndefined; + } + + if (pcmParams->nChannels != 1 + || pcmParams->nSamplingRate != (OMX_U32)kSampleRate) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + + default: + return SimpleSoftOMXComponent::internalSetParameter(index, params); + } +} + +void SoftAMRWBEncoder::onQueueFilled(OMX_U32 portIndex) { + if (mSignalledError) { + return; + } + + List<BufferInfo *> &inQueue = getPortQueue(0); + List<BufferInfo *> &outQueue = getPortQueue(1); + + size_t numBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t); + + for (;;) { + // We do the following until we run out of buffers. + + while (mInputSize < numBytesPerInputFrame) { + // As long as there's still input data to be read we + // will drain "kNumSamplesPerFrame" samples + // into the "mInputFrame" buffer and then encode those + // as a unit into an output buffer. + + if (mSawInputEOS || inQueue.empty()) { + return; + } + + BufferInfo *inInfo = *inQueue.begin(); + OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader; + + const void *inData = inHeader->pBuffer + inHeader->nOffset; + + size_t copy = numBytesPerInputFrame - mInputSize; + if (copy > inHeader->nFilledLen) { + copy = inHeader->nFilledLen; + } + + if (mInputSize == 0) { + mInputTimeUs = inHeader->nTimeStamp; + } + + memcpy((uint8_t *)mInputFrame + mInputSize, inData, copy); + mInputSize += copy; + + inHeader->nOffset += copy; + inHeader->nFilledLen -= copy; + + // "Time" on the input buffer has in effect advanced by the + // number of audio frames we just advanced nOffset by. + inHeader->nTimeStamp += + (copy * 1000000ll / kSampleRate) / sizeof(int16_t); + + if (inHeader->nFilledLen == 0) { + if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { + ALOGV("saw input EOS"); + mSawInputEOS = true; + + // Pad any remaining data with zeroes. + memset((uint8_t *)mInputFrame + mInputSize, + 0, + numBytesPerInputFrame - mInputSize); + + mInputSize = numBytesPerInputFrame; + } + + inQueue.erase(inQueue.begin()); + inInfo->mOwnedByUs = false; + notifyEmptyBufferDone(inHeader); + + inData = NULL; + inHeader = NULL; + inInfo = NULL; + } + } + + // At this point we have all the input data necessary to encode + // a single frame, all we need is an output buffer to store the result + // in. + + if (outQueue.empty()) { + return; + } + + BufferInfo *outInfo = *outQueue.begin(); + OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; + + uint8_t *outPtr = outHeader->pBuffer + outHeader->nOffset; + size_t outAvailable = outHeader->nAllocLen - outHeader->nOffset; + + VO_CODECBUFFER inputData; + memset(&inputData, 0, sizeof(inputData)); + inputData.Buffer = (unsigned char *) mInputFrame; + inputData.Length = mInputSize; + + CHECK_EQ(VO_ERR_NONE, + mApiHandle->SetInputData(mEncoderHandle, &inputData)); + + VO_CODECBUFFER outputData; + memset(&outputData, 0, sizeof(outputData)); + VO_AUDIO_OUTPUTINFO outputInfo; + memset(&outputInfo, 0, sizeof(outputInfo)); + + outputData.Buffer = outPtr; + outputData.Length = outAvailable; + VO_U32 ret = mApiHandle->GetOutputData( + mEncoderHandle, &outputData, &outputInfo); + CHECK(ret == VO_ERR_NONE || ret == VO_ERR_INPUT_BUFFER_SMALL); + + outHeader->nFilledLen = outputData.Length; + outHeader->nFlags = OMX_BUFFERFLAG_ENDOFFRAME; + + if (mSawInputEOS) { + // We also tag this output buffer with EOS if it corresponds + // to the final input buffer. + outHeader->nFlags = OMX_BUFFERFLAG_EOS; + } + + outHeader->nTimeStamp = mInputTimeUs; + +#if 0 + ALOGI("sending %ld bytes of data (time = %lld us, flags = 0x%08lx)", + outHeader->nFilledLen, mInputTimeUs, outHeader->nFlags); + + hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen); +#endif + + outQueue.erase(outQueue.begin()); + outInfo->mOwnedByUs = false; + notifyFillBufferDone(outHeader); + + outHeader = NULL; + outInfo = NULL; + + mInputSize = 0; + } +} + +} // namespace android + +android::SoftOMXComponent *createSoftOMXComponent( + const char *name, const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, OMX_COMPONENTTYPE **component) { + return new android::SoftAMRWBEncoder(name, callbacks, appData, component); +} diff --git a/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.h b/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.h new file mode 100644 index 000000000000..d0c1dabf9313 --- /dev/null +++ b/media/libstagefright/codecs/amrwbenc/SoftAMRWBEncoder.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SOFT_AMRWB_ENCODER_H_ + +#define SOFT_AMRWB_ENCODER_H_ + +#include "SimpleSoftOMXComponent.h" + +#include "voAMRWB.h" + +struct VO_AUDIO_CODECAPI; +struct VO_MEM_OPERATOR; + +namespace android { + +struct SoftAMRWBEncoder : public SimpleSoftOMXComponent { + SoftAMRWBEncoder( + const char *name, + const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, + OMX_COMPONENTTYPE **component); + +protected: + virtual ~SoftAMRWBEncoder(); + + virtual OMX_ERRORTYPE internalGetParameter( + OMX_INDEXTYPE index, OMX_PTR params); + + virtual OMX_ERRORTYPE internalSetParameter( + OMX_INDEXTYPE index, const OMX_PTR params); + + virtual void onQueueFilled(OMX_U32 portIndex); + +private: + enum { + kNumBuffers = 4, + kNumSamplesPerFrame = 320, + }; + + void *mEncoderHandle; + VO_AUDIO_CODECAPI *mApiHandle; + VO_MEM_OPERATOR *mMemOperator; + + OMX_U32 mBitRate; + VOAMRWBMODE mMode; + + size_t mInputSize; + int16_t mInputFrame[kNumSamplesPerFrame]; + int64_t mInputTimeUs; + + bool mSawInputEOS; + bool mSignalledError; + + void initPorts(); + status_t initEncoder(); + + DISALLOW_EVIL_CONSTRUCTORS(SoftAMRWBEncoder); +}; + +} // namespace android + +#endif // SOFT_AMRWB_ENCODER_H_ diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp index 5cc3f78804ab..f3ef3de9724d 100644 --- a/media/libstagefright/colorconversion/ColorConverter.cpp +++ b/media/libstagefright/colorconversion/ColorConverter.cpp @@ -144,8 +144,8 @@ status_t ColorConverter::convertCbYCrY( return ERROR_UNSUPPORTED; } - uint32_t *dst_ptr = (uint32_t *)dst.mBits - + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; + uint16_t *dst_ptr = (uint16_t *)dst.mBits + + dst.mCropTop * dst.mWidth + dst.mCropLeft; const uint8_t *src_ptr = (const uint8_t *)src.mBits + (src.mCropTop * dst.mWidth + src.mCropLeft) * 2; @@ -182,11 +182,15 @@ status_t ColorConverter::convertCbYCrY( | ((kAdjustedClip[g2] >> 2) << 5) | (kAdjustedClip[b2] >> 3); - dst_ptr[x / 2] = (rgb2 << 16) | rgb1; + if (x + 1 < src.cropWidth()) { + *(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1; + } else { + dst_ptr[x] = rgb1; + } } src_ptr += src.mWidth * 2; - dst_ptr += dst.mWidth / 2; + dst_ptr += dst.mWidth; } return OK; @@ -290,15 +294,14 @@ status_t ColorConverter::convertQCOMYUV420SemiPlanar( const BitmapParams &src, const BitmapParams &dst) { uint8_t *kAdjustedClip = initClip(); - if (!((dst.mWidth & 3) == 0 - && (src.mCropLeft & 1) == 0 + if (!((src.mCropLeft & 1) == 0 && src.cropWidth() == dst.cropWidth() && src.cropHeight() == dst.cropHeight())) { return ERROR_UNSUPPORTED; } - uint32_t *dst_ptr = (uint32_t *)dst.mBits - + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; + uint16_t *dst_ptr = (uint16_t *)dst.mBits + + dst.mCropTop * dst.mWidth + dst.mCropLeft; const uint8_t *src_y = (const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft; @@ -340,7 +343,11 @@ status_t ColorConverter::convertQCOMYUV420SemiPlanar( | ((kAdjustedClip[g2] >> 2) << 5) | (kAdjustedClip[r2] >> 3); - dst_ptr[x / 2] = (rgb2 << 16) | rgb1; + if (x + 1 < src.cropWidth()) { + *(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1; + } else { + dst_ptr[x] = rgb1; + } } src_y += src.mWidth; @@ -349,7 +356,7 @@ status_t ColorConverter::convertQCOMYUV420SemiPlanar( src_u += src.mWidth; } - dst_ptr += dst.mWidth / 2; + dst_ptr += dst.mWidth; } return OK; @@ -361,15 +368,14 @@ status_t ColorConverter::convertYUV420SemiPlanar( uint8_t *kAdjustedClip = initClip(); - if (!((dst.mWidth & 3) == 0 - && (src.mCropLeft & 1) == 0 + if (!((src.mCropLeft & 1) == 0 && src.cropWidth() == dst.cropWidth() && src.cropHeight() == dst.cropHeight())) { return ERROR_UNSUPPORTED; } - uint32_t *dst_ptr = (uint32_t *)dst.mBits - + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; + uint16_t *dst_ptr = (uint16_t *)dst.mBits + + dst.mCropTop * dst.mWidth + dst.mCropLeft; const uint8_t *src_y = (const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft; @@ -411,7 +417,11 @@ status_t ColorConverter::convertYUV420SemiPlanar( | ((kAdjustedClip[g2] >> 2) << 5) | (kAdjustedClip[r2] >> 3); - dst_ptr[x / 2] = (rgb2 << 16) | rgb1; + if (x + 1 < src.cropWidth()) { + *(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1; + } else { + dst_ptr[x] = rgb1; + } } src_y += src.mWidth; @@ -420,7 +430,7 @@ status_t ColorConverter::convertYUV420SemiPlanar( src_u += src.mWidth; } - dst_ptr += dst.mWidth / 2; + dst_ptr += dst.mWidth; } return OK; @@ -430,15 +440,14 @@ status_t ColorConverter::convertTIYUV420PackedSemiPlanar( const BitmapParams &src, const BitmapParams &dst) { uint8_t *kAdjustedClip = initClip(); - if (!((dst.mWidth & 3) == 0 - && (src.mCropLeft & 1) == 0 + if (!((src.mCropLeft & 1) == 0 && src.cropWidth() == dst.cropWidth() && src.cropHeight() == dst.cropHeight())) { return ERROR_UNSUPPORTED; } - uint32_t *dst_ptr = (uint32_t *)dst.mBits - + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; + uint16_t *dst_ptr = (uint16_t *)dst.mBits + + dst.mCropTop * dst.mWidth + dst.mCropLeft; const uint8_t *src_y = (const uint8_t *)src.mBits; @@ -478,7 +487,11 @@ status_t ColorConverter::convertTIYUV420PackedSemiPlanar( | ((kAdjustedClip[g2] >> 2) << 5) | (kAdjustedClip[b2] >> 3); - dst_ptr[x / 2] = (rgb2 << 16) | rgb1; + if (x + 1 < src.cropWidth()) { + *(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1; + } else { + dst_ptr[x] = rgb1; + } } src_y += src.mWidth; @@ -487,7 +500,7 @@ status_t ColorConverter::convertTIYUV420PackedSemiPlanar( src_u += src.mWidth; } - dst_ptr += dst.mWidth / 2; + dst_ptr += dst.mWidth; } return OK; diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index 82c647623e5b..a7a3d4781633 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -41,7 +41,7 @@ struct ISurfaceTexture; class DrmManagerClinet; class DecryptHandle; -class TimedTextPlayer; +class TimedTextDriver; struct WVMExtractor; struct AwesomeRenderer : public RefBase { @@ -232,7 +232,7 @@ private: sp<DecryptHandle> mDecryptHandle; int64_t mLastVideoTimeUs; - TimedTextPlayer *mTextPlayer; + TimedTextDriver *mTextDriver; mutable Mutex mTimedTextLock; sp<WVMExtractor> mWVMExtractor; @@ -326,4 +326,3 @@ private: } // namespace android #endif // AWESOME_PLAYER_H_ - diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h index 53e764fd504b..2c87b34e42a8 100644 --- a/media/libstagefright/include/OMX.h +++ b/media/libstagefright/include/OMX.h @@ -31,7 +31,7 @@ class OMX : public BnOMX, public: OMX(); - virtual bool livesLocally(pid_t pid); + virtual bool livesLocally(node_id node, pid_t pid); virtual status_t listNodes(List<ComponentInfo> *list); diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp index 4fbf47ee979c..a1644d2c99fa 100644 --- a/media/libstagefright/matroska/MatroskaExtractor.cpp +++ b/media/libstagefright/matroska/MatroskaExtractor.cpp @@ -610,36 +610,41 @@ bool MatroskaExtractor::isLiveStreaming() const { return mIsLiveStreaming; } -static void addESDSFromAudioSpecificInfo( - const sp<MetaData> &meta, const void *asi, size_t asiSize) { +static void addESDSFromCodecPrivate( + const sp<MetaData> &meta, + bool isAudio, const void *priv, size_t privSize) { static const uint8_t kStaticESDS[] = { 0x03, 22, 0x00, 0x00, // ES_ID 0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag 0x04, 17, - 0x40, // Audio ISO/IEC 14496-3 + 0x40, // ObjectTypeIndication 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - // AudioSpecificInfo (with size prefix) follows + // CodecSpecificInfo (with size prefix) follows }; // Make sure all sizes can be coded in a single byte. - CHECK(asiSize + 22 - 2 < 128); - size_t esdsSize = sizeof(kStaticESDS) + asiSize + 1; + CHECK(privSize + 22 - 2 < 128); + size_t esdsSize = sizeof(kStaticESDS) + privSize + 1; uint8_t *esds = new uint8_t[esdsSize]; memcpy(esds, kStaticESDS, sizeof(kStaticESDS)); uint8_t *ptr = esds + sizeof(kStaticESDS); - *ptr++ = asiSize; - memcpy(ptr, asi, asiSize); + *ptr++ = privSize; + memcpy(ptr, priv, privSize); // Increment by codecPrivateSize less 2 bytes that are accounted for // already in lengths of 22/17 - esds[1] += asiSize - 2; - esds[6] += asiSize - 2; + esds[1] += privSize - 2; + esds[6] += privSize - 2; + + // Set ObjectTypeIndication. + esds[7] = isAudio ? 0x40 // Audio ISO/IEC 14496-3 + : 0x20; // Visual ISO/IEC 14496-2 meta->setData(kKeyESDS, 0, esds, esdsSize); @@ -707,9 +712,21 @@ void MatroskaExtractor::addTracks() { if (!strcmp("V_MPEG4/ISO/AVC", codecID)) { meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); meta->setData(kKeyAVCC, 0, codecPrivate, codecPrivateSize); + } else if (!strcmp("V_MPEG4/ISO/ASP", codecID)) { + if (codecPrivateSize > 0) { + meta->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + addESDSFromCodecPrivate( + meta, false, codecPrivate, codecPrivateSize); + } else { + ALOGW("%s is detected, but does not have configuration.", + codecID); + continue; + } } else if (!strcmp("V_VP8", codecID)) { meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_VPX); } else { + ALOGW("%s is not supported.", codecID); continue; } @@ -727,13 +744,16 @@ void MatroskaExtractor::addTracks() { meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC); CHECK(codecPrivateSize >= 2); - addESDSFromAudioSpecificInfo( - meta, codecPrivate, codecPrivateSize); + addESDSFromCodecPrivate( + meta, true, codecPrivate, codecPrivateSize); } else if (!strcmp("A_VORBIS", codecID)) { meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS); addVorbisCodecInfo(meta, codecPrivate, codecPrivateSize); + } else if (!strcmp("A_MPEG/L3", codecID)) { + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); } else { + ALOGW("%s is not supported.", codecID); continue; } diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp index 694b12d8279b..ace883c7f6b5 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -185,7 +185,7 @@ void OMX::binderDied(const wp<IBinder> &the_late_who) { instance->onObserverDied(mMaster); } -bool OMX::livesLocally(pid_t pid) { +bool OMX::livesLocally(node_id node, pid_t pid) { return pid == getpid(); } diff --git a/media/libstagefright/omx/SimpleSoftOMXComponent.cpp b/media/libstagefright/omx/SimpleSoftOMXComponent.cpp index 0914f32061a5..c79e01f6ddf9 100644 --- a/media/libstagefright/omx/SimpleSoftOMXComponent.cpp +++ b/media/libstagefright/omx/SimpleSoftOMXComponent.cpp @@ -333,8 +333,9 @@ OMX_ERRORTYPE SimpleSoftOMXComponent::getState(OMX_STATETYPE *state) { void SimpleSoftOMXComponent::onMessageReceived(const sp<AMessage> &msg) { Mutex::Autolock autoLock(mLock); - - switch (msg->what()) { + uint32_t msgType = msg->what(); + ALOGV("msgType = %d", msgType); + switch (msgType) { case kWhatSendCommand: { int32_t cmd, param; @@ -354,27 +355,27 @@ void SimpleSoftOMXComponent::onMessageReceived(const sp<AMessage> &msg) { CHECK(mState == OMX_StateExecuting && mTargetState == mState); bool found = false; - for (size_t i = 0; i < mPorts.size(); ++i) { - PortInfo *port = &mPorts.editItemAt(i); + size_t portIndex = (kWhatEmptyThisBuffer == msgType)? + header->nInputPortIndex: header->nOutputPortIndex; + PortInfo *port = &mPorts.editItemAt(portIndex); - for (size_t j = 0; j < port->mBuffers.size(); ++j) { - BufferInfo *buffer = &port->mBuffers.editItemAt(j); + for (size_t j = 0; j < port->mBuffers.size(); ++j) { + BufferInfo *buffer = &port->mBuffers.editItemAt(j); - if (buffer->mHeader == header) { - CHECK(!buffer->mOwnedByUs); + if (buffer->mHeader == header) { + CHECK(!buffer->mOwnedByUs); - buffer->mOwnedByUs = true; + buffer->mOwnedByUs = true; - CHECK((msg->what() == kWhatEmptyThisBuffer - && port->mDef.eDir == OMX_DirInput) - || (port->mDef.eDir == OMX_DirOutput)); + CHECK((msgType == kWhatEmptyThisBuffer + && port->mDef.eDir == OMX_DirInput) + || (port->mDef.eDir == OMX_DirOutput)); - port->mQueue.push_back(buffer); - onQueueFilled(i); + port->mQueue.push_back(buffer); + onQueueFilled(portIndex); - found = true; - break; - } + found = true; + break; } } diff --git a/media/libstagefright/omx/SoftOMXPlugin.cpp b/media/libstagefright/omx/SoftOMXPlugin.cpp index cf9e8c91f651..99ffe7d65814 100644 --- a/media/libstagefright/omx/SoftOMXPlugin.cpp +++ b/media/libstagefright/omx/SoftOMXPlugin.cpp @@ -37,7 +37,9 @@ static const struct { { "OMX.google.aac.decoder", "aacdec", "audio_decoder.aac" }, { "OMX.google.aac.encoder", "aacenc", "audio_encoder.aac" }, { "OMX.google.amrnb.decoder", "amrdec", "audio_decoder.amrnb" }, + { "OMX.google.amrnb.encoder", "amrnbenc", "audio_encoder.amrnb" }, { "OMX.google.amrwb.decoder", "amrdec", "audio_decoder.amrwb" }, + { "OMX.google.amrwb.encoder", "amrwbenc", "audio_encoder.amrwb" }, { "OMX.google.h264.decoder", "h264dec", "video_decoder.avc" }, { "OMX.google.g711.alaw.decoder", "g711dec", "audio_decoder.g711alaw" }, { "OMX.google.g711.mlaw.decoder", "g711dec", "audio_decoder.g711mlaw" }, diff --git a/media/libstagefright/omx/tests/Android.mk b/media/libstagefright/omx/tests/Android.mk index bf694285afcc..41c08be74275 100644 --- a/media/libstagefright/omx/tests/Android.mk +++ b/media/libstagefright/omx/tests/Android.mk @@ -7,11 +7,13 @@ LOCAL_SRC_FILES = \ LOCAL_SHARED_LIBRARIES := \ libstagefright libbinder libmedia libutils -LOCAL_C_INCLUDES:= \ +LOCAL_C_INCLUDES := \ $(JNI_H_INCLUDE) \ frameworks/base/media/libstagefright \ $(TOP)/frameworks/base/include/media/stagefright/openmax -LOCAL_MODULE:= omx_tests +LOCAL_MODULE := omx_tests + +LOCAL_MODULE_TAGS := tests include $(BUILD_EXECUTABLE) diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk index 59d0e154c597..8b23dee0c0c7 100644 --- a/media/libstagefright/timedtext/Android.mk +++ b/media/libstagefright/timedtext/Android.mk @@ -3,7 +3,10 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ TextDescriptions.cpp \ - TimedTextParser.cpp \ + TimedTextDriver.cpp \ + TimedTextInBandSource.cpp \ + TimedTextSource.cpp \ + TimedTextSRTSource.cpp \ TimedTextPlayer.cpp LOCAL_CFLAGS += -Wno-multichar diff --git a/media/libstagefright/timedtext/TimedTextDriver.cpp b/media/libstagefright/timedtext/TimedTextDriver.cpp new file mode 100644 index 000000000000..9ec94153fc89 --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextDriver.cpp @@ -0,0 +1,223 @@ + /* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "TimedTextDriver" +#include <utils/Log.h> + +#include <binder/IPCThreadState.h> + +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/Utils.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> + +#include "TimedTextDriver.h" + +#include "TextDescriptions.h" +#include "TimedTextPlayer.h" +#include "TimedTextSource.h" + +namespace android { + +TimedTextDriver::TimedTextDriver( + const wp<MediaPlayerBase> &listener) + : mLooper(new ALooper), + mListener(listener), + mState(UNINITIALIZED) { + mLooper->setName("TimedTextDriver"); + mLooper->start(); + mPlayer = new TimedTextPlayer(listener); + mLooper->registerHandler(mPlayer); +} + +TimedTextDriver::~TimedTextDriver() { + mTextInBandVector.clear(); + mTextOutOfBandVector.clear(); + mLooper->stop(); +} + +status_t TimedTextDriver::setTimedTextTrackIndex_l(int32_t index) { + if (index >= + (int)(mTextInBandVector.size() + mTextOutOfBandVector.size())) { + return BAD_VALUE; + } + + sp<TimedTextSource> source; + if (index < mTextInBandVector.size()) { + source = mTextInBandVector.itemAt(index); + } else { + source = mTextOutOfBandVector.itemAt(index - mTextInBandVector.size()); + } + mPlayer->setDataSource(source); + return OK; +} + +status_t TimedTextDriver::start() { + Mutex::Autolock autoLock(mLock); + switch (mState) { + case UNINITIALIZED: + return INVALID_OPERATION; + case STOPPED: + mPlayer->start(); + break; + case PLAYING: + return OK; + case PAUSED: + mPlayer->resume(); + break; + default: + TRESPASS(); + } + mState = PLAYING; + return OK; +} + +status_t TimedTextDriver::stop() { + return pause(); +} + +// TODO: Test if pause() works properly. +// Scenario 1: start - pause - resume +// Scenario 2: start - seek +// Scenario 3: start - pause - seek - resume +status_t TimedTextDriver::pause() { + Mutex::Autolock autoLock(mLock); + switch (mState) { + case UNINITIALIZED: + return INVALID_OPERATION; + case STOPPED: + return OK; + case PLAYING: + mPlayer->pause(); + break; + case PAUSED: + return OK; + default: + TRESPASS(); + } + mState = PAUSED; + return OK; +} + +status_t TimedTextDriver::resume() { + return start(); +} + +status_t TimedTextDriver::seekToAsync(int64_t timeUs) { + mPlayer->seekToAsync(timeUs); + return OK; +} + +status_t TimedTextDriver::setTimedTextTrackIndex(int32_t index) { + // TODO: This is current implementation for MediaPlayer::disableTimedText(). + // Find better way for readability. + if (index < 0) { + mPlayer->pause(); + return OK; + } + + status_t ret = OK; + Mutex::Autolock autoLock(mLock); + switch (mState) { + case UNINITIALIZED: + ret = INVALID_OPERATION; + break; + case PAUSED: + ret = setTimedTextTrackIndex_l(index); + break; + case PLAYING: + mPlayer->pause(); + ret = setTimedTextTrackIndex_l(index); + if (ret != OK) { + break; + } + mPlayer->start(); + break; + case STOPPED: + // TODO: The only difference between STOPPED and PAUSED is this + // part. Revise the flow from "MediaPlayer::enableTimedText()" and + // remove one of the status, PAUSED and STOPPED, if possible. + ret = setTimedTextTrackIndex_l(index); + if (ret != OK) { + break; + } + mPlayer->start(); + break; + defaut: + TRESPASS(); + } + return ret; +} + +status_t TimedTextDriver::addInBandTextSource( + const sp<MediaSource>& mediaSource) { + sp<TimedTextSource> source = + TimedTextSource::CreateTimedTextSource(mediaSource); + if (source == NULL) { + return ERROR_UNSUPPORTED; + } + Mutex::Autolock autoLock(mLock); + mTextInBandVector.add(source); + if (mState == UNINITIALIZED) { + mState = STOPPED; + } + return OK; +} + +status_t TimedTextDriver::addOutOfBandTextSource( + const Parcel &request) { + // TODO: Define "TimedTextSource::CreateFromURI(uri)" + // and move below lines there..? + + // String values written in Parcel are UTF-16 values. + const String16 uri16 = request.readString16(); + String8 uri = String8(request.readString16()); + + uri.toLower(); + // To support local subtitle file only for now + if (strncasecmp("file://", uri.string(), 7)) { + return ERROR_UNSUPPORTED; + } + sp<DataSource> dataSource = + DataSource::CreateFromURI(uri); + if (dataSource == NULL) { + return ERROR_UNSUPPORTED; + } + + sp<TimedTextSource> source; + if (uri.getPathExtension() == String8(".srt")) { + source = TimedTextSource::CreateTimedTextSource( + dataSource, TimedTextSource::OUT_OF_BAND_FILE_SRT); + } + + if (source == NULL) { + return ERROR_UNSUPPORTED; + } + + Mutex::Autolock autoLock(mLock); + + mTextOutOfBandVector.add(source); + if (mState == UNINITIALIZED) { + mState = STOPPED; + } + return OK; +} + +} // namespace android diff --git a/media/libstagefright/timedtext/TimedTextDriver.h b/media/libstagefright/timedtext/TimedTextDriver.h new file mode 100644 index 000000000000..efedb6e9028e --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextDriver.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIMED_TEXT_DRIVER_H_ +#define TIMED_TEXT_DRIVER_H_ + +#include <media/stagefright/foundation/ABase.h> // for DISALLOW_* macro +#include <utils/Errors.h> // for status_t +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class ALooper; +class MediaPlayerBase; +class MediaSource; +class Parcel; +class TimedTextPlayer; +class TimedTextSource; + +class TimedTextDriver { +public: + TimedTextDriver(const wp<MediaPlayerBase> &listener); + + ~TimedTextDriver(); + + // TODO: pause-resume pair seems equivalent to stop-start pair. + // Check if it is replaceable with stop-start. + status_t start(); + status_t stop(); + status_t pause(); + status_t resume(); + + status_t seekToAsync(int64_t timeUs); + + status_t addInBandTextSource(const sp<MediaSource>& source); + status_t addOutOfBandTextSource(const Parcel &request); + + status_t setTimedTextTrackIndex(int32_t index); + +private: + Mutex mLock; + + enum State { + UNINITIALIZED, + STOPPED, + PLAYING, + PAUSED, + }; + + sp<ALooper> mLooper; + sp<TimedTextPlayer> mPlayer; + wp<MediaPlayerBase> mListener; + + // Variables to be guarded by mLock. + State mState; + Vector<sp<TimedTextSource> > mTextInBandVector; + Vector<sp<TimedTextSource> > mTextOutOfBandVector; + // -- End of variables to be guarded by mLock + + status_t setTimedTextTrackIndex_l(int32_t index); + + DISALLOW_EVIL_CONSTRUCTORS(TimedTextDriver); +}; + +} // namespace android + +#endif // TIMED_TEXT_DRIVER_H_ diff --git a/media/libstagefright/timedtext/TimedTextInBandSource.cpp b/media/libstagefright/timedtext/TimedTextInBandSource.cpp new file mode 100644 index 000000000000..f2c4d54c4773 --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextInBandSource.cpp @@ -0,0 +1,118 @@ + /* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "TimedTextInBandSource" +#include <utils/Log.h> + +#include <binder/Parcel.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDebug.h> // CHECK_XX macro +#include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> + +#include "TimedTextInBandSource.h" +#include "TextDescriptions.h" + +namespace android { + +TimedTextInBandSource::TimedTextInBandSource(const sp<MediaSource>& mediaSource) + : mSource(mediaSource) { +} + +TimedTextInBandSource::~TimedTextInBandSource() { +} + +status_t TimedTextInBandSource::read( + int64_t *timeUs, Parcel *parcel, const MediaSource::ReadOptions *options) { + MediaBuffer *textBuffer = NULL; + status_t err = mSource->read(&textBuffer, options); + if (err != OK) { + return err; + } + CHECK(textBuffer != NULL); + textBuffer->meta_data()->findInt64(kKeyTime, timeUs); + // TODO: this is legacy code. when 'timeUs' can be <= 0? + if (*timeUs > 0) { + extractAndAppendLocalDescriptions(*timeUs, textBuffer, parcel); + } + textBuffer->release(); + return OK; +} + +// Each text sample consists of a string of text, optionally with sample +// modifier description. The modifier description could specify a new +// text style for the string of text. These descriptions are present only +// if they are needed. This method is used to extract the modifier +// description and append it at the end of the text. +status_t TimedTextInBandSource::extractAndAppendLocalDescriptions( + int64_t timeUs, const MediaBuffer *textBuffer, Parcel *parcel) { + const void *data; + size_t size = 0; + int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS; + + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) { + data = textBuffer->data(); + size = textBuffer->size(); + + if (size > 0) { + parcel->freeData(); + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + return TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, timeUs / 1000, parcel); + } + return OK; + } + return ERROR_UNSUPPORTED; +} + +// To extract and send the global text descriptions for all the text samples +// in the text track or text file. +// TODO: send error message to application via notifyListener()...? +status_t TimedTextInBandSource::extractGlobalDescriptions(Parcel *parcel) { + const void *data; + size_t size = 0; + int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS; + + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + // support 3GPP only for now + if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) == 0) { + uint32_t type; + // get the 'tx3g' box content. This box contains the text descriptions + // used to render the text track + if (!mSource->getFormat()->findData( + kKeyTextFormatData, &type, &data, &size)) { + return ERROR_MALFORMED; + } + + if (size > 0) { + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + return TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, 0, parcel); + } + return OK; + } + return ERROR_UNSUPPORTED; +} + +} // namespace android diff --git a/media/libstagefright/timedtext/TimedTextInBandSource.h b/media/libstagefright/timedtext/TimedTextInBandSource.h new file mode 100644 index 000000000000..26e5737f06ee --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextInBandSource.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIMED_TEXT_IN_BAND_SOURCE_H_ +#define TIMED_TEXT_IN_BAND_SOURCE_H_ + +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> + +#include "TimedTextSource.h" + +namespace android { + +class MediaBuffer; +class Parcel; + +class TimedTextInBandSource : public TimedTextSource { + public: + TimedTextInBandSource(const sp<MediaSource>& mediaSource); + virtual status_t start() { return mSource->start(); } + virtual status_t stop() { return mSource->stop(); } + virtual status_t read( + int64_t *timeUs, + Parcel *parcel, + const MediaSource::ReadOptions *options = NULL); + virtual status_t extractGlobalDescriptions(Parcel *parcel); + + protected: + virtual ~TimedTextInBandSource(); + + private: + sp<MediaSource> mSource; + + status_t extractAndAppendLocalDescriptions( + int64_t timeUs, const MediaBuffer *textBuffer, Parcel *parcel); + + DISALLOW_EVIL_CONSTRUCTORS(TimedTextInBandSource); +}; + +} // namespace android + +#endif // TIMED_TEXT_IN_BAND_SOURCE_H_ diff --git a/media/libstagefright/timedtext/TimedTextParser.h b/media/libstagefright/timedtext/TimedTextParser.h deleted file mode 100644 index 44774c296932..000000000000 --- a/media/libstagefright/timedtext/TimedTextParser.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TIMED_TEXT_PARSER_H_ - -#define TIMED_TEXT_PARSER_H_ - -#include <media/MediaPlayerInterface.h> -#include <media/stagefright/foundation/ABase.h> -#include <media/stagefright/foundation/AString.h> -#include <media/stagefright/MediaSource.h> - -namespace android { - -class DataSource; - -class TimedTextParser : public RefBase { -public: - TimedTextParser(); - virtual ~TimedTextParser(); - - enum FileType { - OUT_OF_BAND_FILE_SRT = 1, - }; - - status_t getText(AString *text, int64_t *startTimeUs, int64_t *endTimeUs, - const MediaSource::ReadOptions *options = NULL); - status_t init(const sp<DataSource> &dataSource, FileType fileType); - void reset(); - -private: - Mutex mLock; - - sp<DataSource> mDataSource; - off64_t mOffset; - - struct TextInfo { - int64_t endTimeUs; - // the offset of the text in the original file - off64_t offset; - int textLen; - }; - - int mIndex; - FileType mFileType; - - // the key indicated the start time of the text - KeyedVector<int64_t, TextInfo> mTextVector; - - status_t getNextInSrtFileFormat( - off64_t *offset, int64_t *startTimeUs, TextInfo *info); - status_t readNextLine(off64_t *offset, AString *data); - - status_t scanFile(); - - DISALLOW_EVIL_CONSTRUCTORS(TimedTextParser); -}; - -} // namespace android - -#endif // TIMED_TEXT_PARSER_H_ - diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp index 3014b0bc0548..8c2df88f4253 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.cpp +++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2012 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. @@ -18,399 +18,164 @@ #define LOG_TAG "TimedTextPlayer" #include <utils/Log.h> -#include <binder/IPCThreadState.h> - +#include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/MediaDebug.h> -#include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> -#include <media/stagefright/MediaSource.h> -#include <media/stagefright/MetaData.h> -#include <media/stagefright/MediaBuffer.h> -#include <media/stagefright/FileSource.h> -#include <media/stagefright/Utils.h> +#include <media/MediaPlayerInterface.h> -#include "include/AwesomePlayer.h" #include "TimedTextPlayer.h" -#include "TimedTextParser.h" -#include "TextDescriptions.h" - -namespace android { -struct TimedTextEvent : public TimedEventQueue::Event { - TimedTextEvent( - TimedTextPlayer *player, - void (TimedTextPlayer::*method)()) - : mPlayer(player), - mMethod(method) { - } - -protected: - virtual ~TimedTextEvent() {} - - virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) { - (mPlayer->*mMethod)(); - } +#include "TimedTextDriver.h" +#include "TimedTextSource.h" -private: - TimedTextPlayer *mPlayer; - void (TimedTextPlayer::*mMethod)(); +namespace android { - TimedTextEvent(const TimedTextEvent &); - TimedTextEvent &operator=(const TimedTextEvent &); -}; +static const int64_t kAdjustmentProcessingTimeUs = 100000ll; -TimedTextPlayer::TimedTextPlayer( - AwesomePlayer *observer, - const wp<MediaPlayerBase> &listener, - TimedEventQueue *queue) - : mSource(NULL), - mOutOfBandSource(NULL), - mSeekTimeUs(0), - mStarted(false), - mTextEventPending(false), - mQueue(queue), - mListener(listener), - mObserver(observer), - mTextBuffer(NULL), - mTextParser(NULL), - mTextType(kNoText) { - mTextEvent = new TimedTextEvent(this, &TimedTextPlayer::onTextEvent); +TimedTextPlayer::TimedTextPlayer(const wp<MediaPlayerBase> &listener) + : mListener(listener), + mSource(NULL), + mSendSubtitleGeneration(0) { } TimedTextPlayer::~TimedTextPlayer() { - if (mStarted) { - reset(); + if (mSource != NULL) { + mSource->stop(); + mSource.clear(); + mSource = NULL; } - - mTextTrackVector.clear(); - mTextOutOfBandVector.clear(); } -status_t TimedTextPlayer::start(uint8_t index) { - CHECK(!mStarted); - - if (index >= - mTextTrackVector.size() + mTextOutOfBandVector.size()) { - ALOGE("Incorrect text track index: %d", index); - return BAD_VALUE; - } - - status_t err; - if (index < mTextTrackVector.size()) { // start an in-band text - mSource = mTextTrackVector.itemAt(index); - - err = mSource->start(); - - if (err != OK) { - return err; - } - mTextType = kInBandText; - } else { // start an out-of-band text - OutOfBandText text = - mTextOutOfBandVector.itemAt(index - mTextTrackVector.size()); - - mOutOfBandSource = text.source; - TimedTextParser::FileType fileType = text.type; - - if (mTextParser == NULL) { - mTextParser = new TimedTextParser(); - } - - if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) { - return err; - } - mTextType = kOutOfBandText; - } - - // send sample description format - if ((err = extractAndSendGlobalDescriptions()) != OK) { - return err; - } - - int64_t positionUs; - mObserver->getPosition(&positionUs); - seekTo(positionUs); - - postTextEvent(); - - mStarted = true; - - return OK; +void TimedTextPlayer::start() { + sp<AMessage> msg = new AMessage(kWhatSeek, id()); + msg->setInt64("seekTimeUs", -1); + msg->post(); } void TimedTextPlayer::pause() { - CHECK(mStarted); - - cancelTextEvent(); + (new AMessage(kWhatPause, id()))->post(); } void TimedTextPlayer::resume() { - CHECK(mStarted); - - postTextEvent(); -} - -void TimedTextPlayer::reset() { - CHECK(mStarted); - - // send an empty text to clear the screen - notifyListener(MEDIA_TIMED_TEXT); - - cancelTextEvent(); - - mSeeking = false; - mStarted = false; - - if (mTextType == kInBandText) { - if (mTextBuffer != NULL) { - mTextBuffer->release(); - mTextBuffer = NULL; - } - - if (mSource != NULL) { - mSource->stop(); - mSource.clear(); - mSource = NULL; - } - } else { - if (mTextParser != NULL) { - mTextParser.clear(); - mTextParser = NULL; - } - if (mOutOfBandSource != NULL) { - mOutOfBandSource.clear(); - mOutOfBandSource = NULL; - } - } + start(); } -status_t TimedTextPlayer::seekTo(int64_t time_us) { - Mutex::Autolock autoLock(mLock); - - mSeeking = true; - mSeekTimeUs = time_us; - - postTextEvent(); - - return OK; +void TimedTextPlayer::seekToAsync(int64_t timeUs) { + sp<AMessage> msg = new AMessage(kWhatSeek, id()); + msg->setInt64("seekTimeUs", timeUs); + msg->post(); } -status_t TimedTextPlayer::setTimedTextTrackIndex(int32_t index) { - if (index >= - (int)(mTextTrackVector.size() + mTextOutOfBandVector.size())) { - return BAD_VALUE; - } - - if (mStarted) { - reset(); - } - - if (index >= 0) { - return start(index); - } - return OK; +void TimedTextPlayer::setDataSource(sp<TimedTextSource> source) { + sp<AMessage> msg = new AMessage(kWhatSetSource, id()); + msg->setObject("source", source); + msg->post(); } -void TimedTextPlayer::onTextEvent() { - Mutex::Autolock autoLock(mLock); - - if (!mTextEventPending) { - return; - } - mTextEventPending = false; - - if (mData.dataSize() > 0) { - notifyListener(MEDIA_TIMED_TEXT, &mData); - mData.freeData(); - } - - MediaSource::ReadOptions options; - if (mSeeking) { - options.setSeekTo(mSeekTimeUs, - MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); - mSeeking = false; - - notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen - } - - int64_t positionUs, timeUs; - mObserver->getPosition(&positionUs); - - if (mTextType == kInBandText) { - if (mSource->read(&mTextBuffer, &options) != OK) { - return; +void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatPause: { + mSendSubtitleGeneration++; + break; } - - mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs); - } else { - int64_t endTimeUs; - if (mTextParser->getText( - &mText, &timeUs, &endTimeUs, &options) != OK) { - return; - } - } - - if (timeUs > 0) { - extractAndAppendLocalDescriptions(timeUs); - } - - if (mTextType == kInBandText) { - if (mTextBuffer != NULL) { - mTextBuffer->release(); - mTextBuffer = NULL; + case kWhatSeek: { + int64_t seekTimeUs = 0; + msg->findInt64("seekTimeUs", &seekTimeUs); + if (seekTimeUs < 0) { + sp<MediaPlayerBase> listener = mListener.promote(); + if (listener != NULL) { + int32_t positionMs = 0; + listener->getCurrentPosition(&positionMs); + seekTimeUs = positionMs * 1000ll; + } + } + doSeekAndRead(seekTimeUs); + break; + } + case kWhatSendSubtitle: { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + if (generation != mSendSubtitleGeneration) { + // Drop obsolete msg. + break; + } + sp<RefBase> obj; + msg->findObject("subtitle", &obj); + if (obj != NULL) { + sp<ParcelEvent> parcelEvent; + parcelEvent = static_cast<ParcelEvent*>(obj.get()); + notifyListener(MEDIA_TIMED_TEXT, &(parcelEvent->parcel)); + } else { + notifyListener(MEDIA_TIMED_TEXT); + } + doRead(); + break; + } + case kWhatSetSource: { + sp<RefBase> obj; + msg->findObject("source", &obj); + if (obj == NULL) break; + if (mSource != NULL) { + mSource->stop(); + } + mSource = static_cast<TimedTextSource*>(obj.get()); + mSource->start(); + Parcel parcel; + if (mSource->extractGlobalDescriptions(&parcel) == OK && + parcel.dataSize() > 0) { + notifyListener(MEDIA_TIMED_TEXT, &parcel); + } else { + notifyListener(MEDIA_TIMED_TEXT); + } + break; } - } else { - mText.clear(); - } - - //send the text now - if (timeUs <= positionUs + 100000ll) { - postTextEvent(); - } else { - postTextEvent(timeUs - positionUs - 100000ll); } } -void TimedTextPlayer::postTextEvent(int64_t delayUs) { - if (mTextEventPending) { - return; - } - - mTextEventPending = true; - mQueue->postEventWithDelay(mTextEvent, delayUs < 0 ? 10000 : delayUs); -} - -void TimedTextPlayer::cancelTextEvent() { - mQueue->cancelEvent(mTextEvent->eventID()); - mTextEventPending = false; +void TimedTextPlayer::doSeekAndRead(int64_t seekTimeUs) { + MediaSource::ReadOptions options; + options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + doRead(&options); } -void TimedTextPlayer::addTextSource(sp<MediaSource> source) { - Mutex::Autolock autoLock(mLock); - mTextTrackVector.add(source); +void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) { + int64_t timeUs = 0; + sp<ParcelEvent> parcelEvent = new ParcelEvent(); + mSource->read(&timeUs, &(parcelEvent->parcel), options); + postTextEvent(parcelEvent, timeUs); } -status_t TimedTextPlayer::setParameter(int key, const Parcel &request) { - Mutex::Autolock autoLock(mLock); - - if (key == KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE) { - const String16 uri16 = request.readString16(); - String8 uri = String8(uri16); - KeyedVector<String8, String8> headers; - - // To support local subtitle file only for now - if (strncasecmp("file://", uri.string(), 7)) { - return INVALID_OPERATION; - } - sp<DataSource> dataSource = - DataSource::CreateFromURI(uri, &headers); - status_t err = dataSource->initCheck(); +void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) { + sp<MediaPlayerBase> listener = mListener.promote(); + if (listener != NULL) { + int64_t positionUs, delayUs; + int32_t positionMs = 0; + listener->getCurrentPosition(&positionMs); + positionUs = positionMs * 1000; - if (err != OK) { - return err; - } - - OutOfBandText text; - text.source = dataSource; - if (uri.getPathExtension() == String8(".srt")) { - text.type = TimedTextParser::OUT_OF_BAND_FILE_SRT; + if (timeUs <= positionUs + kAdjustmentProcessingTimeUs) { + delayUs = 0; } else { - return ERROR_UNSUPPORTED; + delayUs = timeUs - positionUs - kAdjustmentProcessingTimeUs; } - - mTextOutOfBandVector.add(text); - - return OK; - } - return INVALID_OPERATION; -} - -void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) { - if (mListener != NULL) { - sp<MediaPlayerBase> listener = mListener.promote(); - - if (listener != NULL) { - if (parcel && (parcel->dataSize() > 0)) { - listener->sendEvent(msg, 0, 0, parcel); - } else { // send an empty timed text to clear the screen - listener->sendEvent(msg); - } + sp<AMessage> msg = new AMessage(kWhatSendSubtitle, id()); + msg->setInt32("generation", mSendSubtitleGeneration); + if (parcel != NULL) { + msg->setObject("subtitle", parcel); } + msg->post(delayUs); } } -// Each text sample consists of a string of text, optionally with sample -// modifier description. The modifier description could specify a new -// text style for the string of text. These descriptions are present only -// if they are needed. This method is used to extract the modifier -// description and append it at the end of the text. -status_t TimedTextPlayer::extractAndAppendLocalDescriptions(int64_t timeUs) { - const void *data; - size_t size = 0; - int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS; - - if (mTextType == kInBandText) { - const char *mime; - CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); - - if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { - flag |= TextDescriptions::IN_BAND_TEXT_3GPP; - data = mTextBuffer->data(); - size = mTextBuffer->size(); - } else { - // support 3GPP only for now - return ERROR_UNSUPPORTED; +void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) { + sp<MediaPlayerBase> listener = mListener.promote(); + if (listener != NULL) { + if (parcel != NULL && (parcel->dataSize() > 0)) { + listener->sendEvent(msg, 0, 0, parcel); + } else { // send an empty timed text to clear the screen + listener->sendEvent(msg); } - } else { - data = mText.c_str(); - size = mText.size(); - flag |= TextDescriptions::OUT_OF_BAND_TEXT_SRT; } - - if ((size > 0) && (flag != TextDescriptions::LOCAL_DESCRIPTIONS)) { - mData.freeData(); - return TextDescriptions::getParcelOfDescriptions( - (const uint8_t *)data, size, flag, timeUs / 1000, &mData); - } - - return OK; } -// To extract and send the global text descriptions for all the text samples -// in the text track or text file. -status_t TimedTextPlayer::extractAndSendGlobalDescriptions() { - const void *data; - size_t size = 0; - int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS; - - if (mTextType == kInBandText) { - const char *mime; - CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); - - // support 3GPP only for now - if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { - uint32_t type; - // get the 'tx3g' box content. This box contains the text descriptions - // used to render the text track - if (!mSource->getFormat()->findData( - kKeyTextFormatData, &type, &data, &size)) { - return ERROR_MALFORMED; - } - - flag |= TextDescriptions::IN_BAND_TEXT_3GPP; - } - } - - if ((size > 0) && (flag != TextDescriptions::GLOBAL_DESCRIPTIONS)) { - Parcel parcel; - if (TextDescriptions::getParcelOfDescriptions( - (const uint8_t *)data, size, flag, 0, &parcel) == OK) { - if (parcel.dataSize() > 0) { - notifyListener(MEDIA_TIMED_TEXT, &parcel); - } - } - } - - return OK; -} -} +} // namespace android diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h index a744db59417b..837beeb0ee3e 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.h +++ b/media/libstagefright/timedtext/TimedTextPlayer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2012 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. @@ -15,99 +15,61 @@ */ #ifndef TIMEDTEXT_PLAYER_H_ - #define TIMEDTEXT_PLAYER_H_ -#include <media/MediaPlayerInterface.h> +#include <binder/Parcel.h> #include <media/stagefright/foundation/ABase.h> -#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/MediaSource.h> +#include <utils/RefBase.h> -#include "include/TimedEventQueue.h" -#include "TimedTextParser.h" +#include "TimedTextSource.h" namespace android { -class MediaSource; -class AwesomePlayer; -class MediaBuffer; +class AMessage; +class MediaPlayerBase; +class TimedTextDriver; +class TimedTextSource; -class TimedTextPlayer { +class TimedTextPlayer : public AHandler { public: - TimedTextPlayer(AwesomePlayer *observer, - const wp<MediaPlayerBase> &listener, - TimedEventQueue *queue); + TimedTextPlayer(const wp<MediaPlayerBase> &listener); virtual ~TimedTextPlayer(); - // index: the index of the text track which will - // be turned on - status_t start(uint8_t index); - + void start(); void pause(); - void resume(); + void seekToAsync(int64_t timeUs); + void setDataSource(sp<TimedTextSource> source); - status_t seekTo(int64_t time_us); - - void addTextSource(sp<MediaSource> source); - - status_t setTimedTextTrackIndex(int32_t index); - status_t setParameter(int key, const Parcel &request); +protected: + virtual void onMessageReceived(const sp<AMessage> &msg); private: - enum TextType { - kNoText = 0, - kInBandText = 1, - kOutOfBandText = 2, + enum { + kWhatPause = 'paus', + kWhatSeek = 'seek', + kWhatSendSubtitle = 'send', + kWhatSetSource = 'ssrc', }; - Mutex mLock; - - sp<MediaSource> mSource; - sp<DataSource> mOutOfBandSource; - - bool mSeeking; - int64_t mSeekTimeUs; - - bool mStarted; - - sp<TimedEventQueue::Event> mTextEvent; - bool mTextEventPending; - - TimedEventQueue *mQueue; - - wp<MediaPlayerBase> mListener; - AwesomePlayer *mObserver; - - MediaBuffer *mTextBuffer; - Parcel mData; - - // for in-band timed text - Vector<sp<MediaSource> > mTextTrackVector; - - // for out-of-band timed text - struct OutOfBandText { - TimedTextParser::FileType type; - sp<DataSource> source; + // To add Parcel into an AMessage as an object, it should be 'RefBase'. + struct ParcelEvent : public RefBase { + Parcel parcel; }; - Vector<OutOfBandText > mTextOutOfBandVector; - sp<TimedTextParser> mTextParser; - AString mText; - - TextType mTextType; - - void reset(); + wp<MediaPlayerBase> mListener; + sp<TimedTextSource> mSource; + int32_t mSendSubtitleGeneration; + void doSeekAndRead(int64_t seekTimeUs); + void doRead(MediaSource::ReadOptions* options = NULL); void onTextEvent(); - void postTextEvent(int64_t delayUs = -1); - void cancelTextEvent(); - + void postTextEvent(const sp<ParcelEvent>& parcel = NULL, int64_t timeUs = -1); void notifyListener(int msg, const Parcel *parcel = NULL); - status_t extractAndAppendLocalDescriptions(int64_t timeUs); - status_t extractAndSendGlobalDescriptions(); - DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer); }; diff --git a/media/libstagefright/timedtext/TimedTextParser.cpp b/media/libstagefright/timedtext/TimedTextSRTSource.cpp index caea0a4cc6ed..3752d34469a5 100644 --- a/media/libstagefright/timedtext/TimedTextParser.cpp +++ b/media/libstagefright/timedtext/TimedTextSRTSource.cpp @@ -1,5 +1,5 @@ -/* - * Copyright (C) 2011 The Android Open Source Project + /* + * Copyright (C) 2012 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. @@ -14,133 +14,106 @@ * limitations under the License. */ -#include "TimedTextParser.h" +//#define LOG_NDEBUG 0 +#define LOG_TAG "TimedTextSRTSource" +#include <utils/Log.h> + +#include <binder/Parcel.h> +#include <media/stagefright/foundation/AString.h> #include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> + +#include "TimedTextSRTSource.h" +#include "TextDescriptions.h" namespace android { -TimedTextParser::TimedTextParser() - : mDataSource(NULL), - mOffset(0), - mIndex(0) { +TimedTextSRTSource::TimedTextSRTSource(const sp<DataSource>& dataSource) + : mSource(dataSource), + mIndex(0) { } -TimedTextParser::~TimedTextParser() { - reset(); +TimedTextSRTSource::~TimedTextSRTSource() { } -status_t TimedTextParser::init( - const sp<DataSource> &dataSource, FileType fileType) { - mDataSource = dataSource; - mFileType = fileType; - - status_t err; - if ((err = scanFile()) != OK) { +status_t TimedTextSRTSource::start() { + status_t err = scanFile(); + if (err != OK) { reset(); - return err; } - - return OK; + return err; } -void TimedTextParser::reset() { - mDataSource.clear(); +void TimedTextSRTSource::reset() { mTextVector.clear(); - mOffset = 0; mIndex = 0; } -// scan the text file to get start/stop time and the -// offset of each piece of text content -status_t TimedTextParser::scanFile() { - if (mFileType != OUT_OF_BAND_FILE_SRT) { - return ERROR_UNSUPPORTED; +status_t TimedTextSRTSource::stop() { + reset(); + return OK; +} + +status_t TimedTextSRTSource::read( + int64_t *timeUs, + Parcel *parcel, + const MediaSource::ReadOptions *options) { + int64_t endTimeUs; + AString text; + status_t err = getText(options, &text, timeUs, &endTimeUs); + if (err != OK) { + return err; + } + + if (*timeUs > 0) { + extractAndAppendLocalDescriptions(*timeUs, text, parcel); } + return OK; +} +status_t TimedTextSRTSource::scanFile() { off64_t offset = 0; int64_t startTimeUs; bool endOfFile = false; while (!endOfFile) { TextInfo info; - status_t err = getNextInSrtFileFormat(&offset, &startTimeUs, &info); - - if (err != OK) { - if (err == ERROR_END_OF_STREAM) { + status_t err = getNextSubtitleInfo(&offset, &startTimeUs, &info); + switch (err) { + case OK: + mTextVector.add(startTimeUs, info); + break; + case ERROR_END_OF_STREAM: endOfFile = true; - } else { + break; + default: return err; - } - } else { - mTextVector.add(startTimeUs, info); } } - if (mTextVector.isEmpty()) { return ERROR_MALFORMED; } return OK; } -// read one line started from *offset and store it into data. -status_t TimedTextParser::readNextLine(off64_t *offset, AString *data) { - char character; - - data->clear(); - - while (true) { - ssize_t err; - if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) { - if (err == 0) { - return ERROR_END_OF_STREAM; - } - return ERROR_IO; - } - - (*offset) ++; - - // a line could end with CR, LF or CR + LF - if (character == 10) { - break; - } else if (character == 13) { - if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) { - if (err == 0) { // end of the stream - return OK; - } - return ERROR_IO; - } - - (*offset) ++; - - if (character != 10) { - (*offset) --; - } - break; - } - - data->append(character); - } - - return OK; -} - /* SRT format: - * Subtitle number - * Start time --> End time - * Text of subtitle (one or more lines) - * Blank lines + * Subtitle number + * Start time --> End time + * Text of subtitle (one or more lines) + * Blank lines * * .srt file example: - * 1 - * 00:00:20,000 --> 00:00:24,400 - * Altocumulus clouds occur between six thousand + * 1 + * 00:00:20,000 --> 00:00:24,400 + * Altocumulus clouds occr between six thousand * - * 2 - * 00:00:24,600 --> 00:00:27,800 - * and twenty thousand feet above ground level. + * 2 + * 00:00:24,600 --> 00:00:27,800 + * and twenty thousand feet above ground level. */ -status_t TimedTextParser::getNextInSrtFileFormat( - off64_t *offset, int64_t *startTimeUs, TextInfo *info) { +status_t TimedTextSRTSource::getNextSubtitleInfo( + off64_t *offset, int64_t *startTimeUs, TextInfo *info) { AString data; status_t err; @@ -150,10 +123,9 @@ status_t TimedTextParser::getNextInSrtFileFormat( return err; } data.trim(); - } while(data.empty()); + } while (data.empty()); // Just ignore the first non-blank line which is subtitle sequence number. - if ((err = readNextLine(offset, &data)) != OK) { return err; } @@ -161,7 +133,7 @@ status_t TimedTextParser::getNextInSrtFileFormat( // the start time format is: hours:minutes:seconds,milliseconds // 00:00:24,600 --> 00:00:27,800 if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d", - &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) { + &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) { return ERROR_MALFORMED; } @@ -172,7 +144,6 @@ status_t TimedTextParser::getNextInSrtFileFormat( } info->offset = *offset; - bool needMoreData = true; while (needMoreData) { if ((err = readNextLine(offset, &data)) != OK) { @@ -191,25 +162,56 @@ status_t TimedTextParser::getNextInSrtFileFormat( } } } - info->textLen = *offset - info->offset; - return OK; } -status_t TimedTextParser::getText( - AString *text, int64_t *startTimeUs, int64_t *endTimeUs, - const MediaSource::ReadOptions *options) { - Mutex::Autolock autoLock(mLock); +status_t TimedTextSRTSource::readNextLine(off64_t *offset, AString *data) { + data->clear(); + while (true) { + ssize_t readSize; + char character; + if ((readSize = mSource->readAt(*offset, &character, 1)) < 1) { + if (readSize == 0) { + return ERROR_END_OF_STREAM; + } + return ERROR_IO; + } - text->clear(); + (*offset)++; + // a line could end with CR, LF or CR + LF + if (character == 10) { + break; + } else if (character == 13) { + if ((readSize = mSource->readAt(*offset, &character, 1)) < 1) { + if (readSize == 0) { // end of the stream + return OK; + } + return ERROR_IO; + } + + (*offset)++; + if (character != 10) { + (*offset)--; + } + break; + } + data->append(character); + } + return OK; +} + +status_t TimedTextSRTSource::getText( + const MediaSource::ReadOptions *options, + AString *text, int64_t *startTimeUs, int64_t *endTimeUs) { + text->clear(); int64_t seekTimeUs; MediaSource::ReadOptions::SeekMode mode; - if (options && options->getSeekTo(&seekTimeUs, &mode)) { - int64_t lastEndTimeUs = mTextVector.valueAt(mTextVector.size() - 1).endTimeUs; + if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) { + int64_t lastEndTimeUs = + mTextVector.valueAt(mTextVector.size() - 1).endTimeUs; int64_t firstStartTimeUs = mTextVector.keyAt(0); - if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) { return ERROR_OUT_OF_RANGE; } else if (seekTimeUs < firstStartTimeUs) { @@ -232,31 +234,42 @@ status_t TimedTextParser::getText( low = mid + 1; } else { if ((high == mid + 1) - && (seekTimeUs < mTextVector.keyAt(high))) { + && (seekTimeUs < mTextVector.keyAt(high))) { break; } high = mid - 1; } } - mIndex = mid; } } - - TextInfo info = mTextVector.valueAt(mIndex); + const TextInfo &info = mTextVector.valueAt(mIndex); *startTimeUs = mTextVector.keyAt(mIndex); *endTimeUs = info.endTimeUs; - mIndex ++; + mIndex++; char *str = new char[info.textLen]; - if (mDataSource->readAt(info.offset, str, info.textLen) < info.textLen) { + if (mSource->readAt(info.offset, str, info.textLen) < info.textLen) { delete[] str; return ERROR_IO; } - text->append(str, info.textLen); delete[] str; return OK; } +status_t TimedTextSRTSource::extractAndAppendLocalDescriptions( + int64_t timeUs, const AString &text, Parcel *parcel) { + const void *data = text.c_str(); + size_t size = text.size(); + int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS | + TextDescriptions::OUT_OF_BAND_TEXT_SRT; + + if (size > 0) { + return TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, timeUs / 1000, parcel); + } + return OK; +} + } // namespace android diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.h b/media/libstagefright/timedtext/TimedTextSRTSource.h new file mode 100644 index 000000000000..a0734d9b21b7 --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextSRTSource.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIMED_TEXT_SRT_SOURCE_H_ +#define TIMED_TEXT_SRT_SOURCE_H_ + +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <utils/Compat.h> // off64_t + +#include "TimedTextSource.h" + +namespace android { + +class AString; +class DataSource; +class MediaBuffer; +class Parcel; + +class TimedTextSRTSource : public TimedTextSource { + public: + TimedTextSRTSource(const sp<DataSource>& dataSource); + virtual status_t start(); + virtual status_t stop(); + virtual status_t read( + int64_t *timeUs, + Parcel *parcel, + const MediaSource::ReadOptions *options = NULL); + + protected: + virtual ~TimedTextSRTSource(); + + private: + sp<DataSource> mSource; + + struct TextInfo { + int64_t endTimeUs; + // The offset of the text in the original file. + off64_t offset; + int textLen; + }; + + int mIndex; + KeyedVector<int64_t, TextInfo> mTextVector; + + void reset(); + status_t scanFile(); + status_t getNextSubtitleInfo( + off64_t *offset, int64_t *startTimeUs, TextInfo *info); + status_t readNextLine(off64_t *offset, AString *data); + status_t getText( + const MediaSource::ReadOptions *options, + AString *text, int64_t *startTimeUs, int64_t *endTimeUs); + status_t extractAndAppendLocalDescriptions( + int64_t timeUs, const AString &text, Parcel *parcel); + + DISALLOW_EVIL_CONSTRUCTORS(TimedTextSRTSource); +}; + +} // namespace android + +#endif // TIMED_TEXT_SRT_SOURCE_H_ diff --git a/media/libstagefright/timedtext/TimedTextSource.cpp b/media/libstagefright/timedtext/TimedTextSource.cpp new file mode 100644 index 000000000000..9efe67c45af0 --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextSource.cpp @@ -0,0 +1,53 @@ + /* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "TimedTextSource" +#include <utils/Log.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaSource.h> + +#include "TimedTextSource.h" + +#include "TimedTextInBandSource.h" +#include "TimedTextSRTSource.h" + +namespace android { + +// static +sp<TimedTextSource> TimedTextSource::CreateTimedTextSource( + const sp<MediaSource>& mediaSource) { + return new TimedTextInBandSource(mediaSource); +} + +// static +sp<TimedTextSource> TimedTextSource::CreateTimedTextSource( + const sp<DataSource>& dataSource, FileType filetype) { + switch(filetype) { + case OUT_OF_BAND_FILE_SRT: + return new TimedTextSRTSource(dataSource); + case OUT_OF_BAND_FILE_SMI: + // TODO: Implement for SMI. + ALOGE("Supporting SMI is not implemented yet"); + break; + default: + ALOGE("Undefined subtitle format. : %d", filetype); + } + return NULL; +} + +} // namespace android diff --git a/media/libstagefright/timedtext/TimedTextSource.h b/media/libstagefright/timedtext/TimedTextSource.h new file mode 100644 index 000000000000..06bae7150db4 --- /dev/null +++ b/media/libstagefright/timedtext/TimedTextSource.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIMED_TEXT_SOURCE_H_ +#define TIMED_TEXT_SOURCE_H_ + +#include <media/stagefright/foundation/ABase.h> // for DISALLOW_XXX macro. +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> // for MediaSource::ReadOptions +#include <utils/RefBase.h> + +namespace android { + +class DataSource; +class Parcel; + +class TimedTextSource : public RefBase { + public: + enum FileType { + OUT_OF_BAND_FILE_SRT = 1, + OUT_OF_BAND_FILE_SMI = 2, + }; + static sp<TimedTextSource> CreateTimedTextSource( + const sp<MediaSource>& source); + static sp<TimedTextSource> CreateTimedTextSource( + const sp<DataSource>& source, FileType filetype); + TimedTextSource() {} + virtual status_t start() = 0; + virtual status_t stop() = 0; + // Returns subtitle parcel and its start time. + virtual status_t read( + int64_t *timeUs, + Parcel *parcel, + const MediaSource::ReadOptions *options = NULL) = 0; + virtual status_t extractGlobalDescriptions(Parcel *parcel) { + return INVALID_OPERATION; + } + + protected: + virtual ~TimedTextSource() { } + + private: + DISALLOW_EVIL_CONSTRUCTORS(TimedTextSource); +}; + +} // namespace android + +#endif // TIMED_TEXT_SOURCE_H_ diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index 2fc612522f7a..06be2ef1147f 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -253,6 +253,19 @@ void Loader::init_api(void* dso, if (f == NULL) { //ALOGD("%s", name); f = (__eglMustCastToProperFunctionPointerType)gl_unimplemented; + + /* + * GL_EXT_debug_label is special, we always report it as + * supported, it's handled by GLES_trace. If GLES_trace is not + * enabled, then these are no-ops. + */ + if (!strcmp(name, "glInsertEventMarkerEXT")) { + f = (__eglMustCastToProperFunctionPointerType)gl_noop; + } else if (!strcmp(name, "glPushGroupMarkerEXT")) { + f = (__eglMustCastToProperFunctionPointerType)gl_noop; + } else if (!strcmp(name, "glPopGroupMarkerEXT")) { + f = (__eglMustCastToProperFunctionPointerType)gl_noop; + } } *curr++ = f; api++; diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp index e053589a878b..83933e525211 100644 --- a/opengl/libs/EGL/egl.cpp +++ b/opengl/libs/EGL/egl.cpp @@ -233,6 +233,26 @@ EGLImageKHR egl_get_image_for_current_context(EGLImageKHR image) // ---------------------------------------------------------------------------- +const GLubyte * egl_get_string_for_current_context(GLenum name) { + // NOTE: returning NULL here will fall-back to the default + // implementation. + + EGLContext context = egl_tls_t::getContext(); + if (context == EGL_NO_CONTEXT) + return NULL; + + egl_context_t const * const c = get_context(context); + if (c == NULL) // this should never happen, by construction + return NULL; + + if (name != GL_EXTENSIONS) + return NULL; + + return (const GLubyte *)c->gl_extensions.string(); +} + +// ---------------------------------------------------------------------------- + // this mutex protects: // d->disp[] // egl_init_drivers_locked() @@ -290,6 +310,9 @@ void gl_unimplemented() { ALOGE("called unimplemented OpenGL ES API"); } +void gl_noop() { +} + // ---------------------------------------------------------------------------- #if USE_FAST_TLS_KEY diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp index 8b37da56c9e7..73aab26cc76c 100644 --- a/opengl/libs/EGL/eglApi.cpp +++ b/opengl/libs/EGL/eglApi.cpp @@ -566,27 +566,6 @@ EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx) return result; } -static void loseCurrent(egl_context_t * cur_c) -{ - if (cur_c) { - egl_surface_t * cur_r = get_surface(cur_c->read); - egl_surface_t * cur_d = get_surface(cur_c->draw); - - // by construction, these are either 0 or valid (possibly terminated) - // it should be impossible for these to be invalid - ContextRef _cur_c(cur_c); - SurfaceRef _cur_r(cur_r); - SurfaceRef _cur_d(cur_d); - - cur_c->read = NULL; - cur_c->draw = NULL; - - _cur_c.release(); - _cur_r.release(); - _cur_d.release(); - } -} - EGLBoolean eglMakeCurrent( EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { @@ -663,21 +642,13 @@ EGLBoolean eglMakeCurrent( EGLDisplay dpy, EGLSurface draw, impl_read = r->surface; } - EGLBoolean result; - if (c) { - result = c->cnx->egl.eglMakeCurrent( - dp->disp[c->impl].dpy, impl_draw, impl_read, impl_ctx); - } else { - result = cur_c->cnx->egl.eglMakeCurrent( - dp->disp[cur_c->impl].dpy, impl_draw, impl_read, impl_ctx); - } + EGLBoolean result = const_cast<egl_display_t*>(dp)->makeCurrent(c, cur_c, + draw, read, ctx, + impl_draw, impl_read, impl_ctx); if (result == EGL_TRUE) { - - loseCurrent(cur_c); - - if (ctx != EGL_NO_CONTEXT) { + if (c) { setGLHooksThreadSpecific(c->cnx->hooks[c->version]); egl_tls_t::setContext(ctx); #if EGL_TRACE @@ -687,8 +658,6 @@ EGLBoolean eglMakeCurrent( EGLDisplay dpy, EGLSurface draw, _c.acquire(); _r.acquire(); _d.acquire(); - c->read = read; - c->draw = draw; } else { setGLHooksThreadSpecific(&gHooksNoContext); egl_tls_t::setContext(EGL_NO_CONTEXT); @@ -924,7 +893,8 @@ __eglMustCastToProperFunctionPointerType eglGetProcAddress(const char *procname) cnx->hooks[GLESv1_INDEX]->ext.extensions[slot] = cnx->hooks[GLESv2_INDEX]->ext.extensions[slot] = #if EGL_TRACE - debugHooks->ext.extensions[slot] = gHooksTrace.ext.extensions[slot] = + debugHooks->ext.extensions[slot] = + gHooksTrace.ext.extensions[slot] = #endif cnx->egl.eglGetProcAddress(procname); } @@ -1180,7 +1150,7 @@ EGLBoolean eglReleaseThread(void) clearError(); // If there is context bound to the thread, release it - loseCurrent(get_context(getContext())); + egl_display_t::loseCurrent(get_context(getContext())); for (int i=0 ; i<IMPL_NUM_IMPLEMENTATIONS ; i++) { egl_connection_t* const cnx = &gEGLImpl[i]; diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp index 53eaf9a1366a..6b2ae51135f1 100644 --- a/opengl/libs/EGL/egl_display.cpp +++ b/opengl/libs/EGL/egl_display.cpp @@ -342,6 +342,78 @@ EGLBoolean egl_display_t::terminate() { return res; } +void egl_display_t::loseCurrent(egl_context_t * cur_c) +{ + if (cur_c) { + egl_display_t* display = cur_c->getDisplay(); + if (display) { + display->loseCurrentImpl(cur_c); + } + } +} + +void egl_display_t::loseCurrentImpl(egl_context_t * cur_c) +{ + // by construction, these are either 0 or valid (possibly terminated) + // it should be impossible for these to be invalid + ContextRef _cur_c(cur_c); + SurfaceRef _cur_r(cur_c ? get_surface(cur_c->read) : NULL); + SurfaceRef _cur_d(cur_c ? get_surface(cur_c->draw) : NULL); + + { // scope for the lock + Mutex::Autolock _l(lock); + cur_c->onLooseCurrent(); + + } + + // This cannot be called with the lock held because it might end-up + // calling back into EGL (in particular when a surface is destroyed + // it calls ANativeWindow::disconnect + _cur_c.release(); + _cur_r.release(); + _cur_d.release(); +} + +EGLBoolean egl_display_t::makeCurrent(egl_context_t* c, egl_context_t* cur_c, + EGLSurface draw, EGLSurface read, EGLContext ctx, + EGLSurface impl_draw, EGLSurface impl_read, EGLContext impl_ctx) +{ + EGLBoolean result; + + // by construction, these are either 0 or valid (possibly terminated) + // it should be impossible for these to be invalid + ContextRef _cur_c(cur_c); + SurfaceRef _cur_r(cur_c ? get_surface(cur_c->read) : NULL); + SurfaceRef _cur_d(cur_c ? get_surface(cur_c->draw) : NULL); + + { // scope for the lock + Mutex::Autolock _l(lock); + if (c) { + result = c->cnx->egl.eglMakeCurrent( + disp[c->impl].dpy, impl_draw, impl_read, impl_ctx); + if (result == EGL_TRUE) { + c->onMakeCurrent(draw, read); + } + } else { + result = cur_c->cnx->egl.eglMakeCurrent( + disp[cur_c->impl].dpy, impl_draw, impl_read, impl_ctx); + if (result == EGL_TRUE) { + cur_c->onLooseCurrent(); + } + } + } + + if (result == EGL_TRUE) { + // This cannot be called with the lock held because it might end-up + // calling back into EGL (in particular when a surface is destroyed + // it calls ANativeWindow::disconnect + _cur_c.release(); + _cur_r.release(); + _cur_d.release(); + } + + return result; +} // ---------------------------------------------------------------------------- }; // namespace android diff --git a/opengl/libs/EGL/egl_display.h b/opengl/libs/EGL/egl_display.h index 042ae07a91f6..f3c4ddfcabc3 100644 --- a/opengl/libs/EGL/egl_display.h +++ b/opengl/libs/EGL/egl_display.h @@ -39,6 +39,7 @@ namespace android { // ---------------------------------------------------------------------------- class egl_object_t; +class egl_context_t; class egl_connection_t; // ---------------------------------------------------------------------------- @@ -63,6 +64,7 @@ struct egl_config_t { class EGLAPI egl_display_t { // marked as EGLAPI for testing purposes static egl_display_t sDisplay[NUM_DISPLAYS]; EGLDisplay getDisplay(EGLNativeDisplayType display); + void loseCurrentImpl(egl_context_t * cur_c); public: enum { @@ -84,10 +86,14 @@ public: // add reference to this object. returns true if this is a valid object. bool getObject(egl_object_t* object) const; - static egl_display_t* get(EGLDisplay dpy); static EGLDisplay getFromNativeDisplay(EGLNativeDisplayType disp); + EGLBoolean makeCurrent(egl_context_t* c, egl_context_t* cur_c, + EGLSurface draw, EGLSurface read, EGLContext ctx, + EGLSurface impl_draw, EGLSurface impl_read, EGLContext impl_ctx); + static void loseCurrent(egl_context_t * cur_c); + inline bool isReady() const { return (refs > 0); } inline bool isValid() const { return magic == '_dpy'; } inline bool isAlive() const { return isValid(); } diff --git a/opengl/libs/EGL/egl_object.cpp b/opengl/libs/EGL/egl_object.cpp index 26e8c3eb10c4..b660c53142ac 100644 --- a/opengl/libs/EGL/egl_object.cpp +++ b/opengl/libs/EGL/egl_object.cpp @@ -62,5 +62,41 @@ bool egl_object_t::get(egl_display_t const* display, egl_object_t* object) { } // ---------------------------------------------------------------------------- + +egl_context_t::egl_context_t(EGLDisplay dpy, EGLContext context, EGLConfig config, + int impl, egl_connection_t const* cnx, int version) : + egl_object_t(get_display(dpy)), dpy(dpy), context(context), + config(config), read(0), draw(0), impl(impl), cnx(cnx), + version(version) +{ +} + +void egl_context_t::onLooseCurrent() { + read = NULL; + draw = NULL; +} + +void egl_context_t::onMakeCurrent(EGLSurface draw, EGLSurface read) { + this->read = read; + this->draw = draw; + + /* + * Here we cache the GL_EXTENSIONS string for this context and we + * add the extensions always handled by the wrapper + */ + + if (gl_extensions.isEmpty()) { + // call the implementation's glGetString(GL_EXTENSIONS) + const char* exts = (const char *)gEGLImpl[impl].hooks[version]->gl.glGetString(GL_EXTENSIONS); + gl_extensions.setTo(exts); + if (gl_extensions.find("GL_EXT_debug_marker") < 0) { + String8 temp("GL_EXT_debug_marker "); + temp.append(gl_extensions); + gl_extensions.setTo(temp); + } + } +} + +// ---------------------------------------------------------------------------- }; // namespace android // ---------------------------------------------------------------------------- diff --git a/opengl/libs/EGL/egl_object.h b/opengl/libs/EGL/egl_object.h index 7106fa5ebf56..abd4cbb962a6 100644 --- a/opengl/libs/EGL/egl_object.h +++ b/opengl/libs/EGL/egl_object.h @@ -28,6 +28,7 @@ #include <GLES/glext.h> #include <utils/threads.h> +#include <utils/String8.h> #include <system/window.h> @@ -158,11 +159,11 @@ public: typedef egl_object_t::LocalRef<egl_context_t, EGLContext> Ref; egl_context_t(EGLDisplay dpy, EGLContext context, EGLConfig config, - int impl, egl_connection_t const* cnx, int version) : - egl_object_t(get_display(dpy)), dpy(dpy), context(context), - config(config), read(0), draw(0), impl(impl), cnx(cnx), - version(version) { - } + int impl, egl_connection_t const* cnx, int version); + + void onLooseCurrent(); + void onMakeCurrent(EGLSurface draw, EGLSurface read); + EGLDisplay dpy; EGLContext context; EGLConfig config; @@ -171,6 +172,7 @@ public: int impl; egl_connection_t const* cnx; int version; + String8 gl_extensions; }; class egl_image_t: public egl_object_t { diff --git a/opengl/libs/EGL/egldefs.h b/opengl/libs/EGL/egldefs.h index 107acd9b12b1..ff20957f3e41 100644 --- a/opengl/libs/EGL/egldefs.h +++ b/opengl/libs/EGL/egldefs.h @@ -58,6 +58,7 @@ extern gl_hooks_t gHooks[2][IMPL_NUM_IMPLEMENTATIONS]; extern gl_hooks_t gHooksNoContext; extern pthread_key_t gGLWrapperKey; extern "C" void gl_unimplemented(); +extern "C" void gl_noop(); extern char const * const gl_names[]; extern char const * const egl_names[]; diff --git a/opengl/libs/EGL/getProcAddress.cpp b/opengl/libs/EGL/getProcAddress.cpp index f89c865761a4..8dcf38dbeccb 100644 --- a/opengl/libs/EGL/getProcAddress.cpp +++ b/opengl/libs/EGL/getProcAddress.cpp @@ -82,23 +82,41 @@ namespace android { #endif + #define GL_EXTENSION_LIST(name) \ - name(0) name(1) name(2) name(3) \ - name(4) name(5) name(6) name(7) \ - name(8) name(9) name(10) name(11) \ - name(12) name(13) name(14) name(15) \ - name(16) name(17) name(18) name(19) \ - name(20) name(21) name(22) name(23) \ - name(24) name(25) name(26) name(27) \ - name(28) name(29) name(30) name(31) \ - name(32) name(33) name(34) name(35) \ - name(36) name(37) name(38) name(39) \ - name(40) name(41) name(42) name(43) \ - name(44) name(45) name(46) name(47) \ - name(48) name(49) name(50) name(51) \ - name(52) name(53) name(54) name(55) \ - name(56) name(57) name(58) name(59) \ - name(60) name(61) name(62) name(63) + name(0) name(1) name(2) name(3) name(4) name(5) name(6) name(7) \ + name(8) name(9) name(10) name(11) name(12) name(13) name(14) name(15) \ + name(16) name(17) name(18) name(19) name(20) name(21) name(22) name(23) \ + name(24) name(25) name(26) name(27) name(28) name(29) name(30) name(31) \ + name(32) name(33) name(34) name(35) name(36) name(37) name(38) name(39) \ + name(40) name(41) name(42) name(43) name(44) name(45) name(46) name(47) \ + name(48) name(49) name(50) name(51) name(52) name(53) name(54) name(55) \ + name(56) name(57) name(58) name(59) name(60) name(61) name(62) name(63) \ + name(64) name(65) name(66) name(67) name(68) name(69) name(70) name(71) \ + name(72) name(73) name(74) name(75) name(76) name(77) name(78) name(79) \ + name(80) name(81) name(82) name(83) name(84) name(85) name(86) name(87) \ + name(88) name(89) name(90) name(91) name(92) name(93) name(94) name(95) \ + name(96) name(97) name(98) name(99) \ + name(100) name(101) name(102) name(103) name(104) name(105) name(106) name(107) \ + name(108) name(109) name(110) name(111) name(112) name(113) name(114) name(115) \ + name(116) name(117) name(118) name(119) name(120) name(121) name(122) name(123) \ + name(124) name(125) name(126) name(127) name(128) name(129) name(130) name(131) \ + name(132) name(133) name(134) name(135) name(136) name(137) name(138) name(139) \ + name(140) name(141) name(142) name(143) name(144) name(145) name(146) name(147) \ + name(148) name(149) name(150) name(151) name(152) name(153) name(154) name(155) \ + name(156) name(157) name(158) name(159) name(160) name(161) name(162) name(163) \ + name(164) name(165) name(166) name(167) name(168) name(169) name(170) name(171) \ + name(172) name(173) name(174) name(175) name(176) name(177) name(178) name(179) \ + name(180) name(181) name(182) name(183) name(184) name(185) name(186) name(187) \ + name(188) name(189) name(190) name(191) name(192) name(193) name(194) name(195) \ + name(196) name(197) name(198) name(199) \ + name(200) name(201) name(202) name(203) name(204) name(205) name(206) name(207) \ + name(208) name(209) name(210) name(211) name(212) name(213) name(214) name(215) \ + name(216) name(217) name(218) name(219) name(220) name(221) name(222) name(223) \ + name(224) name(225) name(226) name(227) name(228) name(229) name(230) name(231) \ + name(232) name(233) name(234) name(235) name(236) name(237) name(238) name(239) \ + name(240) name(241) name(242) name(243) name(244) name(245) name(246) name(247) \ + name(248) name(249) name(250) name(251) name(252) name(253) name(254) name(255) GL_EXTENSION_LIST( GL_EXTENSION ) diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp index df22b968a1b7..79aa3cd59eeb 100644 --- a/opengl/libs/GLES2/gl2.cpp +++ b/opengl/libs/GLES2/gl2.cpp @@ -110,6 +110,20 @@ extern "C" { #undef CALL_GL_API #undef CALL_GL_API_RETURN +/* + * glGetString() is special because we expose some extensions in the wrapper + */ + +extern "C" const GLubyte * __glGetString(GLenum name); + +const GLubyte * glGetString(GLenum name) +{ + const GLubyte * ret = egl_get_string_for_current_context(name); + if (ret == NULL) { + ret = __glGetString(name); + } + return ret; +} /* * These GL calls are special because they need to EGL to retrieve some diff --git a/opengl/libs/GLES2/gl2_api.in b/opengl/libs/GLES2/gl2_api.in index 5164450ba99f..9a89a52ca1ad 100644 --- a/opengl/libs/GLES2/gl2_api.in +++ b/opengl/libs/GLES2/gl2_api.in @@ -211,7 +211,7 @@ void API_ENTRY(glGetShaderPrecisionFormat)(GLenum shadertype, GLenum precisionty void API_ENTRY(glGetShaderSource)(GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* source) { CALL_GL_API(glGetShaderSource, shader, bufsize, length, source); } -const GLubyte* API_ENTRY(glGetString)(GLenum name) { +const GLubyte* API_ENTRY(__glGetString)(GLenum name) { CALL_GL_API_RETURN(glGetString, name); } void API_ENTRY(glGetTexParameterfv)(GLenum target, GLenum pname, GLfloat* params) { diff --git a/opengl/libs/GLES_CM/gl.cpp b/opengl/libs/GLES_CM/gl.cpp index 2d31a35177c2..adeaa5b8233d 100644 --- a/opengl/libs/GLES_CM/gl.cpp +++ b/opengl/libs/GLES_CM/gl.cpp @@ -165,6 +165,20 @@ extern "C" { #undef CALL_GL_API #undef CALL_GL_API_RETURN +/* + * glGetString() is special because we expose some extensions in the wrapper + */ + +extern "C" const GLubyte * __glGetString(GLenum name); + +const GLubyte * glGetString(GLenum name) +{ + const GLubyte * ret = egl_get_string_for_current_context(name); + if (ret == NULL) { + ret = __glGetString(name); + } + return ret; +} /* * These GL calls are special because they need to EGL to retrieve some diff --git a/opengl/libs/GLES_CM/gl_api.in b/opengl/libs/GLES_CM/gl_api.in index 7f20c4fda493..c8f6b0c4d6c3 100644 --- a/opengl/libs/GLES_CM/gl_api.in +++ b/opengl/libs/GLES_CM/gl_api.in @@ -262,7 +262,7 @@ void API_ENTRY(glGetMaterialxv)(GLenum face, GLenum pname, GLfixed *params) { void API_ENTRY(glGetPointerv)(GLenum pname, GLvoid **params) { CALL_GL_API(glGetPointerv, pname, params); } -const GLubyte * API_ENTRY(glGetString)(GLenum name) { +const GLubyte * API_ENTRY(__glGetString)(GLenum name) { CALL_GL_API_RETURN(glGetString, name); } void API_ENTRY(glGetTexEnviv)(GLenum env, GLenum pname, GLint *params) { diff --git a/opengl/libs/GLES_trace/src/gltrace_eglapi.cpp b/opengl/libs/GLES_trace/src/gltrace_eglapi.cpp index c237d75fcd4f..c442153794a3 100644 --- a/opengl/libs/GLES_trace/src/gltrace_eglapi.cpp +++ b/opengl/libs/GLES_trace/src/gltrace_eglapi.cpp @@ -77,12 +77,10 @@ static void *commandReceiveTask(void *arg) { } void GLTrace_start() { - char value[PROPERTY_VALUE_MAX]; + char udsName[PROPERTY_VALUE_MAX]; - property_get("debug.egl.debug_port", value, "5039"); - const unsigned short port = (unsigned short)atoi(value); - - int clientSocket = gltrace::acceptClientConnection(port); + property_get("debug.egl.debug_portname", udsName, "gltrace"); + int clientSocket = gltrace::acceptClientConnection(udsName); if (clientSocket < 0) { ALOGE("Error creating GLTrace server socket. Quitting application."); exit(-1); diff --git a/opengl/libs/GLES_trace/src/gltrace_fixup.cpp b/opengl/libs/GLES_trace/src/gltrace_fixup.cpp index daba3ff53082..871b5dc720b8 100644 --- a/opengl/libs/GLES_trace/src/gltrace_fixup.cpp +++ b/opengl/libs/GLES_trace/src/gltrace_fixup.cpp @@ -358,6 +358,14 @@ void fixupGLMessage(GLTraceContext *context, nsecs_t start, nsecs_t end, GLMessa fixup_addFBContents(context, glmsg, CURRENTLY_BOUND_FB); } break; + case GLMessage::glPushGroupMarkerEXT: + /* void PushGroupMarkerEXT(sizei length, const char *marker); */ + fixup_CStringPtr(1, glmsg); + break; + case GLMessage::glInsertEventMarkerEXT: + /* void InsertEventMarkerEXT(sizei length, const char *marker); */ + fixup_CStringPtr(1, glmsg); + break; default: break; } diff --git a/opengl/libs/GLES_trace/src/gltrace_transport.cpp b/opengl/libs/GLES_trace/src/gltrace_transport.cpp index ce3fae59a11e..5251b12ce009 100644 --- a/opengl/libs/GLES_trace/src/gltrace_transport.cpp +++ b/opengl/libs/GLES_trace/src/gltrace_transport.cpp @@ -17,9 +17,10 @@ #include <stdlib.h> #include <unistd.h> +#include <unistd.h> #include <sys/socket.h> +#include <sys/un.h> #include <netinet/in.h> -#include <arpa/inet.h> #include <cutils/log.h> @@ -28,22 +29,24 @@ namespace android { namespace gltrace { -int acceptClientConnection(int serverPort) { - int serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); +int acceptClientConnection(char *sockname) { + int serverSocket = socket(AF_LOCAL, SOCK_STREAM, 0); if (serverSocket < 0) { ALOGE("Error (%d) while creating socket. Check if app has network permissions.", serverSocket); return -1; } - struct sockaddr_in server, client; + struct sockaddr_un server, client; - server.sin_family = AF_INET; - server.sin_addr.s_addr = htonl(INADDR_ANY); - server.sin_port = htons(serverPort); + memset(&server, 0, sizeof server); + server.sun_family = AF_UNIX; + // the first byte of sun_path should be '\0' for abstract namespace + strcpy(server.sun_path + 1, sockname); - socklen_t sockaddr_len = sizeof(sockaddr_in); - if (bind(serverSocket, (struct sockaddr *) &server, sizeof(server)) < 0) { + // note that sockaddr_len should be set to the exact size of the buffer that is used. + socklen_t sockaddr_len = sizeof(server.sun_family) + strlen(sockname) + 1; + if (bind(serverSocket, (struct sockaddr *) &server, sockaddr_len) < 0) { close(serverSocket); ALOGE("Failed to bind the server socket"); return -1; @@ -55,7 +58,7 @@ int acceptClientConnection(int serverPort) { return -1; } - ALOGD("gltrace::waitForClientConnection: server listening @ port %d", serverPort); + ALOGD("gltrace::waitForClientConnection: server listening @ path %s", sockname); int clientSocket = accept(serverSocket, (struct sockaddr *)&client, &sockaddr_len); if (clientSocket < 0) { @@ -64,7 +67,7 @@ int acceptClientConnection(int serverPort) { return -1; } - ALOGD("gltrace::waitForClientConnection: client connected: %s", inet_ntoa(client.sin_addr)); + ALOGD("gltrace::waitForClientConnection: client connected."); // do not accept any more incoming connections close(serverSocket); diff --git a/opengl/libs/GLES_trace/src/gltrace_transport.h b/opengl/libs/GLES_trace/src/gltrace_transport.h index d31df7b73993..36650355a16d 100644 --- a/opengl/libs/GLES_trace/src/gltrace_transport.h +++ b/opengl/libs/GLES_trace/src/gltrace_transport.h @@ -76,10 +76,11 @@ public: }; /** - * Utility method: start a server at @serverPort, and wait for a client - * connection. Returns the connected client socket on success, or -1 on failure. + * Utility method: start a server listening at @sockName (unix domain socket, + * abstract namespace path), and wait for a client connection. + * Returns the connected client socket on success, or -1 on failure. */ -int acceptClientConnection(int serverPort); +int acceptClientConnection(char *sockName); }; }; diff --git a/opengl/libs/egl_impl.h b/opengl/libs/egl_impl.h index a8093168e079..8ff51eca4b7a 100644 --- a/opengl/libs/egl_impl.h +++ b/opengl/libs/egl_impl.h @@ -29,6 +29,7 @@ namespace android { // ---------------------------------------------------------------------------- +EGLAPI const GLubyte * egl_get_string_for_current_context(GLenum name); EGLAPI EGLImageKHR egl_get_image_for_current_context(EGLImageKHR image); // ---------------------------------------------------------------------------- diff --git a/opengl/libs/tools/glapigen b/opengl/libs/tools/glapigen index bd8dda362561..9be40cf67d48 100755 --- a/opengl/libs/tools/glapigen +++ b/opengl/libs/tools/glapigen @@ -43,6 +43,9 @@ while (my $line = <>) { if ($name eq "glEGLImageTargetRenderbufferStorageOES") { $prefix = "__"; } + if ($name eq "glGetString") { + $prefix = "__"; + } printf("%s API_ENTRY(%s%s)(%s)", $type, $prefix, $name, $args); diff --git a/packages/SystemUI/lint.xml b/packages/SystemUI/lint.xml new file mode 100644 index 000000000000..59a71099bb3f --- /dev/null +++ b/packages/SystemUI/lint.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<lint> + <issue id="PrivateResource" severity="ignore" /> +</lint>
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java index 723e338998f6..888b76e4553d 100644 --- a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java +++ b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java @@ -77,7 +77,7 @@ public class NetworkOverLimitActivity extends Activity { final INetworkPolicyManager policyService = INetworkPolicyManager.Stub.asInterface( ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); try { - policyService.snoozePolicy(template); + policyService.snoozeLimit(template); } catch (RemoteException e) { Slog.w(TAG, "problem snoozing network policy", e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index d46ab6ca41a2..d787e105c2f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -588,8 +588,12 @@ public class NetworkController extends BroadcastReceiver { } } - if ((isCdma() && isCdmaEri()) || mPhone.isNetworkRoaming()) { - mDataTypeIconId = R.drawable.stat_sys_data_connected_roam; + if (isCdma()) { + if (isCdmaEri()) { + mDataTypeIconId = R.drawable.stat_sys_data_connected_roam; + } + } else if (mPhone.isNetworkRoaming()) { + mDataTypeIconId = R.drawable.stat_sys_data_connected_roam; } } @@ -850,8 +854,10 @@ public class NetworkController extends BroadcastReceiver { Slog.d(TAG, "updateConnectivity: intent=" + intent); } - NetworkInfo info = (NetworkInfo)(intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO)); + final ConnectivityManager connManager = (ConnectivityManager) mContext + .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo info = connManager.getActiveNetworkInfo(); + int connectionStatus = intent.getIntExtra(ConnectivityManager.EXTRA_INET_CONDITION, 0); if (CHATTY) { @@ -1017,10 +1023,13 @@ public class NetworkController extends BroadcastReceiver { mContentDescriptionCombinedSignal = mHasMobileDataFeature ? mContentDescriptionDataType : mContentDescriptionWifi; - if ((isCdma() && isCdmaEri()) || mPhone.isNetworkRoaming()) { + mDataTypeIconId = 0; + if (isCdma()) { + if (isCdmaEri()) { + mDataTypeIconId = R.drawable.stat_sys_data_connected_roam; + } + } else if (mPhone.isNetworkRoaming()) { mDataTypeIconId = R.drawable.stat_sys_data_connected_roam; - } else { - mDataTypeIconId = 0; } } diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java index 38c85bb8db2b..bcba3c209711 100644 --- a/policy/src/com/android/internal/policy/impl/GlobalActions.java +++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java @@ -16,16 +16,23 @@ package com.android.internal.policy.impl; -import android.app.Activity; +import com.android.internal.app.ShutdownThread; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.TelephonyProperties; +import com.android.internal.R; + +import android.app.ActivityManagerNative; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.UserInfo; import android.media.AudioManager; import android.os.Handler; import android.os.Message; +import android.os.RemoteException; import android.os.SystemProperties; import android.provider.Settings; import android.telephony.PhoneStateListener; @@ -39,13 +46,9 @@ import android.view.WindowManager; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; -import com.android.internal.R; -import com.android.internal.app.ShutdownThread; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.TelephonyProperties; -import com.google.android.collect.Lists; import java.util.ArrayList; +import java.util.List; /** * Helper to show the global actions dialog. Each item is an {@link Action} that @@ -101,9 +104,10 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { mKeyguardShowing = keyguardShowing; mDeviceProvisioned = isDeviceProvisioned; - if (mDialog == null) { - mDialog = createDialog(); + if (mDialog != null) { + mDialog.dismiss(); } + mDialog = createDialog(); prepareDialog(); mDialog.show(); @@ -187,6 +191,31 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mItems.add(mSilentModeAction); } + List<UserInfo> users = mContext.getPackageManager().getUsers(); + if (users.size() > 1) { + for (final UserInfo user : users) { + SinglePressAction switchToUser = new SinglePressAction( + com.android.internal.R.drawable.ic_menu_cc, + user.name != null ? user.name : "Primary") { + public void onPress() { + try { + ActivityManagerNative.getDefault().switchUser(user.id); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't switch user " + re); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + mItems.add(switchToUser); + } + } mAdapter = new MyAdapter(); final AlertDialog.Builder ab = new AlertDialog.Builder(mContext); @@ -341,12 +370,19 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac private static abstract class SinglePressAction implements Action { private final int mIconResId; private final int mMessageResId; + private final CharSequence mMessage; protected SinglePressAction(int iconResId, int messageResId) { mIconResId = iconResId; mMessageResId = messageResId; + mMessage = null; } + protected SinglePressAction(int iconResId, CharSequence message) { + mIconResId = iconResId; + mMessageResId = 0; + mMessage = message; + } public boolean isEnabled() { return true; } @@ -363,7 +399,11 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac v.findViewById(R.id.status).setVisibility(View.GONE); icon.setImageDrawable(context.getResources().getDrawable(mIconResId)); - messageView.setText(mMessageResId); + if (mMessage != null) { + messageView.setText(mMessage); + } else { + messageView.setText(mMessageResId); + } return v; } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 2d6e4f8aad1a..d34ed17733ff 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -2389,8 +2389,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** {@inheritDoc} */ - public int finishLayoutLw() { - return 0; + @Override + public void finishLayoutLw() { + return; } /** {@inheritDoc} */ diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 3545b4142fba..cc16f377ae7e 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -160,7 +160,10 @@ static const char * const audio_interfaces[] = { AudioFlinger::AudioFlinger() : BnAudioFlinger(), - mPrimaryHardwareDev(NULL), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1), + mPrimaryHardwareDev(NULL), + mHardwareStatus(AUDIO_HW_IDLE), // see also onFirstRef() + mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1), + mMode(AUDIO_MODE_INVALID), mBtNrecIsOff(false) { } @@ -172,7 +175,6 @@ void AudioFlinger::onFirstRef() Mutex::Autolock _l(mLock); /* TODO: move all this work into an Init() function */ - mHardwareStatus = AUDIO_HW_IDLE; for (size_t i = 0; i < ARRAY_SIZE(audio_interfaces); i++) { const hw_module_t *mod; @@ -265,13 +267,10 @@ status_t AudioFlinger::dumpClients(int fd, const Vector<String16>& args) result.append("Clients:\n"); for (size_t i = 0; i < mClients.size(); ++i) { - wp<Client> wClient = mClients.valueAt(i); - if (wClient != 0) { - sp<Client> client = wClient.promote(); - if (client != 0) { - snprintf(buffer, SIZE, " pid: %d\n", client->pid()); - result.append(buffer); - } + sp<Client> client = mClients.valueAt(i).promote(); + if (client != 0) { + snprintf(buffer, SIZE, " pid: %d\n", client->pid()); + result.append(buffer); } } @@ -810,14 +809,13 @@ status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) } } } - if (thread != NULL) { - result = thread->setParameters(keyValuePairs); - return result; + if (thread != 0) { + return thread->setParameters(keyValuePairs); } return BAD_VALUE; } -String8 AudioFlinger::getParameters(int ioHandle, const String8& keys) +String8 AudioFlinger::getParameters(int ioHandle, const String8& keys) const { // ALOGV("getParameters() io %d, keys %s, tid %d, calling tid %d", // ioHandle, keys.string(), gettid(), IPCThreadState::self()->getCallingPid()); @@ -847,7 +845,7 @@ String8 AudioFlinger::getParameters(int ioHandle, const String8& keys) return String8(""); } -size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount) +size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount) const { status_t ret = initCheck(); if (ret != NO_ERROR) { @@ -857,7 +855,7 @@ size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t form return mPrimaryHardwareDev->get_input_buffer_size(mPrimaryHardwareDev, sampleRate, format, channelCount); } -unsigned int AudioFlinger::getInputFramesLost(int ioHandle) +unsigned int AudioFlinger::getInputFramesLost(int ioHandle) const { if (ioHandle == 0) { return 0; @@ -892,7 +890,7 @@ status_t AudioFlinger::setVoiceVolume(float value) return ret; } -status_t AudioFlinger::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) +status_t AudioFlinger::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) const { status_t status; @@ -971,7 +969,8 @@ void AudioFlinger::audioConfigChanged_l(int event, int ioHandle, void *param2) { size_t size = mNotificationClients.size(); for (size_t i = 0; i < size; i++) { - mNotificationClients.valueAt(i)->client()->ioConfigChanged(event, ioHandle, param2); + mNotificationClients.valueAt(i)->audioFlingerClient()->ioConfigChanged(event, ioHandle, + param2); } } @@ -985,13 +984,19 @@ void AudioFlinger::removeClient_l(pid_t pid) // ---------------------------------------------------------------------------- -AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, int id, uint32_t device) +AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, int id, uint32_t device, + type_t type) : Thread(false), - mAudioFlinger(audioFlinger), mSampleRate(0), mFrameCount(0), mChannelCount(0), - mFrameSize(1), mFormat(AUDIO_FORMAT_INVALID), mStandby(false), mId(id), mExiting(false), - mDevice(device) + mType(type), + mAudioFlinger(audioFlinger), mSampleRate(0), mFrameCount(0), + // mChannelMask + mChannelCount(0), + mFrameSize(1), mFormat(AUDIO_FORMAT_INVALID), + mParamStatus(NO_ERROR), + mStandby(false), mId(id), mExiting(false), + mDevice(device), + mDeathRecipient(new PMDeathRecipient(this)) { - mDeathRecipient = new PMDeathRecipient(this); } AudioFlinger::ThreadBase::~ThreadBase() @@ -1371,20 +1376,24 @@ void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled_l(const sp<EffectModu AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, - uint32_t device) - : ThreadBase(audioFlinger, id, device), - mMixBuffer(NULL), mSuspended(0), mBytesWritten(0), mOutput(output), + uint32_t device, + type_t type) + : ThreadBase(audioFlinger, id, device, type), + mMixBuffer(NULL), mSuspended(0), mBytesWritten(0), + // Assumes constructor is called by AudioFlinger with it's mLock held, + // but it would be safer to explicitly pass initial masterMute as parameter + mMasterMute(audioFlinger->masterMute_l()), + // mStreamTypes[] initialized in constructor body + mOutput(output), + // Assumes constructor is called by AudioFlinger with it's mLock held, + // but it would be safer to explicitly pass initial masterVolume as parameter + mMasterVolume(audioFlinger->masterVolume_l()), mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false) { snprintf(mName, kNameLength, "AudioOut_%d", id); readOutputParameters(); - // Assumes constructor is called by AudioFlinger with it's mLock held, - // but it would be safer to explicitly pass these as parameters - mMasterVolume = mAudioFlinger->masterVolume_l(); - mMasterMute = mAudioFlinger->masterMute_l(); - // mStreamTypes[AUDIO_STREAM_CNT] is initialized by stream_type_t default constructor // There is no AUDIO_STREAM_MIN, and ++ operator does not compile for (audio_stream_type_t stream = (audio_stream_type_t) 0; stream < AUDIO_STREAM_CNT; @@ -1430,13 +1439,10 @@ status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16> result.append(buffer); result.append(" Name Clien Typ Fmt Chn mask Session Buf S M F SRate LeftV RighV Serv User Main buf Aux Buf\n"); for (size_t i = 0; i < mActiveTracks.size(); ++i) { - wp<Track> wTrack = mActiveTracks[i]; - if (wTrack != 0) { - sp<Track> track = wTrack.promote(); - if (track != 0) { - track->dump(buffer, SIZE); - result.append(buffer); - } + sp<Track> track = mActiveTracks[i].promote(); + if (track != 0) { + track->dump(buffer, SIZE); + result.append(buffer); } } write(fd, result.string(), result.size()); @@ -1704,7 +1710,7 @@ String8 AudioFlinger::PlaybackThread::getParameters(const String8& keys) // audioConfigChanged_l() must be called with AudioFlinger::mLock held void AudioFlinger::PlaybackThread::audioConfigChanged_l(int event, int param) { AudioSystem::OutputDescriptor desc; - void *param2 = 0; + void *param2 = NULL; ALOGV("PlaybackThread::audioConfigChanged_l, thread %p, event %d, param %d", this, event, param); @@ -1739,7 +1745,7 @@ void AudioFlinger::PlaybackThread::readOutputParameters() // FIXME - Current mixer implementation only supports stereo output: Always // Allocate a stereo buffer even if HW output is mono. - if (mMixBuffer != NULL) delete[] mMixBuffer; + delete[] mMixBuffer; mMixBuffer = new int16_t[mFrameCount * 2]; memset(mMixBuffer, 0, mFrameCount * 2 * sizeof(int16_t)); @@ -1757,7 +1763,7 @@ void AudioFlinger::PlaybackThread::readOutputParameters() status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames) { - if (halFrames == 0 || dspFrames == 0) { + if (halFrames == NULL || dspFrames == NULL) { return BAD_VALUE; } Mutex::Autolock _l(mLock); @@ -1844,13 +1850,12 @@ uint32_t AudioFlinger::PlaybackThread::activeSleepTimeUs() // ---------------------------------------------------------------------------- -AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device) - : PlaybackThread(audioFlinger, output, id, device), - mAudioMixer(NULL), mPrevMixerStatus(MIXER_IDLE) +AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, + int id, uint32_t device, type_t type) + : PlaybackThread(audioFlinger, output, id, device, type), + mAudioMixer(new AudioMixer(mFrameCount, mSampleRate)), + mPrevMixerStatus(MIXER_IDLE) { - mType = ThreadBase::MIXER; - mAudioMixer = new AudioMixer(mFrameCount, mSampleRate); - // FIXME - Current mixer implementation only supports stereo output if (mChannelCount == 1) { ALOGE("Invalid audio hardware channel count"); @@ -2192,7 +2197,7 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac // read original volumes with volume control float typeVolume = mStreamTypes[track->type()].volume; float v = masterVolume * typeVolume; - uint32_t vlr = cblk->volumeLR; + uint32_t vlr = cblk->getVolumeLR(); vl = vlr & 0xFFFF; vr = vlr >> 16; // track volumes come from shared memory, so can't be trusted and must be clamped @@ -2460,6 +2465,8 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() } if (status == NO_ERROR && reconfig) { delete mAudioMixer; + // for safety in case readOutputParameters() accesses mAudioMixer (it doesn't) + mAudioMixer = NULL; readOutputParameters(); mAudioMixer = new AudioMixer(mFrameCount, mSampleRate); for (size_t i = 0; i < mTracks.size() ; i++) { @@ -2512,9 +2519,10 @@ uint32_t AudioFlinger::MixerThread::suspendSleepTimeUs() // ---------------------------------------------------------------------------- AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device) - : PlaybackThread(audioFlinger, output, id, device) + : PlaybackThread(audioFlinger, output, id, device, DIRECT) + // mLeftVolFloat, mRightVolFloat + // mLeftVolShort, mRightVolShort { - mType = ThreadBase::DIRECT; } AudioFlinger::DirectOutputThread::~DirectOutputThread() @@ -2728,7 +2736,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() } else { float typeVolume = mStreamTypes[track->type()].volume; float v = mMasterVolume * typeVolume; - uint32_t vlr = cblk->volumeLR; + uint32_t vlr = cblk->getVolumeLR(); float v_clamped = v * (vlr & 0xFFFF); if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; left = v_clamped/MAX_GAIN; @@ -2991,10 +2999,11 @@ uint32_t AudioFlinger::DirectOutputThread::suspendSleepTimeUs() // ---------------------------------------------------------------------------- -AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::MixerThread* mainThread, int id) - : MixerThread(audioFlinger, mainThread->getOutput(), id, mainThread->device()), mWaitTimeMs(UINT_MAX) +AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, + AudioFlinger::MixerThread* mainThread, int id) + : MixerThread(audioFlinger, mainThread->getOutput(), id, mainThread->device(), DUPLICATING), + mWaitTimeMs(UINT_MAX) { - mType = ThreadBase::DUPLICATING; addOutputTrack(mainThread); } @@ -3195,7 +3204,7 @@ void AudioFlinger::DuplicatingThread::updateWaitTime() mWaitTimeMs = UINT_MAX; for (size_t i = 0; i < mOutputTracks.size(); i++) { sp<ThreadBase> strong = mOutputTracks[i]->thread().promote(); - if (strong != NULL) { + if (strong != 0) { uint32_t waitTimeMs = (strong->frameCount() * 2 * 1000) / strong->sampleRate(); if (waitTimeMs < mWaitTimeMs) { mWaitTimeMs = waitTimeMs; @@ -3243,13 +3252,16 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( : RefBase(), mThread(thread), mClient(client), - mCblk(0), + mCblk(NULL), + // mBuffer + // mBufferEnd mFrameCount(0), mState(IDLE), - mClientTid(-1), mFormat(format), mFlags(flags & ~SYSTEM_FLAGS_MASK), mSessionId(sessionId) + // mChannelCount + // mChannelMask { ALOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(), sharedBuffer->size()); @@ -3265,7 +3277,7 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( mCblkMemory = client->heap()->allocate(size); if (mCblkMemory != 0) { mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->pointer()); - if (mCblk) { // construct the shared structure in-place. + if (mCblk != NULL) { // construct the shared structure in-place. new(mCblk) audio_track_cblk_t(); // clear all buffers mCblk->frameCount = frameCount; @@ -3308,15 +3320,19 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( AudioFlinger::ThreadBase::TrackBase::~TrackBase() { - if (mCblk) { + if (mCblk != NULL) { mCblk->~audio_track_cblk_t(); // destroy our shared-structure. if (mClient == NULL) { delete mCblk; } } mCblkMemory.clear(); // and free the shared memory - if (mClient != NULL) { + if (mClient != 0) { + // Client destructor must run with AudioFlinger mutex locked Mutex::Autolock _l(mClient->audioFlinger()->mLock); + // If the client's reference count drops to zero, the associated destructor + // must run with AudioFlinger lock held. Thus the explicit clear() rather than + // relying on the automatic clear() at end of scope. mClient.clear(); } } @@ -3382,7 +3398,7 @@ void* AudioFlinger::ThreadBase::TrackBase::getBuffer(uint32_t offset, uint32_t f server %d, serverBase %d, user %d, userBase %d", bufferStart, bufferEnd, mBuffer, mBufferEnd, cblk->server, cblk->serverBase, cblk->user, cblk->userBase); - return 0; + return NULL; } return bufferStart; @@ -3467,10 +3483,10 @@ void AudioFlinger::PlaybackThread::Track::destroy() void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size) { - uint32_t vlr = mCblk->volumeLR; + uint32_t vlr = mCblk->getVolumeLR(); snprintf(buffer, size, " %05d %05d %03u %03u 0x%08x %05u %04u %1d %1d %1d %05u %05u %05u 0x%08x 0x%08x 0x%08x 0x%08x\n", mName - AudioMixer::TRACK0, - (mClient == NULL) ? getpid() : mClient->pid(), + (mClient == 0) ? getpid() : mClient->pid(), mStreamType, mFormat, mChannelMask, @@ -3797,7 +3813,7 @@ void AudioFlinger::RecordThread::RecordTrack::stop() void AudioFlinger::RecordThread::RecordTrack::dump(char* buffer, size_t size) { snprintf(buffer, size, " %05d %03u 0x%08x %05d %04u %01d %05u %08x %08x\n", - (mClient == NULL) ? getpid() : mClient->pid(), + (mClient == 0) ? getpid() : mClient->pid(), mFormat, mChannelMask, mSessionId, @@ -3826,7 +3842,6 @@ AudioFlinger::PlaybackThread::OutputTrack::OutputTrack( if (mCblk != NULL) { mCblk->flags |= CBLK_DIRECTION_OUT; mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t); - mCblk->volumeLR = (MAX_GAIN_INT << 16) | MAX_GAIN_INT; mOutBuffer.frameCount = 0; playbackThread->mTracks.add(this); ALOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, " \ @@ -4064,7 +4079,7 @@ AudioFlinger::Client::~Client() mAudioFlinger->removeClient_l(mPid); } -const sp<MemoryDealer>& AudioFlinger::Client::heap() const +sp<MemoryDealer> AudioFlinger::Client::heap() const { return mMemoryDealer; } @@ -4074,13 +4089,12 @@ const sp<MemoryDealer>& AudioFlinger::Client::heap() const AudioFlinger::NotificationClient::NotificationClient(const sp<AudioFlinger>& audioFlinger, const sp<IAudioFlingerClient>& client, pid_t pid) - : mAudioFlinger(audioFlinger), mPid(pid), mClient(client) + : mAudioFlinger(audioFlinger), mPid(pid), mAudioFlingerClient(client) { } AudioFlinger::NotificationClient::~NotificationClient() { - mClient.clear(); } void AudioFlinger::NotificationClient::binderDied(const wp<IBinder>& who) @@ -4265,15 +4279,16 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, uint32_t channels, int id, uint32_t device) : - ThreadBase(audioFlinger, id, device), - mInput(input), mTrack(NULL), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpInBuffer(NULL) + ThreadBase(audioFlinger, id, device, RECORD), + mInput(input), mTrack(NULL), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpInBuffer(NULL), + // mRsmpInIndex and mInputBytes set by readInputParameters() + mReqChannelCount(popcount(channels)), + mReqSampleRate(sampleRate) + // mBytesRead is only meaningful while active, and so is cleared in start() + // (but might be better to also clear here for dump?) { - mType = ThreadBase::RECORD; - snprintf(mName, kNameLength, "AudioIn_%d", id); - mReqChannelCount = popcount(channels); - mReqSampleRate = sampleRate; readInputParameters(); } @@ -4281,10 +4296,8 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::RecordThread::~RecordThread() { delete[] mRsmpInBuffer; - if (mResampler != NULL) { - delete mResampler; - delete[] mRsmpOutBuffer; - } + delete mResampler; + delete[] mRsmpOutBuffer; } void AudioFlinger::RecordThread::onFirstRef() @@ -4515,7 +4528,7 @@ sp<AudioFlinger::RecordThread::RecordTrack> AudioFlinger::RecordThread::createR track = new RecordTrack(this, client, sampleRate, format, channelMask, frameCount, flags, sessionId); - if (track->getCblk() == NULL) { + if (track->getCblk() == 0) { lStatus = NO_MEMORY; goto Exit; } @@ -4618,7 +4631,6 @@ status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args) const size_t SIZE = 256; char buffer[SIZE]; String8 result; - pid_t pid = 0; snprintf(buffer, SIZE, "\nInput thread %p internals\n", this); result.append(buffer); @@ -4806,7 +4818,7 @@ String8 AudioFlinger::RecordThread::getParameters(const String8& keys) void AudioFlinger::RecordThread::audioConfigChanged_l(int event, int param) { AudioSystem::OutputDescriptor desc; - void *param2 = 0; + void *param2 = NULL; switch (event) { case AudioSystem::INPUT_OPENED: @@ -4828,9 +4840,11 @@ void AudioFlinger::RecordThread::audioConfigChanged_l(int event, int param) { void AudioFlinger::RecordThread::readInputParameters() { - if (mRsmpInBuffer) delete mRsmpInBuffer; - if (mRsmpOutBuffer) delete mRsmpOutBuffer; - if (mResampler) delete mResampler; + delete mRsmpInBuffer; + // mRsmpInBuffer is always assigned a new[] below + delete mRsmpOutBuffer; + mRsmpOutBuffer = NULL; + delete mResampler; mResampler = NULL; mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common); @@ -4982,10 +4996,10 @@ int AudioFlinger::openOutput(uint32_t *pDevices, } mPlaybackThreads.add(id, thread); - if (pSamplingRate) *pSamplingRate = samplingRate; - if (pFormat) *pFormat = format; - if (pChannels) *pChannels = channels; - if (pLatencyMs) *pLatencyMs = thread->latency(); + if (pSamplingRate != NULL) *pSamplingRate = samplingRate; + if (pFormat != NULL) *pFormat = format; + if (pChannels != NULL) *pChannels = channels; + if (pLatencyMs != NULL) *pLatencyMs = thread->latency(); // notify client processes of the new output creation thread->audioConfigChanged_l(AudioSystem::OUTPUT_OPENED); @@ -5037,7 +5051,7 @@ status_t AudioFlinger::closeOutput(int output) } } } - void *param2 = 0; + void *param2 = NULL; audioConfigChanged_l(AudioSystem::OUTPUT_CLOSED, output, param2); mPlaybackThreads.removeItem(output); } @@ -5088,7 +5102,7 @@ int AudioFlinger::openInput(uint32_t *pDevices, uint32_t *pSamplingRate, audio_format_t *pFormat, uint32_t *pChannels, - uint32_t acoustics) + audio_in_acoustics_t acoustics) { status_t status; RecordThread *thread = NULL; @@ -5113,7 +5127,7 @@ int AudioFlinger::openInput(uint32_t *pDevices, status = inHwDev->open_input_stream(inHwDev, *pDevices, &format, &channels, &samplingRate, - (audio_in_acoustics_t)acoustics, + acoustics, &inStream); ALOGV("openInput() openInputStream returned input %p, SamplingRate %d, Format %d, Channels %x, acoustics %x, status %d", inStream, @@ -5133,7 +5147,7 @@ int AudioFlinger::openInput(uint32_t *pDevices, ALOGV("openInput() reopening with proposed sampling rate and channels"); status = inHwDev->open_input_stream(inHwDev, *pDevices, &format, &channels, &samplingRate, - (audio_in_acoustics_t)acoustics, + acoustics, &inStream); } @@ -5153,9 +5167,9 @@ int AudioFlinger::openInput(uint32_t *pDevices, device); mRecordThreads.add(id, thread); ALOGV("openInput() created record thread: ID %d thread %p", id, thread); - if (pSamplingRate) *pSamplingRate = reqSamplingRate; - if (pFormat) *pFormat = format; - if (pChannels) *pChannels = reqChannels; + if (pSamplingRate != NULL) *pSamplingRate = reqSamplingRate; + if (pFormat != NULL) *pFormat = format; + if (pChannels != NULL) *pChannels = reqChannels; input->stream->common.standby(&input->stream->common); @@ -5180,7 +5194,7 @@ status_t AudioFlinger::closeInput(int input) } ALOGV("closeInput() %d", input); - void *param2 = 0; + void *param2 = NULL; audioConfigChanged_l(AudioSystem::INPUT_CLOSED, input, param2); mRecordThreads.removeItem(input); } @@ -5242,12 +5256,8 @@ void AudioFlinger::acquireAudioSessionId(int audioSession) return; } } - AudioSessionRef *ref = new AudioSessionRef(); - ref->sessionid = audioSession; - ref->pid = caller; - ref->cnt = 1; - mAudioSessionRefs.push(ref); - ALOGV(" added new entry for %d", ref->sessionid); + mAudioSessionRefs.push(new AudioSessionRef(audioSession, caller)); + ALOGV(" added new entry for %d", audioSession); } void AudioFlinger::releaseAudioSessionId(int audioSession) @@ -5402,19 +5412,20 @@ uint32_t AudioFlinger::primaryOutputDevice_l() // ---------------------------------------------------------------------------- -status_t AudioFlinger::queryNumberEffects(uint32_t *numEffects) +status_t AudioFlinger::queryNumberEffects(uint32_t *numEffects) const { Mutex::Autolock _l(mLock); return EffectQueryNumberEffects(numEffects); } -status_t AudioFlinger::queryEffect(uint32_t index, effect_descriptor_t *descriptor) +status_t AudioFlinger::queryEffect(uint32_t index, effect_descriptor_t *descriptor) const { Mutex::Autolock _l(mLock); return EffectQueryEffect(index, descriptor); } -status_t AudioFlinger::getEffectDescriptor(effect_uuid_t *pUuid, effect_descriptor_t *descriptor) +status_t AudioFlinger::getEffectDescriptor(effect_uuid_t *pUuid, + effect_descriptor_t *descriptor) const { Mutex::Autolock _l(mLock); return EffectGetDescriptor(pUuid, descriptor); @@ -5791,7 +5802,7 @@ sp<AudioFlinger::EffectHandle> AudioFlinger::ThreadBase::createEffect_l( // create effect handle and connect it to effect module handle = new EffectHandle(effect, client, effectClient, priority); lStatus = effect->addHandle(handle); - if (enabled) { + if (enabled != NULL) { *enabled = (int)effect->isEnabled(); } } @@ -6170,7 +6181,7 @@ AudioFlinger::EffectModule::~EffectModule() } } -status_t AudioFlinger::EffectModule::addHandle(sp<EffectHandle>& handle) +status_t AudioFlinger::EffectModule::addHandle(const sp<EffectHandle>& handle) { status_t status; @@ -6217,7 +6228,7 @@ size_t AudioFlinger::EffectModule::removeHandle(const wp<EffectHandle>& handle) bool enabled = false; EffectHandle *hdl = handle.unsafe_get(); - if (hdl) { + if (hdl != NULL) { ALOGV("removeHandle() unsafe_get OK"); enabled = hdl->enabled(); } @@ -6849,7 +6860,7 @@ AudioFlinger::EffectHandle::EffectHandle(const sp<EffectModule>& effect, if (mCblkMemory != 0) { mCblk = static_cast<effect_param_cblk_t *>(mCblkMemory->pointer()); - if (mCblk) { + if (mCblk != NULL) { new(mCblk) effect_param_cblk_t(); mBuffer = (uint8_t *)mCblk + bufOffset; } @@ -6946,7 +6957,7 @@ void AudioFlinger::EffectHandle::disconnect(bool unpiniflast) // release sp on module => module destructor can be called now mEffect.clear(); if (mClient != 0) { - if (mCblk) { + if (mCblk != NULL) { mCblk->~effect_param_cblk_t(); // destroy our shared-structure. } mCblkMemory.clear(); // and free the shared memory @@ -7076,10 +7087,10 @@ status_t AudioFlinger::EffectHandle::onTransact( void AudioFlinger::EffectHandle::dump(char* buffer, size_t size) { - bool locked = mCblk ? tryLock(mCblk->lock) : false; + bool locked = mCblk != NULL && tryLock(mCblk->lock); snprintf(buffer, size, "\t\t\t%05d %05d %01u %01u %05u %05u\n", - (mClient == NULL) ? getpid() : mClient->pid(), + (mClient == 0) ? getpid() : mClient->pid(), mPriority, mHasControl, !locked, @@ -7532,7 +7543,8 @@ void AudioFlinger::EffectChain::setEffectSuspendedAll_l(bool suspend) ALOGV("setEffectSuspendedAll_l() add entry for 0"); } if (desc->mRefCount++ == 0) { - Vector< sp<EffectModule> > effects = getSuspendEligibleEffects(); + Vector< sp<EffectModule> > effects; + getSuspendEligibleEffects(effects); for (size_t i = 0; i < effects.size(); i++) { setEffectSuspended_l(&effects[i]->desc().type, true); } @@ -7583,16 +7595,14 @@ bool AudioFlinger::EffectChain::isEffectEligibleForSuspend(const effect_descript return true; } -Vector< sp<AudioFlinger::EffectModule> > AudioFlinger::EffectChain::getSuspendEligibleEffects() +void AudioFlinger::EffectChain::getSuspendEligibleEffects(Vector< sp<AudioFlinger::EffectModule> > &effects) { - Vector< sp<EffectModule> > effects; + effects.clear(); for (size_t i = 0; i < mEffects.size(); i++) { - if (!isEffectEligibleForSuspend(mEffects[i]->desc())) { - continue; + if (isEffectEligibleForSuspend(mEffects[i]->desc())) { + effects.add(mEffects[i]); } - effects.add(mEffects[i]); } - return effects; } sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectIfEnabled( diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index 766ba44343bb..96c5ceb6ff74 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -105,12 +105,12 @@ public: virtual bool getMicMute() const; virtual status_t setParameters(int ioHandle, const String8& keyValuePairs); - virtual String8 getParameters(int ioHandle, const String8& keys); + virtual String8 getParameters(int ioHandle, const String8& keys) const; virtual void registerClient(const sp<IAudioFlingerClient>& client); - virtual size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount); - virtual unsigned int getInputFramesLost(int ioHandle); + virtual size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount) const; + virtual unsigned int getInputFramesLost(int ioHandle) const; virtual int openOutput(uint32_t *pDevices, uint32_t *pSamplingRate, @@ -131,7 +131,7 @@ public: uint32_t *pSamplingRate, audio_format_t *pFormat, uint32_t *pChannels, - uint32_t acoustics); + audio_in_acoustics_t acoustics); virtual status_t closeInput(int input); @@ -139,7 +139,7 @@ public: virtual status_t setVoiceVolume(float volume); - virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output); + virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) const; virtual int newAudioSessionId(); @@ -147,11 +147,12 @@ public: virtual void releaseAudioSessionId(int audioSession); - virtual status_t queryNumberEffects(uint32_t *numEffects); + virtual status_t queryNumberEffects(uint32_t *numEffects) const; - virtual status_t queryEffect(uint32_t index, effect_descriptor_t *descriptor); + virtual status_t queryEffect(uint32_t index, effect_descriptor_t *descriptor) const; - virtual status_t getEffectDescriptor(effect_uuid_t *pUuid, effect_descriptor_t *descriptor); + virtual status_t getEffectDescriptor(effect_uuid_t *pUuid, + effect_descriptor_t *descriptor) const; virtual sp<IEffect> createEffect(pid_t pid, effect_descriptor_t *pDesc, @@ -226,16 +227,16 @@ private: public: Client(const sp<AudioFlinger>& audioFlinger, pid_t pid); virtual ~Client(); - const sp<MemoryDealer>& heap() const; + sp<MemoryDealer> heap() const; pid_t pid() const { return mPid; } sp<AudioFlinger> audioFlinger() { return mAudioFlinger; } private: Client(const Client&); Client& operator = (const Client&); - sp<AudioFlinger> mAudioFlinger; - sp<MemoryDealer> mMemoryDealer; - pid_t mPid; + const sp<AudioFlinger> mAudioFlinger; + const sp<MemoryDealer> mMemoryDealer; + const pid_t mPid; }; // --- Notification Client --- @@ -246,7 +247,7 @@ private: pid_t pid); virtual ~NotificationClient(); - sp<IAudioFlingerClient> client() { return mClient; } + sp<IAudioFlingerClient> audioFlingerClient() const { return mAudioFlingerClient; } // IBinder::DeathRecipient virtual void binderDied(const wp<IBinder>& who); @@ -255,9 +256,9 @@ private: NotificationClient(const NotificationClient&); NotificationClient& operator = (const NotificationClient&); - sp<AudioFlinger> mAudioFlinger; - pid_t mPid; - sp<IAudioFlingerClient> mClient; + const sp<AudioFlinger> mAudioFlinger; + const pid_t mPid; + const sp<IAudioFlingerClient> mAudioFlingerClient; }; class TrackHandle; @@ -277,17 +278,17 @@ private: class ThreadBase : public Thread { public: - ThreadBase (const sp<AudioFlinger>& audioFlinger, int id, uint32_t device); - virtual ~ThreadBase(); - - enum type { + enum type_t { MIXER, // Thread class is MixerThread DIRECT, // Thread class is DirectOutputThread DUPLICATING, // Thread class is DuplicatingThread RECORD // Thread class is RecordThread }; + ThreadBase (const sp<AudioFlinger>& audioFlinger, int id, uint32_t device, type_t type); + virtual ~ThreadBase(); + status_t dumpBase(int fd, const Vector<String16>& args); status_t dumpEffectChains(int fd, const Vector<String16>& args); @@ -367,8 +368,8 @@ private: bool step(); void reset(); - wp<ThreadBase> mThread; - sp<Client> mClient; + const wp<ThreadBase> mThread; + /*const*/ sp<Client> mClient; // see explanation at ~TrackBase() why not const sp<IMemory> mCblkMemory; audio_track_cblk_t* mCblk; void* mBuffer; @@ -376,10 +377,9 @@ private: uint32_t mFrameCount; // we don't really need a lock for these track_state mState; - int mClientTid; - audio_format_t mFormat; + const audio_format_t mFormat; uint32_t mFlags; - int mSessionId; + const int mSessionId; uint8_t mChannelCount; uint32_t mChannelMask; }; @@ -408,7 +408,7 @@ private: }; virtual status_t initCheck() const = 0; - int type() const { return mType; } + type_t type() const { return mType; } uint32_t sampleRate() const; int channelCount() const; audio_format_t format() const; @@ -530,9 +530,9 @@ private: friend class RecordThread; friend class RecordTrack; - int mType; + const type_t mType; Condition mWaitWorkCV; - sp<AudioFlinger> mAudioFlinger; + const sp<AudioFlinger> mAudioFlinger; uint32_t mSampleRate; size_t mFrameCount; uint32_t mChannelMask; @@ -553,7 +553,7 @@ private: char mName[kNameLength]; sp<IPowerManager> mPowerManager; sp<IBinder> mWakeLockToken; - sp<PMDeathRecipient> mDeathRecipient; + const sp<PMDeathRecipient> mDeathRecipient; // list of suspended effects per session and per type. The first vector is // keyed by session ID, the second by type UUID timeLow field KeyedVector< int, KeyedVector< int, sp<SuspendedSessionDesc> > > mSuspendedSessions; @@ -671,7 +671,7 @@ private: bool write(int16_t* data, uint32_t frames); bool bufferQueueEmpty() { return (mBufferQueue.size() == 0) ? true : false; } bool isActive() { return mActive; } - wp<ThreadBase>& thread() { return mThread; } + const wp<ThreadBase>& thread() { return mThread; } private: @@ -688,10 +688,11 @@ private: Vector < Buffer* > mBufferQueue; AudioBufferProvider::Buffer mOutBuffer; bool mActive; - DuplicatingThread* mSourceThread; + DuplicatingThread* const mSourceThread; // for waitTimeMs() in write() }; // end of OutputTrack - PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device); + PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, + uint32_t device, type_t type); virtual ~PlaybackThread(); virtual status_t dump(int fd, const Vector<String16>& args); @@ -817,7 +818,8 @@ private: MixerThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, - uint32_t device); + uint32_t device, + type_t type = MIXER); virtual ~MixerThread(); // Thread virtuals @@ -917,7 +919,7 @@ private: virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); private: - sp<PlaybackThread::Track> mTrack; + const sp<PlaybackThread::Track> mTrack; }; friend class Client; @@ -1021,8 +1023,8 @@ private: int16_t *mRsmpInBuffer; size_t mRsmpInIndex; size_t mInputBytes; - int mReqChannelCount; - uint32_t mReqSampleRate; + const int mReqChannelCount; + const uint32_t mReqSampleRate; ssize_t mBytesRead; }; @@ -1036,7 +1038,7 @@ private: virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); private: - sp<RecordThread::RecordTrack> mRecordTrack; + const sp<RecordThread::RecordTrack> mRecordTrack; }; //--- Audio Effect Management @@ -1105,9 +1107,9 @@ private: int16_t *outBuffer() { return mConfig.outputCfg.buffer.s16; } void setChain(const wp<EffectChain>& chain) { mChain = chain; } void setThread(const wp<ThreadBase>& thread) { mThread = thread; } - wp<ThreadBase>& thread() { return mThread; } + const wp<ThreadBase>& thread() { return mThread; } - status_t addHandle(sp<EffectHandle>& handle); + status_t addHandle(const sp<EffectHandle>& handle); void disconnect(const wp<EffectHandle>& handle, bool unpiniflast); size_t removeHandle (const wp<EffectHandle>& handle); @@ -1325,7 +1327,8 @@ mutable Mutex mLock; // mutex for process, commands and handl // get a list of effect modules to suspend when an effect of the type // passed is enabled. - Vector< sp<EffectModule> > getSuspendEligibleEffects(); + void getSuspendEligibleEffects(Vector< sp<EffectModule> > &effects); + // get an effect module if it is currently enable sp<EffectModule> getEffectIfEnabled(const effect_uuid_t *type); // true if the effect whose descriptor is passed can be suspended @@ -1377,8 +1380,11 @@ mutable Mutex mLock; // mutex for process, commands and handl }; struct AudioSessionRef { - int sessionid; - pid_t pid; + // FIXME rename parameter names when fields get "m" prefix + AudioSessionRef(int sessionid_, pid_t pid_) : + sessionid(sessionid_), pid(pid_), cnt(1) {} + const int sessionid; + const pid_t pid; int cnt; }; diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp index a8102e5fffd0..3a030928d43d 100644 --- a/services/audioflinger/AudioMixer.cpp +++ b/services/audioflinger/AudioMixer.cpp @@ -48,9 +48,10 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) mState.enabledTracks= 0; mState.needsChanged = 0; mState.frameCount = frameCount; + mState.hook = process__nop; mState.outputTemp = NULL; mState.resampleTemp = NULL; - mState.hook = process__nop; + // mState.reserved track_t* t = mState.tracks; for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) { t->needs = 0; @@ -67,15 +68,16 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) // t->prevAuxLevel // t->frameCount t->channelCount = 2; - t->enabled = 0; + t->enabled = false; t->format = 16; t->channelMask = AUDIO_CHANNEL_OUT_STEREO; - t->buffer.raw = 0; t->bufferProvider = NULL; + t->buffer.raw = NULL; + // t->buffer.frameCount t->hook = NULL; + t->in = NULL; t->resampler = NULL; t->sampleRate = mSampleRate; - t->in = NULL; t->mainBuffer = NULL; t->auxBuffer = NULL; t++; @@ -119,11 +121,11 @@ void AudioMixer::deleteTrackName(int name) assert(uint32_t(name) < MAX_NUM_TRACKS); ALOGV("deleteTrackName(%d)", name); track_t& track(mState.tracks[ name ]); - if (track.enabled != 0) { - track.enabled = 0; + if (track.enabled) { + track.enabled = false; invalidateState(1<<name); } - if (track.resampler) { + if (track.resampler != NULL) { // delete the resampler delete track.resampler; track.resampler = NULL; @@ -141,8 +143,8 @@ void AudioMixer::enable(int name) assert(uint32_t(name) < MAX_NUM_TRACKS); track_t& track = mState.tracks[name]; - if (track.enabled != 1) { - track.enabled = 1; + if (!track.enabled) { + track.enabled = true; ALOGV("enable(%d)", name); invalidateState(1 << name); } @@ -154,8 +156,8 @@ void AudioMixer::disable(int name) assert(uint32_t(name) < MAX_NUM_TRACKS); track_t& track = mState.tracks[name]; - if (track.enabled != 0) { - track.enabled = 0; + if (track.enabled) { + track.enabled = false; ALOGV("disable(%d)", name); invalidateState(1 << name); } @@ -381,9 +383,9 @@ void AudioMixer::process__validate(state_t* state) // compute everything we need... int countActiveTracks = 0; - int all16BitsStereoNoResample = 1; - int resampling = 0; - int volumeRamp = 0; + bool all16BitsStereoNoResample = true; + bool resampling = false; + bool volumeRamp = false; uint32_t en = state->enabledTracks; while (en) { const int i = 31 - __builtin_clz(en); @@ -400,7 +402,7 @@ void AudioMixer::process__validate(state_t* state) } if (t.volumeInc[0]|t.volumeInc[1]) { - volumeRamp = 1; + volumeRamp = true; } else if (!t.doesResample() && t.volumeRL == 0) { n |= NEEDS_MUTE_ENABLED; } @@ -410,16 +412,16 @@ void AudioMixer::process__validate(state_t* state) t.hook = track__nop; } else { if ((n & NEEDS_AUX__MASK) == NEEDS_AUX_ENABLED) { - all16BitsStereoNoResample = 0; + all16BitsStereoNoResample = false; } if ((n & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) { - all16BitsStereoNoResample = 0; - resampling = 1; + all16BitsStereoNoResample = false; + resampling = true; t.hook = track__genericResample; } else { if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){ t.hook = track__16BitsMono; - all16BitsStereoNoResample = 0; + all16BitsStereoNoResample = false; } if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_2){ t.hook = track__16BitsStereo; @@ -467,7 +469,7 @@ void AudioMixer::process__validate(state_t* state) // Now that the volume ramp has been done, set optimal state and // track hooks for subsequent mixer process if (countActiveTracks) { - int allMuted = 1; + bool allMuted = true; uint32_t en = state->enabledTracks; while (en) { const int i = 31 - __builtin_clz(en); @@ -478,7 +480,7 @@ void AudioMixer::process__validate(state_t* state) t.needs |= NEEDS_MUTE_ENABLED; t.hook = track__nop; } else { - allMuted = 0; + allMuted = false; } } if (allMuted) { @@ -807,7 +809,7 @@ void AudioMixer::process__nop(state_t* state) while (outFrames) { t1.buffer.frameCount = outFrames; t1.bufferProvider->getNextBuffer(&t1.buffer); - if (!t1.buffer.raw) break; + if (t1.buffer.raw == NULL) break; outFrames -= t1.buffer.frameCount; t1.bufferProvider->releaseBuffer(&t1.buffer); } @@ -1127,9 +1129,7 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) } } - if (buff != NULL) { - delete [] buff; - } + delete [] buff; } #endif diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp index 2df1385f5237..755f51f51dd2 100644 --- a/services/audioflinger/AudioPolicyService.cpp +++ b/services/audioflinger/AudioPolicyService.cpp @@ -144,9 +144,9 @@ AudioPolicyService::~AudioPolicyService() } mInputs.clear(); - if (mpAudioPolicy && mpAudioPolicyDev) + if (mpAudioPolicy != NULL && mpAudioPolicyDev != NULL) mpAudioPolicyDev->destroy_audio_policy(mpAudioPolicyDev, mpAudioPolicy); - if (mpAudioPolicyDev) + if (mpAudioPolicyDev != NULL) audio_policy_dev_close(mpAudioPolicyDev); } @@ -649,7 +649,7 @@ AudioPolicyService::AudioCommandThread::~AudioCommandThread() release_wake_lock(mName.string()); } mAudioCommands.clear(); - if (mpToneGenerator != NULL) delete mpToneGenerator; + delete mpToneGenerator; } void AudioPolicyService::AudioCommandThread::onFirstRef() @@ -682,8 +682,7 @@ bool AudioPolicyService::AudioCommandThread::threadLoop() ToneData *data = (ToneData *)command->mParam; ALOGV("AudioCommandThread() processing start tone %d on stream %d", data->mType, data->mStream); - if (mpToneGenerator != NULL) - delete mpToneGenerator; + delete mpToneGenerator; mpToneGenerator = new ToneGenerator(data->mStream, 1.0); mpToneGenerator->startTone(data->mType); delete data; @@ -790,7 +789,8 @@ status_t AudioPolicyService::AudioCommandThread::dump(int fd) return NO_ERROR; } -void AudioPolicyService::AudioCommandThread::startToneCommand(int type, audio_stream_type_t stream) +void AudioPolicyService::AudioCommandThread::startToneCommand(ToneGenerator::tone_type type, + audio_stream_type_t stream) { AudioCommand *command = new AudioCommand(); command->mCommand = START_TONE; @@ -1160,7 +1160,7 @@ effect_param_t *AudioPolicyService::loadEffectParameter(cnode *root) if (param == NULL && value == NULL) { // try to parse simple parameter form {int int} param = root->first_child; - if (param) { + if (param != NULL) { // Note: that a pair of random strings is read as 0 0 int *ptr = (int *)fx_param->data; int *ptr2 = (int *)((char *)param + sizeof(effect_param_t)); @@ -1362,7 +1362,7 @@ static audio_io_handle_t aps_open_output(void *service, audio_policy_output_flags_t flags) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) { + if (af == 0) { ALOGW("%s: could not get AudioFlinger", __func__); return 0; } @@ -1376,7 +1376,7 @@ static audio_io_handle_t aps_open_dup_output(void *service, audio_io_handle_t output2) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) { + if (af == 0) { ALOGW("%s: could not get AudioFlinger", __func__); return 0; } @@ -1386,7 +1386,7 @@ static audio_io_handle_t aps_open_dup_output(void *service, static int aps_close_output(void *service, audio_io_handle_t output) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) + if (af == 0) return PERMISSION_DENIED; return af->closeOutput(output); @@ -1395,7 +1395,7 @@ static int aps_close_output(void *service, audio_io_handle_t output) static int aps_suspend_output(void *service, audio_io_handle_t output) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) { + if (af == 0) { ALOGW("%s: could not get AudioFlinger", __func__); return PERMISSION_DENIED; } @@ -1406,7 +1406,7 @@ static int aps_suspend_output(void *service, audio_io_handle_t output) static int aps_restore_output(void *service, audio_io_handle_t output) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) { + if (af == 0) { ALOGW("%s: could not get AudioFlinger", __func__); return PERMISSION_DENIED; } @@ -1419,10 +1419,10 @@ static audio_io_handle_t aps_open_input(void *service, uint32_t *pSamplingRate, audio_format_t *pFormat, uint32_t *pChannels, - uint32_t acoustics) + audio_in_acoustics_t acoustics) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) { + if (af == 0) { ALOGW("%s: could not get AudioFlinger", __func__); return 0; } @@ -1434,7 +1434,7 @@ static audio_io_handle_t aps_open_input(void *service, static int aps_close_input(void *service, audio_io_handle_t input) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) + if (af == 0) return PERMISSION_DENIED; return af->closeInput(input); @@ -1444,7 +1444,7 @@ static int aps_set_stream_output(void *service, audio_stream_type_t stream, audio_io_handle_t output) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) + if (af == 0) return PERMISSION_DENIED; return af->setStreamOutput(stream, output); @@ -1455,7 +1455,7 @@ static int aps_move_effects(void *service, int session, audio_io_handle_t dst_output) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == NULL) + if (af == 0) return PERMISSION_DENIED; return af->moveEffects(session, (int)src_output, (int)dst_output); diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h index 3c0f5eda06cf..62219e505ad9 100644 --- a/services/audioflinger/AudioPolicyService.h +++ b/services/audioflinger/AudioPolicyService.h @@ -79,7 +79,7 @@ public: audio_format_t format = AUDIO_FORMAT_DEFAULT, uint32_t channels = 0, audio_in_acoustics_t acoustics = - (audio_in_acoustics_t)0, + (audio_in_acoustics_t)0 /*AUDIO_IN_ACOUSTICS_NONE*/, int audioSession = 0); virtual status_t startInput(audio_io_handle_t input); virtual status_t stopInput(audio_io_handle_t input); @@ -171,7 +171,8 @@ private: virtual bool threadLoop(); void exit(); - void startToneCommand(int type = 0, audio_stream_type_t stream = AUDIO_STREAM_VOICE_CALL); + void startToneCommand(ToneGenerator::tone_type type, + audio_stream_type_t stream); void stopToneCommand(); status_t volumeCommand(audio_stream_type_t stream, float volume, int output, int delayMs = 0); status_t parametersCommand(int ioHandle, const char *keyValuePairs, int delayMs = 0); @@ -198,7 +199,7 @@ private: class ToneData { public: - int mType; // tone type (START_TONE only) + ToneGenerator::tone_type mType; // tone type (START_TONE only) audio_stream_type_t mStream; // stream type (START_TONE only) }; diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp index feacd961b124..6e17a4a33f28 100644 --- a/services/audioflinger/AudioResampler.cpp +++ b/services/audioflinger/AudioResampler.cpp @@ -130,12 +130,6 @@ AudioResampler::AudioResampler(int bitDepth, int inChannelCount, mVolume[0] = mVolume[1] = 0; mBuffer.frameCount = 0; - // save format for quick lookup - if (inChannelCount == 1) { - mFormat = MONO_16_BIT; - } else { - mFormat = STEREO_16_BIT; - } } AudioResampler::~AudioResampler() { diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h index ffa690afd0ad..e57e2e977fe4 100644 --- a/services/audioflinger/AudioResampler.h +++ b/services/audioflinger/AudioResampler.h @@ -66,7 +66,6 @@ protected: // multiplier to calculate fixed point phase increment static const double kPhaseMultiplier = 1L << kNumPhaseBits; - enum format {MONO_16_BIT, STEREO_16_BIT}; AudioResampler(int bitDepth, int inChannelCount, int32_t sampleRate); // prevent copying @@ -83,7 +82,6 @@ protected: uint32_t mVolumeRL; }; int16_t mTargetVolume[2]; - format mFormat; size_t mInputIndex; int32_t mPhaseIncrement; uint32_t mPhaseFraction; diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp index 1f8cf630bc88..296c95ec937e 100644 --- a/services/input/EventHub.cpp +++ b/services/input/EventHub.cpp @@ -831,7 +831,7 @@ status_t EventHub::openDeviceLocked(const char *devicePath) { ALOGV("Opening device: %s", devicePath); - int fd = open(devicePath, O_RDWR); + int fd = open(devicePath, O_RDWR | O_CLOEXEC); if(fd < 0) { ALOGE("could not open %s, %s\n", devicePath, strerror(errno)); return -1; @@ -1063,14 +1063,20 @@ status_t EventHub::openDeviceLocked(const char *devicePath) { return -1; } + // Enable wake-lock behavior on kernels that support it. + // TODO: Only need this for devices that can really wake the system. + bool usingSuspendBlock = ioctl(fd, EVIOCSSUSPENDBLOCK, 1) == 0; + ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, " - "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s", + "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, " + "usingSuspendBlock=%s", deviceId, fd, devicePath, device->identifier.name.string(), device->classes, device->configurationFile.string(), device->keyMap.keyLayoutFile.string(), device->keyMap.keyCharacterMapFile.string(), - toString(mBuiltInKeyboardId == deviceId)); + toString(mBuiltInKeyboardId == deviceId), + toString(usingSuspendBlock)); mDevices.add(deviceId, device); diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 4f811783b8ce..081f1f4779d2 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -24,67 +24,35 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.Intent.FilterComparison; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.net.Uri; import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; import android.util.Pair; import android.util.Slog; -import android.util.TypedValue; -import android.util.Xml; +import android.util.SparseArray; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; -import com.android.internal.os.AtomicFile; -import com.android.internal.util.FastXmlSerializer; import com.android.internal.widget.IRemoteViewsAdapterConnection; -import com.android.internal.widget.IRemoteViewsFactory; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -import java.io.File; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; -import java.util.Set; + +/** + * Redirects calls to this service to the instance of the service for the appropriate user. + */ class AppWidgetService extends IAppWidgetService.Stub { private static final String TAG = "AppWidgetService"; - private static final String SETTINGS_FILENAME = "appwidgets.xml"; - private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes - /* * When identifying a Host or Provider based on the calling process, use the uid field. * When identifying a Host or Provider based on a package manager broadcast, use the @@ -125,11 +93,9 @@ class AppWidgetService extends IAppWidgetService.Stub * globally and may lead us to leak AppWidgetService instances (if there were more than one). */ static class ServiceConnectionProxy implements ServiceConnection { - private final Pair<Integer, Intent.FilterComparison> mKey; private final IBinder mConnectionCb; ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) { - mKey = key; mConnectionCb = connectionCb; } public void onServiceConnected(ComponentName name, IBinder service) { @@ -155,13 +121,6 @@ class AppWidgetService extends IAppWidgetService.Stub } } - // Manages active connections to RemoteViewsServices - private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> - mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>(); - // Manages persistent references to RemoteViewsServices from different App Widgets - private final HashMap<FilterComparison, HashSet<Integer>> - mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>(); - Context mContext; Locale mLocale; PackageManager mPackageManager; @@ -171,35 +130,32 @@ class AppWidgetService extends IAppWidgetService.Stub final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>(); ArrayList<Host> mHosts = new ArrayList<Host>(); boolean mSafeMode; - boolean mStateLoaded; - // These are for debugging only -- widgets are going missing in some rare instances - ArrayList<Provider> mDeletedProviders = new ArrayList<Provider>(); - ArrayList<Host> mDeletedHosts = new ArrayList<Host>(); + + private final SparseArray<AppWidgetServiceImpl> mAppWidgetServices; AppWidgetService(Context context) { mContext = context; - mPackageManager = context.getPackageManager(); - mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5); + AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0); + mAppWidgetServices.append(0, primary); } public void systemReady(boolean safeMode) { mSafeMode = safeMode; - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - } + mAppWidgetServices.get(0).systemReady(safeMode); // Register for the boot completed broadcast, so we can send the - // ENABLE broacasts. If we try to send them now, they time out, + // ENABLE broacasts. If we try to send them now, they time out, // because the system isn't ready to handle them yet. mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); // Register for configuration changes so we can update the names // of the widgets when the locale changes. - mContext.registerReceiver(mBroadcastReceiver, - new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null); + mContext.registerReceiver(mBroadcastReceiver, new IntentFilter( + Intent.ACTION_CONFIGURATION_CHANGED), null, null); // Register for broadcasts about package install, etc., so we can // update the provider list. @@ -216,216 +172,24 @@ class AppWidgetService extends IAppWidgetService.Stub mContext.registerReceiver(mBroadcastReceiver, sdFilter); } - private void ensureStateLoadedLocked() { - if (!mStateLoaded) { - loadAppWidgetList(); - loadStateLocked(); - mStateLoaded = true; - } - } - - private void dumpProvider(Provider p, int index, PrintWriter pw) { - AppWidgetProviderInfo info = p.info; - pw.print(" ["); pw.print(index); pw.print("] provider "); - pw.print(info.provider.flattenToShortString()); - pw.println(':'); - pw.print(" min=("); pw.print(info.minWidth); - pw.print("x"); pw.print(info.minHeight); - pw.print(") minResize=("); pw.print(info.minResizeWidth); - pw.print("x"); pw.print(info.minResizeHeight); - pw.print(") updatePeriodMillis="); - pw.print(info.updatePeriodMillis); - pw.print(" resizeMode="); - pw.print(info.resizeMode); - pw.print(" autoAdvanceViewId="); - pw.print(info.autoAdvanceViewId); - pw.print(" initialLayout=#"); - pw.print(Integer.toHexString(info.initialLayout)); - pw.print(" zombie="); pw.println(p.zombie); - } - - private void dumpHost(Host host, int index, PrintWriter pw) { - pw.print(" ["); pw.print(index); pw.print("] hostId="); - pw.print(host.hostId); pw.print(' '); - pw.print(host.packageName); pw.print('/'); - pw.print(host.uid); pw.println(':'); - pw.print(" callbacks="); pw.println(host.callbacks); - pw.print(" instances.size="); pw.print(host.instances.size()); - pw.print(" zombie="); pw.println(host.zombie); - } - - private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) { - pw.print(" ["); pw.print(index); pw.print("] id="); - pw.println(id.appWidgetId); - pw.print(" hostId="); - pw.print(id.host.hostId); pw.print(' '); - pw.print(id.host.packageName); pw.print('/'); - pw.println(id.host.uid); - if (id.provider != null) { - pw.print(" provider="); - pw.println(id.provider.info.provider.flattenToShortString()); - } - if (id.host != null) { - pw.print(" host.callbacks="); pw.println(id.host.callbacks); - } - if (id.views != null) { - pw.print(" views="); pw.println(id.views); - } - } - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - synchronized (mAppWidgetIds) { - int N = mInstalledProviders.size(); - pw.println("Providers:"); - for (int i=0; i<N; i++) { - dumpProvider(mInstalledProviders.get(i), i, pw); - } - - N = mAppWidgetIds.size(); - pw.println(" "); - pw.println("AppWidgetIds:"); - for (int i=0; i<N; i++) { - dumpAppWidgetId(mAppWidgetIds.get(i), i, pw); - } - - N = mHosts.size(); - pw.println(" "); - pw.println("Hosts:"); - for (int i=0; i<N; i++) { - dumpHost(mHosts.get(i), i, pw); - } - - N = mDeletedProviders.size(); - pw.println(" "); - pw.println("Deleted Providers:"); - for (int i=0; i<N; i++) { - dumpProvider(mDeletedProviders.get(i), i, pw); - } - - N = mDeletedHosts.size(); - pw.println(" "); - pw.println("Deleted Hosts:"); - for (int i=0; i<N; i++) { - dumpHost(mDeletedHosts.get(i), i, pw); - } - } - } - - public int allocateAppWidgetId(String packageName, int hostId) { - int callingUid = enforceCallingUid(packageName); - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int appWidgetId = mNextAppWidgetId++; - - Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); - - AppWidgetId id = new AppWidgetId(); - id.appWidgetId = appWidgetId; - id.host = host; - - host.instances.add(id); - mAppWidgetIds.add(id); - - saveStateLocked(); - - return appWidgetId; - } + public int allocateAppWidgetId(String packageName, int hostId) throws RemoteException { + return getImplForUser().allocateAppWidgetId(packageName, hostId); } - - public void deleteAppWidgetId(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null) { - deleteAppWidgetLocked(id); - saveStateLocked(); - } - } - } - - public void deleteHost(int hostId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int callingUid = getCallingUid(); - Host host = lookupHostLocked(callingUid, hostId); - if (host != null) { - deleteHostLocked(host); - saveStateLocked(); - } - } - } - - public void deleteAllHosts() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int callingUid = getCallingUid(); - final int N = mHosts.size(); - boolean changed = false; - for (int i=N-1; i>=0; i--) { - Host host = mHosts.get(i); - if (host.uid == callingUid) { - deleteHostLocked(host); - changed = true; - } - } - if (changed) { - saveStateLocked(); - } - } + + @Override + public void deleteAppWidgetId(int appWidgetId) throws RemoteException { + getImplForUser().deleteAppWidgetId(appWidgetId); } - void deleteHostLocked(Host host) { - final int N = host.instances.size(); - for (int i=N-1; i>=0; i--) { - AppWidgetId id = host.instances.get(i); - deleteAppWidgetLocked(id); - } - host.instances.clear(); - mHosts.remove(host); - mDeletedHosts.add(host); - // it's gone or going away, abruptly drop the callback connection - host.callbacks = null; + @Override + public void deleteHost(int hostId) throws RemoteException { + getImplForUser().deleteHost(hostId); } - void deleteAppWidgetLocked(AppWidgetId id) { - // We first unbind all services that are bound to this id - unbindAppWidgetRemoteViewsServicesLocked(id); - - Host host = id.host; - host.instances.remove(id); - pruneHostLocked(host); - - mAppWidgetIds.remove(id); - - Provider p = id.provider; - if (p != null) { - p.instances.remove(id); - if (!p.zombie) { - // send the broacast saying that this appWidgetId has been deleted - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); - intent.setComponent(p.info.provider); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); - mContext.sendBroadcast(intent); - if (p.instances.size() == 0) { - // cancel the future updates - cancelBroadcasts(p); - - // send the broacast saying that the provider is not in use any more - intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); - } - } - } + @Override + public void deleteAllHosts() throws RemoteException { + getImplForUser().deleteAllHosts(); } void cancelBroadcasts(Provider p) { @@ -441,617 +205,58 @@ class AppWidgetService extends IAppWidgetService.Stub } } - public void bindAppWidgetId(int appWidgetId, ComponentName provider) { - mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, - "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider); - - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - if (id.provider != null) { - throw new IllegalArgumentException("appWidgetId " + appWidgetId + " already bound to " - + id.provider.info.provider); - } - Provider p = lookupProviderLocked(provider); - if (p == null) { - throw new IllegalArgumentException("not a appwidget provider: " + provider); - } - if (p.zombie) { - throw new IllegalArgumentException("can't bind to a 3rd party provider in" - + " safe mode: " + provider); - } - - id.provider = p; - p.instances.add(id); - int instancesSize = p.instances.size(); - if (instancesSize == 1) { - // tell the provider that it's ready - sendEnableIntentLocked(p); - } - - // send an update now -- We need this update now, and just for this appWidgetId. - // It's less critical when the next one happens, so when we schdule the next one, - // we add updatePeriodMillis to its start time. That time will have some slop, - // but that's okay. - sendUpdateIntentLocked(p, new int[] { appWidgetId }); - - // schedule the future updates - registerForBroadcastsLocked(p, getAppWidgetIds(p)); - saveStateLocked(); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Binds to a specific RemoteViewsService - public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - final ComponentName componentName = intent.getComponent(); - try { - final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, - PackageManager.GET_PERMISSIONS); - if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { - throw new SecurityException("Selected service does not require " - + android.Manifest.permission.BIND_REMOTEVIEWS - + ": " + componentName); - } - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException("Unknown component " + componentName); - } - - // If there is already a connection made for this service intent, then disconnect from - // that first. (This does not allow multiple connections to the same service under - // the same key) - ServiceConnectionProxy conn = null; - FilterComparison fc = new FilterComparison(intent); - Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc); - if (mBoundRemoteViewsServices.containsKey(key)) { - conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - mBoundRemoteViewsServices.remove(key); - } - - // Bind to the RemoteViewsService (which will trigger a callback to the - // RemoteViewsAdapter.onServiceConnected()) - final long token = Binder.clearCallingIdentity(); - try { - conn = new ServiceConnectionProxy(key, connection); - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - mBoundRemoteViewsServices.put(key, conn); - } finally { - Binder.restoreCallingIdentity(token); - } - - // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine - // when we can call back to the RemoteViewsService later to destroy associated - // factories. - incrementAppWidgetServiceRefCount(appWidgetId, fc); - } - } - - // Unbinds from a specific RemoteViewsService - public void unbindRemoteViewsService(int appWidgetId, Intent intent) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - // Unbind from the RemoteViewsService (which will trigger a callback to the bound - // RemoteViewsAdapter) - Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, - new FilterComparison(intent)); - if (mBoundRemoteViewsServices.containsKey(key)) { - // We don't need to use the appWidgetId until after we are sure there is something - // to unbind. Note that this may mask certain issues with apps calling unbind() - // more than necessary. - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - - ServiceConnectionProxy conn = - (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - mBoundRemoteViewsServices.remove(key); - } else { - Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound"); - } - } - } - - // Unbinds from a RemoteViewsService when we delete an app widget - private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { - int appWidgetId = id.appWidgetId; - // Unbind all connections to Services bound to this AppWidgetId - Iterator<Pair<Integer, Intent.FilterComparison>> it = - mBoundRemoteViewsServices.keySet().iterator(); - while (it.hasNext()) { - final Pair<Integer, Intent.FilterComparison> key = it.next(); - if (key.first.intValue() == appWidgetId) { - final ServiceConnectionProxy conn = (ServiceConnectionProxy) - mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - it.remove(); - } - } - - // Check if we need to destroy any services (if no other app widgets are - // referencing the same service) - decrementAppWidgetServiceRefCount(appWidgetId); - } - - // Destroys the cached factory on the RemoteViewsService's side related to the specified intent - private void destroyRemoteViewsService(final Intent intent) { - final ServiceConnection conn = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - final IRemoteViewsFactory cb = - IRemoteViewsFactory.Stub.asInterface(service); - try { - cb.onDestroy(intent); - } catch (RemoteException e) { - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } - mContext.unbindService(this); - } - @Override - public void onServiceDisconnected(android.content.ComponentName name) { - // Do nothing - } - }; - - // Bind to the service and remove the static intent->factory mapping in the - // RemoteViewsService. - final long token = Binder.clearCallingIdentity(); - try { - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - // Adds to the ref-count for a given RemoteViewsService intent - private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) { - HashSet<Integer> appWidgetIds = null; - if (mRemoteViewsServicesAppWidgets.containsKey(fc)) { - appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc); - } else { - appWidgetIds = new HashSet<Integer>(); - mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds); - } - appWidgetIds.add(appWidgetId); - } - - // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if - // the ref-count reaches zero. - private void decrementAppWidgetServiceRefCount(int appWidgetId) { - Iterator<FilterComparison> it = - mRemoteViewsServicesAppWidgets.keySet().iterator(); - while (it.hasNext()) { - final FilterComparison key = it.next(); - final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key); - if (ids.remove(appWidgetId)) { - // If we have removed the last app widget referencing this service, then we - // should destroy it and remove it from this set - if (ids.isEmpty()) { - destroyRemoteViewsService(key.getIntent()); - it.remove(); - } - } - } - } - - public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null && id.provider != null && !id.provider.zombie) { - return id.provider.info; - } - return null; - } - } - - public RemoteViews getAppWidgetViews(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null) { - return id.views; - } - return null; - } - } - - public List<AppWidgetProviderInfo> getInstalledProviders() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - final int N = mInstalledProviders.size(); - ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (!p.zombie) { - result.add(p.info); - } - } - return result; - } - } - - public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - updateAppWidgetInstanceLocked(id, views); - } - } - } - - public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - updateAppWidgetInstanceLocked(id, views, true); - } - } - } - - public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); - } - } - } - - public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Provider p = lookupProviderLocked(provider); - if (p == null) { - Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider); - return; - } - ArrayList<AppWidgetId> instances = p.instances; - final int callingUid = getCallingUid(); - final int N = instances.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = instances.get(i); - if (canAccessAppWidgetId(id, callingUid)) { - updateAppWidgetInstanceLocked(id, views); - } - } - } - } - - void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { - updateAppWidgetInstanceLocked(id, views, false); - } - - void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { - // allow for stale appWidgetIds and other badness - // lookup also checks that the calling process can access the appWidgetId - // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) - if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - - // We do not want to save this RemoteViews - if (!isPartialUpdate) id.views = views; - - // is anyone listening? - if (id.host.callbacks != null) { - try { - // the lock is held, but this is a oneway call - id.host.callbacks.updateAppWidget(id.appWidgetId, views); - } catch (RemoteException e) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this instance. - id.host.callbacks = null; - } - } - } - } - - void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { - // allow for stale appWidgetIds and other badness - // lookup also checks that the calling process can access the appWidgetId - // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) - if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - // is anyone listening? - if (id.host.callbacks != null) { - try { - // the lock is held, but this is a oneway call - id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); - } catch (RemoteException e) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this instance. - id.host.callbacks = null; - } - } - - // If the host is unavailable, then we call the associated - // RemoteViewsFactory.onDataSetChanged() directly - if (id.host.callbacks == null) { - Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet(); - for (FilterComparison key : keys) { - if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) { - Intent intent = key.getIntent(); - - final ServiceConnection conn = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - IRemoteViewsFactory cb = - IRemoteViewsFactory.Stub.asInterface(service); - try { - cb.onDataSetChangedAsync(); - } catch (RemoteException e) { - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } - mContext.unbindService(this); - } - @Override - public void onServiceDisconnected(android.content.ComponentName name) { - // Do nothing - } - }; - - // Bind to the service and call onDataSetChanged() - final long token = Binder.clearCallingIdentity(); - try { - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - } - } - } - - public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, - List<RemoteViews> updatedViews) { - int callingUid = enforceCallingUid(packageName); - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); - host.callbacks = callbacks; - - updatedViews.clear(); - - ArrayList<AppWidgetId> instances = host.instances; - int N = instances.size(); - int[] updatedIds = new int[N]; - for (int i=0; i<N; i++) { - AppWidgetId id = instances.get(i); - updatedIds[i] = id.appWidgetId; - updatedViews.add(id.views); - } - return updatedIds; - } - } - - public void stopListening(int hostId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Host host = lookupHostLocked(getCallingUid(), hostId); - if (host != null) { - host.callbacks = null; - pruneHostLocked(host); - } - } - } - - boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) { - if (id.host.uid == callingUid) { - // Apps hosting the AppWidget have access to it. - return true; - } - if (id.provider != null && id.provider.uid == callingUid) { - // Apps providing the AppWidget have access to it (if the appWidgetId has been bound) - return true; - } - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) - == PackageManager.PERMISSION_GRANTED) { - // Apps that can bind have access to all appWidgetIds. - return true; - } - // Nobody else can access it. - return false; - } - - AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) { - int callingUid = getCallingUid(); - final int N = mAppWidgetIds.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = mAppWidgetIds.get(i); - if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) { - return id; - } - } - return null; - } - - Provider lookupProviderLocked(ComponentName provider) { - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.info.provider.equals(provider)) { - return p; - } - } - return null; + @Override + public void bindAppWidgetId(int appWidgetId, ComponentName provider) throws RemoteException { + getImplForUser().bindAppWidgetId(appWidgetId, provider); } - Host lookupHostLocked(int uid, int hostId) { - final int N = mHosts.size(); - for (int i=0; i<N; i++) { - Host h = mHosts.get(i); - if (h.uid == uid && h.hostId == hostId) { - return h; - } - } - return null; + @Override + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) + throws RemoteException { + getImplForUser().bindRemoteViewsService(appWidgetId, intent, connection); } - Host lookupOrAddHostLocked(int uid, String packageName, int hostId) { - final int N = mHosts.size(); - for (int i=0; i<N; i++) { - Host h = mHosts.get(i); - if (h.hostId == hostId && h.packageName.equals(packageName)) { - return h; - } - } - Host host = new Host(); - host.packageName = packageName; - host.uid = uid; - host.hostId = hostId; - mHosts.add(host); - return host; + @Override + public int[] startListening(IAppWidgetHost host, String packageName, int hostId, + List<RemoteViews> updatedViews) throws RemoteException { + return getImplForUser().startListening(host, packageName, hostId, updatedViews); } - void pruneHostLocked(Host host) { - if (host.instances.size() == 0 && host.callbacks == null) { - mHosts.remove(host); - } + // TODO: Call this from PackageManagerService when a user is removed + public void removeUser(int userId) { } - void loadAppWidgetList() { - PackageManager pm = mPackageManager; - - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - for (int i=0; i<N; i++) { - ResolveInfo ri = broadcastReceivers.get(i); - addProviderLocked(ri); + private AppWidgetServiceImpl getImplForUser() { + final int userId = Binder.getOrigCallingUser(); + AppWidgetServiceImpl service = mAppWidgetServices.get(userId); + if (service == null) { + Slog.e(TAG, "Unable to find AppWidgetServiceImpl for the current user"); + // TODO: Verify that it's a valid user + service = new AppWidgetServiceImpl(mContext, userId); + service.systemReady(mSafeMode); + // Assume that BOOT_COMPLETED was received, as this is a non-primary user. + service.sendInitialBroadcasts(); + mAppWidgetServices.append(userId, service); } - } - boolean addProviderLocked(ResolveInfo ri) { - if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - return false; - } - if (!ri.activityInfo.isEnabled()) { - return false; - } - Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, - ri.activityInfo.name), ri); - if (p != null) { - mInstalledProviders.add(p); - return true; - } else { - return false; - } + return service; } - void removeProviderLocked(int index, Provider p) { - int N = p.instances.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = p.instances.get(i); - // Call back with empty RemoteViews - updateAppWidgetInstanceLocked(id, null); - // Stop telling the host about updates for this from now on - cancelBroadcasts(p); - // clear out references to this appWidgetId - id.host.instances.remove(id); - mAppWidgetIds.remove(id); - id.provider = null; - pruneHostLocked(id.host); - id.host = null; - } - p.instances.clear(); - mInstalledProviders.remove(index); - mDeletedProviders.add(p); - // no need to send the DISABLE broadcast, since the receiver is gone anyway - cancelBroadcasts(p); + @Override + public int[] getAppWidgetIds(ComponentName provider) throws RemoteException { + return getImplForUser().getAppWidgetIds(provider); } - void sendEnableIntentLocked(Provider p) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); + @Override + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) throws RemoteException { + return getImplForUser().getAppWidgetInfo(appWidgetId); } - void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { - if (appWidgetIds != null && appWidgetIds.length > 0) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); - } + @Override + public RemoteViews getAppWidgetViews(int appWidgetId) throws RemoteException { + return getImplForUser().getAppWidgetViews(appWidgetId); } - void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { - if (p.info.updatePeriodMillis > 0) { - // if this is the first instance, set the alarm. otherwise, - // rely on the fact that we've already set it and that - // PendingIntent.getBroadcast will update the extras. - boolean alreadyRegistered = p.broadcast != null; - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); - intent.setComponent(p.info.provider); - long token = Binder.clearCallingIdentity(); - try { - p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } finally { - Binder.restoreCallingIdentity(token); - } - if (!alreadyRegistered) { - long period = p.info.updatePeriodMillis; - if (period < MIN_UPDATE_PERIOD) { - period = MIN_UPDATE_PERIOD; - } - mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + period, period, p.broadcast); - } - } - } - static int[] getAppWidgetIds(Provider p) { int instancesSize = p.instances.size(); int appWidgetIds[] = new int[instancesSize]; @@ -1060,570 +265,70 @@ class AppWidgetService extends IAppWidgetService.Stub } return appWidgetIds; } - - public int[] getAppWidgetIds(ComponentName provider) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Provider p = lookupProviderLocked(provider); - if (p != null && getCallingUid() == p.uid) { - return getAppWidgetIds(p); - } else { - return new int[0]; - } - } - } - - private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { - Provider p = null; - - ActivityInfo activityInfo = ri.activityInfo; - XmlResourceParser parser = null; - try { - parser = activityInfo.loadXmlMetaData(mPackageManager, - AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); - if (parser == null) { - Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER + " meta-data for " - + "AppWidget provider '" + component + '\''); - return null; - } - - AttributeSet attrs = Xml.asAttributeSet(parser); - - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && type != XmlPullParser.START_TAG) { - // drain whitespace, comments, etc. - } - - String nodeName = parser.getName(); - if (!"appwidget-provider".equals(nodeName)) { - Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for" - + " AppWidget provider '" + component + '\''); - return null; - } - - p = new Provider(); - AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo(); - info.provider = component; - p.uid = activityInfo.applicationInfo.uid; - - Resources res = mPackageManager.getResourcesForApplication( - activityInfo.applicationInfo); - - TypedArray sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.AppWidgetProviderInfo); - // These dimensions has to be resolved in the application's context. - // We simply send back the raw complex data, which will be - // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}. - TypedValue value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth); - info.minWidth = value != null ? value.data : 0; - value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight); - info.minHeight = value != null ? value.data : 0; - value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth); - info.minResizeWidth = value != null ? value.data : info.minWidth; - value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight); - info.minResizeHeight = value != null ? value.data : info.minHeight; - - info.updatePeriodMillis = sa.getInt( - com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); - info.initialLayout = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); - String className = sa.getString( - com.android.internal.R.styleable.AppWidgetProviderInfo_configure); - if (className != null) { - info.configure = new ComponentName(component.getPackageName(), className); - } - info.label = activityInfo.loadLabel(mPackageManager).toString(); - info.icon = ri.getIconResource(); - info.previewImage = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); - info.autoAdvanceViewId = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); - info.resizeMode = sa.getInt( - com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, - AppWidgetProviderInfo.RESIZE_NONE); - - sa.recycle(); - } catch (Exception e) { - // Ok to catch Exception here, because anything going wrong because - // of what a client process passes to us should not be fatal for the - // system process. - Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e); - return null; - } finally { - if (parser != null) parser.close(); - } - return p; + @Override + public List<AppWidgetProviderInfo> getInstalledProviders() throws RemoteException { + return getImplForUser().getInstalledProviders(); } - int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException { - PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0); - if (pkgInfo == null || pkgInfo.applicationInfo == null) { - throw new PackageManager.NameNotFoundException(); - } - return pkgInfo.applicationInfo.uid; + @Override + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) + throws RemoteException { + getImplForUser().notifyAppWidgetViewDataChanged(appWidgetIds, viewId); } - int enforceCallingUid(String packageName) throws IllegalArgumentException { - int callingUid = getCallingUid(); - int packageUid; - try { - packageUid = getUidForPackage(packageName); - } catch (PackageManager.NameNotFoundException ex) { - throw new IllegalArgumentException("packageName and uid don't match packageName=" - + packageName); - } - if (callingUid != packageUid) { - throw new IllegalArgumentException("packageName and uid don't match packageName=" - + packageName); - } - return callingUid; + @Override + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) + throws RemoteException { + getImplForUser().partiallyUpdateAppWidgetIds(appWidgetIds, views); } - void sendInitialBroadcasts() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.instances.size() > 0) { - sendEnableIntentLocked(p); - int[] appWidgetIds = getAppWidgetIds(p); - sendUpdateIntentLocked(p, appWidgetIds); - registerForBroadcastsLocked(p, appWidgetIds); - } - } - } + @Override + public void stopListening(int hostId) throws RemoteException { + getImplForUser().stopListening(hostId); } - // only call from initialization -- it assumes that the data structures are all empty - void loadStateLocked() { - AtomicFile file = savedStateFile(); - try { - FileInputStream stream = file.openRead(); - readStateFromFileLocked(stream); - - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - Slog.w(TAG, "Failed to close state FileInputStream " + e); - } - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "Failed to read state: " + e); - } + @Override + public void unbindRemoteViewsService(int appWidgetId, Intent intent) throws RemoteException { + getImplForUser().unbindRemoteViewsService(appWidgetId, intent); } - void saveStateLocked() { - AtomicFile file = savedStateFile(); - FileOutputStream stream; - try { - stream = file.startWrite(); - if (writeStateToFileLocked(stream)) { - file.finishWrite(stream); - } else { - file.failWrite(stream); - Slog.w(TAG, "Failed to save state, restoring backup."); - } - } catch (IOException e) { - Slog.w(TAG, "Failed open state file for write: " + e); - } + @Override + public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) throws RemoteException { + getImplForUser().updateAppWidgetIds(appWidgetIds, views); } - boolean writeStateToFileLocked(FileOutputStream stream) { - int N; - - try { - XmlSerializer out = new FastXmlSerializer(); - out.setOutput(stream, "utf-8"); - out.startDocument(null, true); - out.startTag(null, "gs"); - - int providerIndex = 0; - N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.instances.size() > 0) { - out.startTag(null, "p"); - out.attribute(null, "pkg", p.info.provider.getPackageName()); - out.attribute(null, "cl", p.info.provider.getClassName()); - out.endTag(null, "p"); - p.tag = providerIndex; - providerIndex++; - } - } - - N = mHosts.size(); - for (int i=0; i<N; i++) { - Host host = mHosts.get(i); - out.startTag(null, "h"); - out.attribute(null, "pkg", host.packageName); - out.attribute(null, "id", Integer.toHexString(host.hostId)); - out.endTag(null, "h"); - host.tag = i; - } - - N = mAppWidgetIds.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = mAppWidgetIds.get(i); - out.startTag(null, "g"); - out.attribute(null, "id", Integer.toHexString(id.appWidgetId)); - out.attribute(null, "h", Integer.toHexString(id.host.tag)); - if (id.provider != null) { - out.attribute(null, "p", Integer.toHexString(id.provider.tag)); - } - out.endTag(null, "g"); - } - - out.endTag(null, "gs"); - - out.endDocument(); - return true; - } catch (IOException e) { - Slog.w(TAG, "Failed to write state: " + e); - return false; - } + @Override + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) + throws RemoteException { + getImplForUser().updateAppWidgetProvider(provider, views); } - void readStateFromFileLocked(FileInputStream stream) { - boolean success = false; - - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(stream, null); - - int type; - int providerIndex = 0; - HashMap<Integer,Provider> loadedProviders = new HashMap<Integer, Provider>(); - do { - type = parser.next(); - if (type == XmlPullParser.START_TAG) { - String tag = parser.getName(); - if ("p".equals(tag)) { - // TODO: do we need to check that this package has the same signature - // as before? - String pkg = parser.getAttributeValue(null, "pkg"); - String cl = parser.getAttributeValue(null, "cl"); - - final PackageManager packageManager = mContext.getPackageManager(); - try { - packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); - } catch (PackageManager.NameNotFoundException e) { - String[] pkgs = packageManager.currentToCanonicalPackageNames( - new String[] { pkg }); - pkg = pkgs[0]; - } - - Provider p = lookupProviderLocked(new ComponentName(pkg, cl)); - if (p == null && mSafeMode) { - // if we're in safe mode, make a temporary one - p = new Provider(); - p.info = new AppWidgetProviderInfo(); - p.info.provider = new ComponentName(pkg, cl); - p.zombie = true; - mInstalledProviders.add(p); - } - if (p != null) { - // if it wasn't uninstalled or something - loadedProviders.put(providerIndex, p); - } - providerIndex++; - } - else if ("h".equals(tag)) { - Host host = new Host(); - - // TODO: do we need to check that this package has the same signature - // as before? - host.packageName = parser.getAttributeValue(null, "pkg"); - try { - host.uid = getUidForPackage(host.packageName); - } catch (PackageManager.NameNotFoundException ex) { - host.zombie = true; - } - if (!host.zombie || mSafeMode) { - // In safe mode, we don't discard the hosts we don't recognize - // so that they're not pruned from our list. Otherwise, we do. - host.hostId = Integer.parseInt( - parser.getAttributeValue(null, "id"), 16); - mHosts.add(host); - } - } - else if ("g".equals(tag)) { - AppWidgetId id = new AppWidgetId(); - id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16); - if (id.appWidgetId >= mNextAppWidgetId) { - mNextAppWidgetId = id.appWidgetId + 1; - } - - String providerString = parser.getAttributeValue(null, "p"); - if (providerString != null) { - // there's no provider if it hasn't been bound yet. - // maybe we don't have to save this, but it brings the system - // to the state it was in. - int pIndex = Integer.parseInt(providerString, 16); - id.provider = loadedProviders.get(pIndex); - if (false) { - Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider " - + pIndex + " which is " + id.provider); - } - if (id.provider == null) { - // This provider is gone. We just let the host figure out - // that this happened when it fails to load it. - continue; - } - } - - int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16); - id.host = mHosts.get(hIndex); - if (id.host == null) { - // This host is gone. - continue; - } - - if (id.provider != null) { - id.provider.instances.add(id); - } - id.host.instances.add(id); - mAppWidgetIds.add(id); - } - } - } while (type != XmlPullParser.END_DOCUMENT); - success = true; - } catch (NullPointerException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (NumberFormatException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (XmlPullParserException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (IOException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (IndexOutOfBoundsException e) { - Slog.w(TAG, "failed parsing " + e); - } - - if (success) { - // delete any hosts that didn't manage to get connected (should happen) - // if it matters, they'll be reconnected. - for (int i=mHosts.size()-1; i>=0; i--) { - pruneHostLocked(mHosts.get(i)); - } - } else { - // failed reading, clean up - Slog.w(TAG, "Failed to read state, clearing widgets and hosts."); - - mAppWidgetIds.clear(); - mHosts.clear(); - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - mInstalledProviders.get(i).instances.clear(); - } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + // Dump the state of all the app widget providers + for (int i = 0; i < mAppWidgetServices.size(); i++) { + AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); + service.dump(fd, pw, args); } } - AtomicFile savedStateFile() { - return new AtomicFile(new File("/data/system/" + SETTINGS_FILENAME)); - } - BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - //Slog.d(TAG, "received " + action); + // Slog.d(TAG, "received " + action); if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { - sendInitialBroadcasts(); + getImplForUser().sendInitialBroadcasts(); } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - Locale revised = Locale.getDefault(); - if (revised == null || mLocale == null || - !(revised.equals(mLocale))) { - mLocale = revised; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int N = mInstalledProviders.size(); - for (int i=N-1; i>=0; i--) { - Provider p = mInstalledProviders.get(i); - String pkgName = p.info.provider.getPackageName(); - updateProvidersForPackageLocked(pkgName); - } - saveStateLocked(); - } + for (int i = 0; i < mAppWidgetServices.size(); i++) { + AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); + service.onConfigurationChanged(); } } else { - boolean added = false; - boolean changed = false; - String pkgList[] = null; - if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = true; - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = false; - } else { - Uri uri = intent.getData(); - if (uri == null) { - return; - } - String pkgName = uri.getSchemeSpecificPart(); - if (pkgName == null) { - return; - } - pkgList = new String[] { pkgName }; - added = Intent.ACTION_PACKAGE_ADDED.equals(action); - changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); - } - if (pkgList == null || pkgList.length == 0) { - return; - } - if (added || changed) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Bundle extras = intent.getExtras(); - if (changed || (extras != null && - extras.getBoolean(Intent.EXTRA_REPLACING, false))) { - for (String pkgName : pkgList) { - // The package was just upgraded - updateProvidersForPackageLocked(pkgName); - } - } else { - // The package was just added - for (String pkgName : pkgList) { - addProvidersForPackageLocked(pkgName); - } - } - saveStateLocked(); - } - } else { - Bundle extras = intent.getExtras(); - if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { - // The package is being updated. We'll receive a PACKAGE_ADDED shortly. - } else { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (String pkgName : pkgList) { - removeProvidersForPackageLocked(pkgName); - saveStateLocked(); - } - } - } - } + // TODO: Verify that this only needs to be delivered for the related user and not + // all the users + getImplForUser().onBroadcastReceived(intent); } } }; - - void addProvidersForPackageLocked(String pkgName) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.setPackage(pkgName); - List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - for (int i=0; i<N; i++) { - ResolveInfo ri = broadcastReceivers.get(i); - ActivityInfo ai = ri.activityInfo; - if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - continue; - } - if (pkgName.equals(ai.packageName)) { - addProviderLocked(ri); - } - } - } - - void updateProvidersForPackageLocked(String pkgName) { - HashSet<String> keep = new HashSet<String>(); - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.setPackage(pkgName); - List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - // add the missing ones and collect which ones to keep - int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - for (int i=0; i<N; i++) { - ResolveInfo ri = broadcastReceivers.get(i); - ActivityInfo ai = ri.activityInfo; - if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - continue; - } - if (pkgName.equals(ai.packageName)) { - ComponentName component = new ComponentName(ai.packageName, ai.name); - Provider p = lookupProviderLocked(component); - if (p == null) { - if (addProviderLocked(ri)) { - keep.add(ai.name); - } - } else { - Provider parsed = parseProviderInfoXml(component, ri); - if (parsed != null) { - keep.add(ai.name); - // Use the new AppWidgetProviderInfo. - p.info = parsed.info; - // If it's enabled - final int M = p.instances.size(); - if (M > 0) { - int[] appWidgetIds = getAppWidgetIds(p); - // Reschedule for the new updatePeriodMillis (don't worry about handling - // it specially if updatePeriodMillis didn't change because we just sent - // an update, and the next one will be updatePeriodMillis from now). - cancelBroadcasts(p); - registerForBroadcastsLocked(p, appWidgetIds); - // If it's currently showing, call back with the new AppWidgetProviderInfo. - for (int j=0; j<M; j++) { - AppWidgetId id = p.instances.get(j); - id.views = null; - if (id.host != null && id.host.callbacks != null) { - try { - id.host.callbacks.providerChanged(id.appWidgetId, p.info); - } catch (RemoteException ex) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this - // instance. - id.host.callbacks = null; - } - } - } - // Now that we've told the host, push out an update. - sendUpdateIntentLocked(p, appWidgetIds); - } - } - } - } - } - - // prune the ones we don't want to keep - N = mInstalledProviders.size(); - for (int i=N-1; i>=0; i--) { - Provider p = mInstalledProviders.get(i); - if (pkgName.equals(p.info.provider.getPackageName()) - && !keep.contains(p.info.provider.getClassName())) { - removeProviderLocked(i, p); - } - } - } - - void removeProvidersForPackageLocked(String pkgName) { - int N = mInstalledProviders.size(); - for (int i=N-1; i>=0; i--) { - Provider p = mInstalledProviders.get(i); - if (pkgName.equals(p.info.provider.getPackageName())) { - removeProviderLocked(i, p); - } - } - - // Delete the hosts for this package too - // - // By now, we have removed any AppWidgets that were in any hosts here, - // so we don't need to worry about sending DISABLE broadcasts to them. - N = mHosts.size(); - for (int i=N-1; i>=0; i--) { - Host host = mHosts.get(i); - if (pkgName.equals(host.packageName)) { - deleteHostLocked(host); - } - } - } } - diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java new file mode 100644 index 000000000000..250386fa5c3d --- /dev/null +++ b/services/java/com/android/server/AppWidgetServiceImpl.java @@ -0,0 +1,1606 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.Intent.FilterComparison; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserId; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Pair; +import android.util.Slog; +import android.util.TypedValue; +import android.util.Xml; +import android.widget.RemoteViews; + +import com.android.internal.appwidget.IAppWidgetHost; +import com.android.internal.os.AtomicFile; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.widget.IRemoteViewsAdapterConnection; +import com.android.internal.widget.IRemoteViewsFactory; +import com.android.server.am.ActivityManagerService; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +class AppWidgetServiceImpl { + + private static final String TAG = "AppWidgetServiceImpl"; + private static final String SETTINGS_FILENAME = "appwidgets.xml"; + private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes + + /* + * When identifying a Host or Provider based on the calling process, use the uid field. When + * identifying a Host or Provider based on a package manager broadcast, use the package given. + */ + + static class Provider { + int uid; + AppWidgetProviderInfo info; + ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>(); + PendingIntent broadcast; + boolean zombie; // if we're in safe mode, don't prune this just because nobody references it + + int tag; // for use while saving state (the index) + } + + static class Host { + int uid; + int hostId; + String packageName; + ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>(); + IAppWidgetHost callbacks; + boolean zombie; // if we're in safe mode, don't prune this just because nobody references it + + int tag; // for use while saving state (the index) + } + + static class AppWidgetId { + int appWidgetId; + Provider provider; + RemoteViews views; + Host host; + } + + /** + * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This + * needs to be a static inner class since a reference to the ServiceConnection is held globally + * and may lead us to leak AppWidgetService instances (if there were more than one). + */ + static class ServiceConnectionProxy implements ServiceConnection { + private final IBinder mConnectionCb; + + ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) { + mConnectionCb = connectionCb; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub + .asInterface(mConnectionCb); + try { + cb.onServiceConnected(service); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void onServiceDisconnected(ComponentName name) { + disconnect(); + } + + public void disconnect() { + final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub + .asInterface(mConnectionCb); + try { + cb.onServiceDisconnected(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + // Manages active connections to RemoteViewsServices + private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> mBoundRemoteViewsServices = new HashMap<Pair<Integer, FilterComparison>, ServiceConnection>(); + // Manages persistent references to RemoteViewsServices from different App Widgets + private final HashMap<FilterComparison, HashSet<Integer>> mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>(); + + Context mContext; + Locale mLocale; + PackageManager mPackageManager; + AlarmManager mAlarmManager; + ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>(); + int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1; + final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>(); + ArrayList<Host> mHosts = new ArrayList<Host>(); + boolean mSafeMode; + int mUserId; + boolean mStateLoaded; + + // These are for debugging only -- widgets are going missing in some rare instances + ArrayList<Provider> mDeletedProviders = new ArrayList<Provider>(); + ArrayList<Host> mDeletedHosts = new ArrayList<Host>(); + + AppWidgetServiceImpl(Context context, int userId) { + mContext = context; + mPackageManager = context.getPackageManager(); + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mUserId = userId; + } + + public void systemReady(boolean safeMode) { + mSafeMode = safeMode; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + } + } + + void onConfigurationChanged() { + Locale revised = Locale.getDefault(); + if (revised == null || mLocale == null || !(revised.equals(mLocale))) { + mLocale = revised; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + String pkgName = p.info.provider.getPackageName(); + updateProvidersForPackageLocked(pkgName); + } + saveStateLocked(); + } + } + } + + void onBroadcastReceived(Intent intent) { + final String action = intent.getAction(); + boolean added = false; + boolean changed = false; + String pkgList[] = null; + if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + added = true; + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + added = false; + } else { + Uri uri = intent.getData(); + if (uri == null) { + return; + } + String pkgName = uri.getSchemeSpecificPart(); + if (pkgName == null) { + return; + } + pkgList = new String[] { pkgName }; + added = Intent.ACTION_PACKAGE_ADDED.equals(action); + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); + } + if (pkgList == null || pkgList.length == 0) { + return; + } + if (added || changed) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Bundle extras = intent.getExtras(); + if (changed + || (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false))) { + for (String pkgName : pkgList) { + // The package was just upgraded + updateProvidersForPackageLocked(pkgName); + } + } else { + // The package was just added + for (String pkgName : pkgList) { + addProvidersForPackageLocked(pkgName); + } + } + saveStateLocked(); + } + } else { + Bundle extras = intent.getExtras(); + if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { + // The package is being updated. We'll receive a PACKAGE_ADDED shortly. + } else { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (String pkgName : pkgList) { + removeProvidersForPackageLocked(pkgName); + saveStateLocked(); + } + } + } + } + } + + private void dumpProvider(Provider p, int index, PrintWriter pw) { + AppWidgetProviderInfo info = p.info; + pw.print(" ["); pw.print(index); pw.print("] provider "); + pw.print(info.provider.flattenToShortString()); + pw.println(':'); + pw.print(" min=("); pw.print(info.minWidth); + pw.print("x"); pw.print(info.minHeight); + pw.print(") minResize=("); pw.print(info.minResizeWidth); + pw.print("x"); pw.print(info.minResizeHeight); + pw.print(") updatePeriodMillis="); + pw.print(info.updatePeriodMillis); + pw.print(" resizeMode="); + pw.print(info.resizeMode); + pw.print(" autoAdvanceViewId="); + pw.print(info.autoAdvanceViewId); + pw.print(" initialLayout=#"); + pw.print(Integer.toHexString(info.initialLayout)); + pw.print(" zombie="); pw.println(p.zombie); + } + + private void dumpHost(Host host, int index, PrintWriter pw) { + pw.print(" ["); pw.print(index); pw.print("] hostId="); + pw.print(host.hostId); pw.print(' '); + pw.print(host.packageName); pw.print('/'); + pw.print(host.uid); pw.println(':'); + pw.print(" callbacks="); pw.println(host.callbacks); + pw.print(" instances.size="); pw.print(host.instances.size()); + pw.print(" zombie="); pw.println(host.zombie); + } + + private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) { + pw.print(" ["); pw.print(index); pw.print("] id="); + pw.println(id.appWidgetId); + pw.print(" hostId="); + pw.print(id.host.hostId); pw.print(' '); + pw.print(id.host.packageName); pw.print('/'); + pw.println(id.host.uid); + if (id.provider != null) { + pw.print(" provider="); + pw.println(id.provider.info.provider.flattenToShortString()); + } + if (id.host != null) { + pw.print(" host.callbacks="); pw.println(id.host.callbacks); + } + if (id.views != null) { + pw.print(" views="); pw.println(id.views); + } + } + + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mAppWidgetIds) { + int N = mInstalledProviders.size(); + pw.println("Providers:"); + for (int i=0; i<N; i++) { + dumpProvider(mInstalledProviders.get(i), i, pw); + } + + N = mAppWidgetIds.size(); + pw.println(" "); + pw.println("AppWidgetIds:"); + for (int i=0; i<N; i++) { + dumpAppWidgetId(mAppWidgetIds.get(i), i, pw); + } + + N = mHosts.size(); + pw.println(" "); + pw.println("Hosts:"); + for (int i=0; i<N; i++) { + dumpHost(mHosts.get(i), i, pw); + } + + N = mDeletedProviders.size(); + pw.println(" "); + pw.println("Deleted Providers:"); + for (int i=0; i<N; i++) { + dumpProvider(mDeletedProviders.get(i), i, pw); + } + + N = mDeletedHosts.size(); + pw.println(" "); + pw.println("Deleted Hosts:"); + for (int i=0; i<N; i++) { + dumpHost(mDeletedHosts.get(i), i, pw); + } + } + } + + private void ensureStateLoadedLocked() { + if (!mStateLoaded) { + loadAppWidgetList(); + loadStateLocked(); + mStateLoaded = true; + } + } + + public int allocateAppWidgetId(String packageName, int hostId) { + int callingUid = enforceCallingUid(packageName); + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int appWidgetId = mNextAppWidgetId++; + + Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); + + AppWidgetId id = new AppWidgetId(); + id.appWidgetId = appWidgetId; + id.host = host; + + host.instances.add(id); + mAppWidgetIds.add(id); + + saveStateLocked(); + + return appWidgetId; + } + } + + public void deleteAppWidgetId(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null) { + deleteAppWidgetLocked(id); + saveStateLocked(); + } + } + } + + public void deleteHost(int hostId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int callingUid = Binder.getCallingUid(); + Host host = lookupHostLocked(callingUid, hostId); + if (host != null) { + deleteHostLocked(host); + saveStateLocked(); + } + } + } + + public void deleteAllHosts() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int callingUid = Binder.getCallingUid(); + final int N = mHosts.size(); + boolean changed = false; + for (int i = N - 1; i >= 0; i--) { + Host host = mHosts.get(i); + if (host.uid == callingUid) { + deleteHostLocked(host); + changed = true; + } + } + if (changed) { + saveStateLocked(); + } + } + } + + void deleteHostLocked(Host host) { + final int N = host.instances.size(); + for (int i = N - 1; i >= 0; i--) { + AppWidgetId id = host.instances.get(i); + deleteAppWidgetLocked(id); + } + host.instances.clear(); + mHosts.remove(host); + mDeletedHosts.add(host); + // it's gone or going away, abruptly drop the callback connection + host.callbacks = null; + } + + void deleteAppWidgetLocked(AppWidgetId id) { + // We first unbind all services that are bound to this id + unbindAppWidgetRemoteViewsServicesLocked(id); + + Host host = id.host; + host.instances.remove(id); + pruneHostLocked(host); + + mAppWidgetIds.remove(id); + + Provider p = id.provider; + if (p != null) { + p.instances.remove(id); + if (!p.zombie) { + // send the broacast saying that this appWidgetId has been deleted + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); + intent.setComponent(p.info.provider); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); + mContext.sendBroadcast(intent); + if (p.instances.size() == 0) { + // cancel the future updates + cancelBroadcasts(p); + + // send the broacast saying that the provider is not in use any more + intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent); + } + } + } + } + + void cancelBroadcasts(Provider p) { + if (p.broadcast != null) { + mAlarmManager.cancel(p.broadcast); + long token = Binder.clearCallingIdentity(); + try { + p.broadcast.cancel(); + } finally { + Binder.restoreCallingIdentity(token); + } + p.broadcast = null; + } + } + + public void bindAppWidgetId(int appWidgetId, ComponentName provider) { + mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, + "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider); + + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + if (id.provider != null) { + throw new IllegalArgumentException("appWidgetId " + appWidgetId + + " already bound to " + id.provider.info.provider); + } + Provider p = lookupProviderLocked(provider); + if (p == null) { + throw new IllegalArgumentException("not a appwidget provider: " + provider); + } + if (p.zombie) { + throw new IllegalArgumentException("can't bind to a 3rd party provider in" + + " safe mode: " + provider); + } + + Binder.restoreCallingIdentity(ident); + + id.provider = p; + p.instances.add(id); + int instancesSize = p.instances.size(); + if (instancesSize == 1) { + // tell the provider that it's ready + sendEnableIntentLocked(p); + } + + // send an update now -- We need this update now, and just for this appWidgetId. + // It's less critical when the next one happens, so when we schedule the next one, + // we add updatePeriodMillis to its start time. That time will have some slop, + // but that's okay. + sendUpdateIntentLocked(p, new int[] { appWidgetId }); + + // schedule the future updates + registerForBroadcastsLocked(p, getAppWidgetIds(p)); + saveStateLocked(); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Binds to a specific RemoteViewsService + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + final ComponentName componentName = intent.getComponent(); + try { + final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, + PackageManager.GET_PERMISSIONS); + if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { + throw new SecurityException("Selected service does not require " + + android.Manifest.permission.BIND_REMOTEVIEWS + ": " + componentName); + } + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown component " + componentName); + } + + // If there is already a connection made for this service intent, then disconnect from + // that first. (This does not allow multiple connections to the same service under + // the same key) + ServiceConnectionProxy conn = null; + FilterComparison fc = new FilterComparison(intent); + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc); + if (mBoundRemoteViewsServices.containsKey(key)) { + conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); + conn.disconnect(); + mContext.unbindService(conn); + mBoundRemoteViewsServices.remove(key); + } + + // Bind to the RemoteViewsService (which will trigger a callback to the + // RemoteViewsAdapter.onServiceConnected()) + final long token = Binder.clearCallingIdentity(); + try { + conn = new ServiceConnectionProxy(key, connection); + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + mBoundRemoteViewsServices.put(key, conn); + } finally { + Binder.restoreCallingIdentity(token); + } + + // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine + // when we can call back to the RemoteViewsService later to destroy associated + // factories. + incrementAppWidgetServiceRefCount(appWidgetId, fc); + } + } + + // Unbinds from a specific RemoteViewsService + public void unbindRemoteViewsService(int appWidgetId, Intent intent) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + // Unbind from the RemoteViewsService (which will trigger a callback to the bound + // RemoteViewsAdapter) + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, new FilterComparison( + intent)); + if (mBoundRemoteViewsServices.containsKey(key)) { + // We don't need to use the appWidgetId until after we are sure there is something + // to unbind. Note that this may mask certain issues with apps calling unbind() + // more than necessary. + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + + ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices + .get(key); + conn.disconnect(); + mContext.unbindService(conn); + mBoundRemoteViewsServices.remove(key); + } else { + Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound"); + } + } + } + + // Unbinds from a RemoteViewsService when we delete an app widget + private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { + int appWidgetId = id.appWidgetId; + // Unbind all connections to Services bound to this AppWidgetId + Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet() + .iterator(); + while (it.hasNext()) { + final Pair<Integer, Intent.FilterComparison> key = it.next(); + if (key.first.intValue() == appWidgetId) { + final ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices + .get(key); + conn.disconnect(); + mContext.unbindService(conn); + it.remove(); + } + } + + // Check if we need to destroy any services (if no other app widgets are + // referencing the same service) + decrementAppWidgetServiceRefCount(appWidgetId); + } + + // Destroys the cached factory on the RemoteViewsService's side related to the specified intent + private void destroyRemoteViewsService(final Intent intent) { + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service); + try { + cb.onDestroy(intent); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (RuntimeException e) { + e.printStackTrace(); + } + mContext.unbindService(this); + } + + @Override + public void onServiceDisconnected(android.content.ComponentName name) { + // Do nothing + } + }; + + // Bind to the service and remove the static intent->factory mapping in the + // RemoteViewsService. + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Adds to the ref-count for a given RemoteViewsService intent + private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) { + HashSet<Integer> appWidgetIds = null; + if (mRemoteViewsServicesAppWidgets.containsKey(fc)) { + appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc); + } else { + appWidgetIds = new HashSet<Integer>(); + mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds); + } + appWidgetIds.add(appWidgetId); + } + + // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if + // the ref-count reaches zero. + private void decrementAppWidgetServiceRefCount(int appWidgetId) { + Iterator<FilterComparison> it = mRemoteViewsServicesAppWidgets.keySet().iterator(); + while (it.hasNext()) { + final FilterComparison key = it.next(); + final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key); + if (ids.remove(appWidgetId)) { + // If we have removed the last app widget referencing this service, then we + // should destroy it and remove it from this set + if (ids.isEmpty()) { + destroyRemoteViewsService(key.getIntent()); + it.remove(); + } + } + } + } + + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null && id.provider != null && !id.provider.zombie) { + return id.provider.info; + } + return null; + } + } + + public RemoteViews getAppWidgetViews(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null) { + return id.views; + } + return null; + } + } + + public List<AppWidgetProviderInfo> getInstalledProviders() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + final int N = mInstalledProviders.size(); + ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (!p.zombie) { + result.add(p.info); + } + } + return result; + } + } + + public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views); + } + } + } + + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views, true); + } + } + } + + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); + } + } + } + + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Provider p = lookupProviderLocked(provider); + if (p == null) { + Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider); + return; + } + ArrayList<AppWidgetId> instances = p.instances; + final int callingUid = Binder.getCallingUid(); + final int N = instances.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = instances.get(i); + if (canAccessAppWidgetId(id, callingUid)) { + updateAppWidgetInstanceLocked(id, views); + } + } + } + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { + updateAppWidgetInstanceLocked(id, views, false); + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + + // We do not want to save this RemoteViews + if (!isPartialUpdate) + id.views = views; + + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.updateAppWidget(id.appWidgetId, views); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + } + } + + void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + + // If the host is unavailable, then we call the associated + // RemoteViewsFactory.onDataSetChanged() directly + if (id.host.callbacks == null) { + Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet(); + for (FilterComparison key : keys) { + if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) { + Intent intent = key.getIntent(); + + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IRemoteViewsFactory cb = IRemoteViewsFactory.Stub + .asInterface(service); + try { + cb.onDataSetChangedAsync(); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (RuntimeException e) { + e.printStackTrace(); + } + mContext.unbindService(this); + } + + @Override + public void onServiceDisconnected(android.content.ComponentName name) { + // Do nothing + } + }; + + // Bind to the service and call onDataSetChanged() + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + } + } + } + + public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, + List<RemoteViews> updatedViews) { + int callingUid = enforceCallingUid(packageName); + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); + host.callbacks = callbacks; + + updatedViews.clear(); + + ArrayList<AppWidgetId> instances = host.instances; + int N = instances.size(); + int[] updatedIds = new int[N]; + for (int i = 0; i < N; i++) { + AppWidgetId id = instances.get(i); + updatedIds[i] = id.appWidgetId; + updatedViews.add(id.views); + } + return updatedIds; + } + } + + public void stopListening(int hostId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Host host = lookupHostLocked(Binder.getCallingUid(), hostId); + if (host != null) { + host.callbacks = null; + pruneHostLocked(host); + } + } + } + + boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) { + if (id.host.uid == callingUid) { + // Apps hosting the AppWidget have access to it. + return true; + } + if (id.provider != null && id.provider.uid == callingUid) { + // Apps providing the AppWidget have access to it (if the appWidgetId has been bound) + return true; + } + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) == PackageManager.PERMISSION_GRANTED) { + // Apps that can bind have access to all appWidgetIds. + return true; + } + // Nobody else can access it. + return false; + } + + AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) { + int callingUid = Binder.getCallingUid(); + final int N = mAppWidgetIds.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = mAppWidgetIds.get(i); + if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) { + return id; + } + } + return null; + } + + Provider lookupProviderLocked(ComponentName provider) { + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.info.provider.equals(provider)) { + return p; + } + } + return null; + } + + Host lookupHostLocked(int uid, int hostId) { + final int N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host h = mHosts.get(i); + if (h.uid == uid && h.hostId == hostId) { + return h; + } + } + return null; + } + + Host lookupOrAddHostLocked(int uid, String packageName, int hostId) { + final int N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host h = mHosts.get(i); + if (h.hostId == hostId && h.packageName.equals(packageName)) { + return h; + } + } + Host host = new Host(); + host.packageName = packageName; + host.uid = uid; + host.hostId = hostId; + mHosts.add(host); + return host; + } + + void pruneHostLocked(Host host) { + if (host.instances.size() == 0 && host.callbacks == null) { + mHosts.remove(host); + } + } + + void loadAppWidgetList() { + PackageManager pm = mPackageManager; + + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent, + PackageManager.GET_META_DATA); + + final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + for (int i = 0; i < N; i++) { + ResolveInfo ri = broadcastReceivers.get(i); + addProviderLocked(ri); + } + } + + boolean addProviderLocked(ResolveInfo ri) { + if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + return false; + } + if (!ri.activityInfo.isEnabled()) { + return false; + } + Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, + ri.activityInfo.name), ri); + if (p != null) { + mInstalledProviders.add(p); + return true; + } else { + return false; + } + } + + void removeProviderLocked(int index, Provider p) { + int N = p.instances.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = p.instances.get(i); + // Call back with empty RemoteViews + updateAppWidgetInstanceLocked(id, null); + // Stop telling the host about updates for this from now on + cancelBroadcasts(p); + // clear out references to this appWidgetId + id.host.instances.remove(id); + mAppWidgetIds.remove(id); + id.provider = null; + pruneHostLocked(id.host); + id.host = null; + } + p.instances.clear(); + mInstalledProviders.remove(index); + mDeletedProviders.add(p); + // no need to send the DISABLE broadcast, since the receiver is gone anyway + cancelBroadcasts(p); + } + + void sendEnableIntentLocked(Provider p) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent); + } + + void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { + if (appWidgetIds != null && appWidgetIds.length > 0) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent); + } + } + + void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { + if (p.info.updatePeriodMillis > 0) { + // if this is the first instance, set the alarm. otherwise, + // rely on the fact that we've already set it and that + // PendingIntent.getBroadcast will update the extras. + boolean alreadyRegistered = p.broadcast != null; + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + intent.setComponent(p.info.provider); + long token = Binder.clearCallingIdentity(); + try { + p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } finally { + Binder.restoreCallingIdentity(token); + } + if (!alreadyRegistered) { + long period = p.info.updatePeriodMillis; + if (period < MIN_UPDATE_PERIOD) { + period = MIN_UPDATE_PERIOD; + } + mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock + .elapsedRealtime() + + period, period, p.broadcast); + } + } + } + + static int[] getAppWidgetIds(Provider p) { + int instancesSize = p.instances.size(); + int appWidgetIds[] = new int[instancesSize]; + for (int i = 0; i < instancesSize; i++) { + appWidgetIds[i] = p.instances.get(i).appWidgetId; + } + return appWidgetIds; + } + + public int[] getAppWidgetIds(ComponentName provider) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Provider p = lookupProviderLocked(provider); + if (p != null && Binder.getCallingUid() == p.uid) { + return getAppWidgetIds(p); + } else { + return new int[0]; + } + } + } + + private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { + Provider p = null; + + ActivityInfo activityInfo = ri.activityInfo; + XmlResourceParser parser = null; + try { + parser = activityInfo.loadXmlMetaData(mPackageManager, + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); + if (parser == null) { + Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER + + " meta-data for " + "AppWidget provider '" + component + '\''); + return null; + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + // drain whitespace, comments, etc. + } + + String nodeName = parser.getName(); + if (!"appwidget-provider".equals(nodeName)) { + Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for" + + " AppWidget provider '" + component + '\''); + return null; + } + + p = new Provider(); + AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo(); + info.provider = component; + p.uid = activityInfo.applicationInfo.uid; + + Resources res = mPackageManager + .getResourcesForApplication(activityInfo.applicationInfo); + + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AppWidgetProviderInfo); + + // These dimensions has to be resolved in the application's context. + // We simply send back the raw complex data, which will be + // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}. + TypedValue value = sa + .peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth); + info.minWidth = value != null ? value.data : 0; + value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight); + info.minHeight = value != null ? value.data : 0; + value = sa.peekValue( + com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth); + info.minResizeWidth = value != null ? value.data : info.minWidth; + value = sa.peekValue( + com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight); + info.minResizeHeight = value != null ? value.data : info.minHeight; + info.updatePeriodMillis = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); + info.initialLayout = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); + String className = sa + .getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure); + if (className != null) { + info.configure = new ComponentName(component.getPackageName(), className); + } + info.label = activityInfo.loadLabel(mPackageManager).toString(); + info.icon = ri.getIconResource(); + info.previewImage = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); + info.autoAdvanceViewId = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); + info.resizeMode = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, + AppWidgetProviderInfo.RESIZE_NONE); + + sa.recycle(); + } catch (Exception e) { + // Ok to catch Exception here, because anything going wrong because + // of what a client process passes to us should not be fatal for the + // system process. + Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e); + return null; + } finally { + if (parser != null) + parser.close(); + } + return p; + } + + int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException { + PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0); + if (pkgInfo == null || pkgInfo.applicationInfo == null) { + throw new PackageManager.NameNotFoundException(); + } + return pkgInfo.applicationInfo.uid; + } + + int enforceCallingUid(String packageName) throws IllegalArgumentException { + int callingUid = Binder.getCallingUid(); + int packageUid; + try { + packageUid = getUidForPackage(packageName); + } catch (PackageManager.NameNotFoundException ex) { + throw new IllegalArgumentException("packageName and uid don't match packageName=" + + packageName); + } + if (!UserId.isSameApp(callingUid, packageUid)) { + throw new IllegalArgumentException("packageName and uid don't match packageName=" + + packageName); + } + return callingUid; + } + + void sendInitialBroadcasts() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.instances.size() > 0) { + sendEnableIntentLocked(p); + int[] appWidgetIds = getAppWidgetIds(p); + sendUpdateIntentLocked(p, appWidgetIds); + registerForBroadcastsLocked(p, appWidgetIds); + } + } + } + } + + // only call from initialization -- it assumes that the data structures are all empty + void loadStateLocked() { + AtomicFile file = savedStateFile(); + try { + FileInputStream stream = file.openRead(); + readStateFromFileLocked(stream); + + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + Slog.w(TAG, "Failed to close state FileInputStream " + e); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "Failed to read state: " + e); + } + } + + void saveStateLocked() { + AtomicFile file = savedStateFile(); + FileOutputStream stream; + try { + stream = file.startWrite(); + if (writeStateToFileLocked(stream)) { + file.finishWrite(stream); + } else { + file.failWrite(stream); + Slog.w(TAG, "Failed to save state, restoring backup."); + } + } catch (IOException e) { + Slog.w(TAG, "Failed open state file for write: " + e); + } + } + + boolean writeStateToFileLocked(FileOutputStream stream) { + int N; + + try { + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(stream, "utf-8"); + out.startDocument(null, true); + out.startTag(null, "gs"); + + int providerIndex = 0; + N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.instances.size() > 0) { + out.startTag(null, "p"); + out.attribute(null, "pkg", p.info.provider.getPackageName()); + out.attribute(null, "cl", p.info.provider.getClassName()); + out.endTag(null, "p"); + p.tag = providerIndex; + providerIndex++; + } + } + + N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host host = mHosts.get(i); + out.startTag(null, "h"); + out.attribute(null, "pkg", host.packageName); + out.attribute(null, "id", Integer.toHexString(host.hostId)); + out.endTag(null, "h"); + host.tag = i; + } + + N = mAppWidgetIds.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = mAppWidgetIds.get(i); + out.startTag(null, "g"); + out.attribute(null, "id", Integer.toHexString(id.appWidgetId)); + out.attribute(null, "h", Integer.toHexString(id.host.tag)); + if (id.provider != null) { + out.attribute(null, "p", Integer.toHexString(id.provider.tag)); + } + out.endTag(null, "g"); + } + + out.endTag(null, "gs"); + + out.endDocument(); + return true; + } catch (IOException e) { + Slog.w(TAG, "Failed to write state: " + e); + return false; + } + } + + void readStateFromFileLocked(FileInputStream stream) { + boolean success = false; + + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + + int type; + int providerIndex = 0; + HashMap<Integer, Provider> loadedProviders = new HashMap<Integer, Provider>(); + do { + type = parser.next(); + if (type == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if ("p".equals(tag)) { + // TODO: do we need to check that this package has the same signature + // as before? + String pkg = parser.getAttributeValue(null, "pkg"); + String cl = parser.getAttributeValue(null, "cl"); + + final PackageManager packageManager = mContext.getPackageManager(); + try { + packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); + } catch (PackageManager.NameNotFoundException e) { + String[] pkgs = packageManager + .currentToCanonicalPackageNames(new String[] { pkg }); + pkg = pkgs[0]; + } + + Provider p = lookupProviderLocked(new ComponentName(pkg, cl)); + if (p == null && mSafeMode) { + // if we're in safe mode, make a temporary one + p = new Provider(); + p.info = new AppWidgetProviderInfo(); + p.info.provider = new ComponentName(pkg, cl); + p.zombie = true; + mInstalledProviders.add(p); + } + if (p != null) { + // if it wasn't uninstalled or something + loadedProviders.put(providerIndex, p); + } + providerIndex++; + } else if ("h".equals(tag)) { + Host host = new Host(); + + // TODO: do we need to check that this package has the same signature + // as before? + host.packageName = parser.getAttributeValue(null, "pkg"); + try { + host.uid = getUidForPackage(host.packageName); + } catch (PackageManager.NameNotFoundException ex) { + host.zombie = true; + } + if (!host.zombie || mSafeMode) { + // In safe mode, we don't discard the hosts we don't recognize + // so that they're not pruned from our list. Otherwise, we do. + host.hostId = Integer + .parseInt(parser.getAttributeValue(null, "id"), 16); + mHosts.add(host); + } + } else if ("g".equals(tag)) { + AppWidgetId id = new AppWidgetId(); + id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16); + if (id.appWidgetId >= mNextAppWidgetId) { + mNextAppWidgetId = id.appWidgetId + 1; + } + + String providerString = parser.getAttributeValue(null, "p"); + if (providerString != null) { + // there's no provider if it hasn't been bound yet. + // maybe we don't have to save this, but it brings the system + // to the state it was in. + int pIndex = Integer.parseInt(providerString, 16); + id.provider = loadedProviders.get(pIndex); + if (false) { + Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider " + + pIndex + " which is " + id.provider); + } + if (id.provider == null) { + // This provider is gone. We just let the host figure out + // that this happened when it fails to load it. + continue; + } + } + + int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16); + id.host = mHosts.get(hIndex); + if (id.host == null) { + // This host is gone. + continue; + } + + if (id.provider != null) { + id.provider.instances.add(id); + } + id.host.instances.add(id); + mAppWidgetIds.add(id); + } + } + } while (type != XmlPullParser.END_DOCUMENT); + success = true; + } catch (NullPointerException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (NumberFormatException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (XmlPullParserException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (IOException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (IndexOutOfBoundsException e) { + Slog.w(TAG, "failed parsing " + e); + } + + if (success) { + // delete any hosts that didn't manage to get connected (should happen) + // if it matters, they'll be reconnected. + for (int i = mHosts.size() - 1; i >= 0; i--) { + pruneHostLocked(mHosts.get(i)); + } + } else { + // failed reading, clean up + Slog.w(TAG, "Failed to read state, clearing widgets and hosts."); + + mAppWidgetIds.clear(); + mHosts.clear(); + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + mInstalledProviders.get(i).instances.clear(); + } + } + } + + AtomicFile savedStateFile() { + int userId = Binder.getOrigCallingUser(); + File dir = new File("/data/system/users/" + userId); + File settingsFile = new File(dir, SETTINGS_FILENAME); + if (!dir.exists()) { + dir.mkdirs(); + if (userId == 0) { + // Migrate old data + File oldFile = new File("/data/system/" + SETTINGS_FILENAME); + // Method doesn't throw an exception on failure. Ignore any errors + // in moving the file (like non-existence) + oldFile.renameTo(settingsFile); + } + } + return new AtomicFile(settingsFile); + } + + void addProvidersForPackageLocked(String pkgName) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.setPackage(pkgName); + List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, + PackageManager.GET_META_DATA); + + final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + for (int i = 0; i < N; i++) { + ResolveInfo ri = broadcastReceivers.get(i); + ActivityInfo ai = ri.activityInfo; + if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + continue; + } + if (pkgName.equals(ai.packageName)) { + addProviderLocked(ri); + } + } + } + + void updateProvidersForPackageLocked(String pkgName) { + HashSet<String> keep = new HashSet<String>(); + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.setPackage(pkgName); + List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, + PackageManager.GET_META_DATA); + + // add the missing ones and collect which ones to keep + int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + for (int i = 0; i < N; i++) { + ResolveInfo ri = broadcastReceivers.get(i); + ActivityInfo ai = ri.activityInfo; + if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + continue; + } + if (pkgName.equals(ai.packageName)) { + ComponentName component = new ComponentName(ai.packageName, ai.name); + Provider p = lookupProviderLocked(component); + if (p == null) { + if (addProviderLocked(ri)) { + keep.add(ai.name); + } + } else { + Provider parsed = parseProviderInfoXml(component, ri); + if (parsed != null) { + keep.add(ai.name); + // Use the new AppWidgetProviderInfo. + p.info = parsed.info; + // If it's enabled + final int M = p.instances.size(); + if (M > 0) { + int[] appWidgetIds = getAppWidgetIds(p); + // Reschedule for the new updatePeriodMillis (don't worry about handling + // it specially if updatePeriodMillis didn't change because we just sent + // an update, and the next one will be updatePeriodMillis from now). + cancelBroadcasts(p); + registerForBroadcastsLocked(p, appWidgetIds); + // If it's currently showing, call back with the new + // AppWidgetProviderInfo. + for (int j = 0; j < M; j++) { + AppWidgetId id = p.instances.get(j); + id.views = null; + if (id.host != null && id.host.callbacks != null) { + try { + id.host.callbacks.providerChanged(id.appWidgetId, p.info); + } catch (RemoteException ex) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this + // instance. + id.host.callbacks = null; + } + } + } + // Now that we've told the host, push out an update. + sendUpdateIntentLocked(p, appWidgetIds); + } + } + } + } + } + + // prune the ones we don't want to keep + N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + if (pkgName.equals(p.info.provider.getPackageName()) + && !keep.contains(p.info.provider.getClassName())) { + removeProviderLocked(i, p); + } + } + } + + void removeProvidersForPackageLocked(String pkgName) { + int N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + if (pkgName.equals(p.info.provider.getPackageName())) { + removeProviderLocked(i, p); + } + } + + // Delete the hosts for this package too + // + // By now, we have removed any AppWidgets that were in any hosts here, + // so we don't need to worry about sending DISABLE broadcasts to them. + N = mHosts.size(); + for (int i = N - 1; i >= 0; i--) { + Host host = mHosts.get(i); + if (pkgName.equals(host.packageName)) { + deleteHostLocked(host); + } + } + } +} diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 31515e129921..a7b08f5b57fb 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -1675,7 +1675,8 @@ class BackupManagerService extends IBackupManager.Stub { synchronized(mClearDataLock) { mClearingData = true; try { - mActivityManager.clearApplicationUserData(packageName, observer); + mActivityManager.clearApplicationUserData(packageName, observer, + Binder.getOrigCallingUser()); } catch (RemoteException e) { // can't happen because the activity manager is in this process } diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index a372fb8e06cf..eab60a79ee96 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -1394,9 +1394,7 @@ private NetworkStateTracker makeWimaxStateTracker() { private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() { @Override public void onUidRulesChanged(int uid, int uidRules) { - // only someone like NPMS should only be calling us - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - + // caller is NPMS, since we only register with them if (LOGD_RULES) { log("onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")"); } @@ -1415,9 +1413,7 @@ private NetworkStateTracker makeWimaxStateTracker() { @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { - // only someone like NPMS should only be calling us - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - + // caller is NPMS, since we only register with them if (LOGD_RULES) { log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")"); } @@ -1429,6 +1425,27 @@ private NetworkStateTracker makeWimaxStateTracker() { } } } + + @Override + public void onRestrictBackgroundChanged(boolean restrictBackground) { + // caller is NPMS, since we only register with them + if (LOGD_RULES) { + log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")"); + } + + // kick off connectivity change broadcast for active network, since + // global background policy change is radical. + final int networkType = mActiveDefaultNetwork; + if (isNetworkTypeValid(networkType)) { + final NetworkStateTracker tracker = mNetTrackers[networkType]; + if (tracker != null) { + final NetworkInfo info = tracker.getNetworkInfo(); + if (info != null && info.isConnected()) { + sendConnectedBroadcast(info); + } + } + } + } }; /** diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 56afe7f51414..8cb9d99be32b 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -1962,8 +1962,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } else { mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; } - NetworkInfo info = - (NetworkInfo)intent.getExtra(ConnectivityManager.EXTRA_NETWORK_INFO); + + final ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo info = connManager.getActiveNetworkInfo(); // Notify location providers of current network state synchronized (mLock) { diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java index f475dd6e3344..308661f1b067 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -153,6 +153,10 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo start = i + 1; } } + if (start == 0) { + final String rawEvent = new String(buffer, start, count, Charsets.UTF_8); + log("RCV incomplete <- {" + rawEvent + "}"); + } // We should end at the amount we read. If not, compact then // buffer and read again. @@ -297,7 +301,11 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo throws NativeDaemonConnectorException { final ArrayList<NativeDaemonEvent> events = Lists.newArrayList(); - mResponseQueue.clear(); + while (mResponseQueue.size() > 0) { + try { + log("ignoring {" + mResponseQueue.take() + "}"); + } catch (Exception e) {} + } final String sentCommand = sendCommandLocked(cmd, args); diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java index f7fe39ecfd68..a7d19929133b 100644 --- a/services/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -234,8 +234,9 @@ public class NetworkTimeUpdateService { String action = intent.getAction(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) { // There is connectivity - NetworkInfo netInfo = (NetworkInfo)intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); + final ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo netInfo = connManager.getActiveNetworkInfo(); if (netInfo != null) { // Verify that it's a WIFI connection if (netInfo.getState() == NetworkInfo.State.CONNECTED && diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 5039294a2680..34a8a027673e 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -45,6 +45,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.UserId; import android.os.Vibrator; import android.provider.Settings; import android.telephony.TelephonyManager; @@ -1034,7 +1035,7 @@ public class NotificationManagerService extends INotificationManager.Stub try { ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo( pkg, 0); - if (ai.uid != uid) { + if (!UserId.isSameApp(ai.uid, uid)) { throw new SecurityException("Calling uid " + uid + " gave package" + pkg + " which is owned by uid " + ai.uid); } diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 23fa94a5a888..8bda7559b729 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -46,7 +46,6 @@ import android.text.TextUtils.SimpleStringSplitter; import android.util.Slog; import android.util.SparseArray; import android.view.IWindow; -import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -96,6 +95,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private static final int DO_SET_SERVICE_INFO = 10; + public static final int ACTIVE_WINDOW_ID = -1; + + public static final long ROOT_NODE_ID = -1; + private static int sNextWindowId; final HandlerCaller mCaller; @@ -467,7 +470,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public void registerEventListener(IEventListener listener) { + public void registerUiTestAutomationService(IEventListener listener, + AccessibilityServiceInfo accessibilityServiceInfo) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, FUNCTION_REGISTER_EVENT_LISTENER); ComponentName componentName = new ComponentName("foo.bar", @@ -490,13 +494,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } // Hook the automation service up. - AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo(); - accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; - accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; Service service = new Service(componentName, accessibilityServiceInfo, true); service.onServiceConnected(componentName, listener.asBinder()); } + public void unregisterUiTestAutomationService(IEventListener listener) { + synchronized (mLock) { + final int serviceCount = mServices.size(); + for (int i = 0; i < serviceCount; i++) { + Service service = mServices.get(i); + if (service.mServiceInterface == listener && service.mIsAutomation) { + // Automation service is not bound, so pretend it died to perform clean up. + service.binderDied(); + } + } + } + } + /** * Removes an AccessibilityInteractionConnection. * @@ -1070,10 +1084,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId, - int interactionId, IAccessibilityInteractionConnectionCallback callback, - long interrogatingTid) + public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, + long accessibilityNodeId, int viewId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); @@ -1081,12 +1096,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!permissionGranted) { return 0; } else { - connection = getConnectionToRetrievalAllowingWindowLocked(); + connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to a retrieve " - + "allowing window."); - } return 0; } } @@ -1094,44 +1105,33 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, callback, - interrogatingPid, interrogatingTid); + connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId, + interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error finding node."); + Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(mSecurityPolicy.getRetrievalAllowingWindowLocked()); - } - - public float findAccessibilityNodeInfosByTextInActiveWindow( - String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long threadId) - throws RemoteException { - return findAccessibilityNodeInfosByText(text, - mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID, interactionId, callback, - threadId); + return getCompatibilityScale(resolvedWindowId); } - public float findAccessibilityNodeInfosByText(String text, - int accessibilityWindowId, long accessibilityNodeId, int interactionId, + public float findAccessibilityNodeInfosByText(int accessibilityWindowId, + long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId); + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { return 0; } else { - connection = getConnectionToRetrievalAllowingWindowLocked(); + connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to focused window."); - } return 0; } } @@ -1139,40 +1139,35 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByText(text, accessibilityNodeId, + connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error finding node."); + Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(accessibilityWindowId); + return getCompatibilityScale(resolvedWindowId); } public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId); + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { return 0; } else { - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId); - if (wrapper == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to window: " - + accessibilityWindowId); - } + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { return 0; } - connection = wrapper.mConnection; } } final int interrogatingPid = Binder.getCallingPid(); @@ -1182,35 +1177,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error requesting node with accessibilityNodeId: " - + accessibilityNodeId); + Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(accessibilityWindowId); + return getCompatibilityScale(resolvedWindowId); } public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, int action, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this, - accessibilityWindowId, action); + resolvedWindowId, action); if (!permissionGranted) { return false; } else { - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId); - if (wrapper == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to window: " - + accessibilityWindowId); - } + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { return false; } - connection = wrapper.mConnection; } } final int interrogatingPid = Binder.getCallingPid(); @@ -1220,8 +1209,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error requesting node with accessibilityNodeId: " - + accessibilityNodeId); + Slog.e(LOG_TAG, "Error calling performAccessibilityAction()"); } } finally { Binder.restoreCallingIdentity(identityToken); @@ -1265,14 +1253,26 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private IAccessibilityInteractionConnection getConnectionToRetrievalAllowingWindowLocked() { - final int windowId = mSecurityPolicy.getRetrievalAllowingWindowLocked(); + private IAccessibilityInteractionConnection getConnectionLocked(int windowId) { if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); } - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(windowId); - return (wrapper != null) ? wrapper.mConnection : null; + AccessibilityConnectionWrapper wrapper = mWindowIdToInteractionConnectionWrapperMap.get( + windowId); + if (wrapper != null && wrapper.mConnection != null) { + return wrapper.mConnection; + } + if (DEBUG) { + Slog.e(LOG_TAG, "No interaction connection to window: " + windowId); + } + return null; + } + + private int resolveAccessibilityWindowId(int accessibilityWindowId) { + if (accessibilityWindowId == ACTIVE_WINDOW_ID) { + return mSecurityPolicy.mRetrievalAlowingWindowId; + } + return accessibilityWindowId; } private float getCompatibilityScale(int windowId) { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index db0a736dfefa..e6a1e68717fb 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -75,6 +75,7 @@ import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -104,6 +105,7 @@ import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserId; import android.provider.Settings; import android.util.EventLog; import android.util.Pair; @@ -111,6 +113,7 @@ import android.util.Slog; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.SparseArray; +import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Gravity; import android.view.LayoutInflater; @@ -135,6 +138,7 @@ import java.io.StringWriter; import java.lang.IllegalStateException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -149,7 +153,9 @@ import java.util.concurrent.atomic.AtomicLong; public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { + private static final String USER_DATA_DIR = "/data/user/"; static final String TAG = "ActivityManager"; + static final String TAG_MU = "ActivityManagerServiceMU"; static final boolean DEBUG = false; static final boolean localLOGV = DEBUG; static final boolean DEBUG_SWITCH = localLOGV || false; @@ -158,6 +164,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_OOM_ADJ = localLOGV || false; static final boolean DEBUG_TRANSITION = localLOGV || false; static final boolean DEBUG_BROADCAST = localLOGV || false; + static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; static final boolean DEBUG_SERVICE = localLOGV || false; static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false; @@ -171,6 +178,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_CONFIGURATION = localLOGV || false; static final boolean DEBUG_POWER = localLOGV || false; static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; + static final boolean DEBUG_MU = localLOGV || false; static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; @@ -221,7 +229,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int CPU_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000; // How long we allow a receiver to run before giving up on it. - static final int BROADCAST_TIMEOUT = 10*1000; + static final int BROADCAST_FG_TIMEOUT = 10*1000; + static final int BROADCAST_BG_TIMEOUT = 60*1000; // How long we wait for a service to finish executing. static final int SERVICE_TIMEOUT = 20*1000; @@ -276,33 +285,832 @@ public final class ActivityManagerService extends ActivityManagerNative = new ArrayList<PendingActivityLaunch>(); /** - * List of all active broadcasts that are to be executed immediately - * (without waiting for another broadcast to finish). Currently this only - * contains broadcasts to registered receivers, to avoid spinning up - * a bunch of processes to execute IntentReceiver components. + * BROADCASTS + * + * We keep two broadcast queues and associated bookkeeping, one for those at + * foreground priority, and one for normal (background-priority) broadcasts. */ - final ArrayList<BroadcastRecord> mParallelBroadcasts - = new ArrayList<BroadcastRecord>(); + public class BroadcastQueue { + static final String TAG = "BroadcastQueue"; - /** - * List of all active broadcasts that are to be executed one at a time. - * The object at the top of the list is the currently activity broadcasts; - * those after it are waiting for the top to finish.. - */ - final ArrayList<BroadcastRecord> mOrderedBroadcasts - = new ArrayList<BroadcastRecord>(); + static final int MAX_BROADCAST_HISTORY = 25; - /** - * Historical data of past broadcasts, for debugging. - */ - static final int MAX_BROADCAST_HISTORY = 25; - final BroadcastRecord[] mBroadcastHistory - = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + /** + * Recognizable moniker for this queue + */ + String mQueueName; - /** - * Set when we current have a BROADCAST_INTENT_MSG in flight. - */ - boolean mBroadcastsScheduled = false; + /** + * Timeout period for this queue's broadcasts + */ + long mTimeoutPeriod; + + /** + * Lists of all active broadcasts that are to be executed immediately + * (without waiting for another broadcast to finish). Currently this only + * contains broadcasts to registered receivers, to avoid spinning up + * a bunch of processes to execute IntentReceiver components. Background- + * and foreground-priority broadcasts are queued separately. + */ + final ArrayList<BroadcastRecord> mParallelBroadcasts + = new ArrayList<BroadcastRecord>(); + /** + * List of all active broadcasts that are to be executed one at a time. + * The object at the top of the list is the currently activity broadcasts; + * those after it are waiting for the top to finish. As with parallel + * broadcasts, separate background- and foreground-priority queues are + * maintained. + */ + final ArrayList<BroadcastRecord> mOrderedBroadcasts + = new ArrayList<BroadcastRecord>(); + + /** + * Historical data of past broadcasts, for debugging. + */ + final BroadcastRecord[] mBroadcastHistory + = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + + /** + * Set when we current have a BROADCAST_INTENT_MSG in flight. + */ + boolean mBroadcastsScheduled = false; + + /** + * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler. + */ + boolean mPendingBroadcastTimeoutMessage; + + /** + * Intent broadcasts that we have tried to start, but are + * waiting for the application's process to be created. We only + * need one per scheduling class (instead of a list) because we always + * process broadcasts one at a time, so no others can be started while + * waiting for this one. + */ + BroadcastRecord mPendingBroadcast = null; + + /** + * The receiver index that is pending, to restart the broadcast if needed. + */ + int mPendingBroadcastRecvIndex; + + BroadcastQueue(String name, long timeoutPeriod) { + mQueueName = name; + mTimeoutPeriod = timeoutPeriod; + } + + public boolean isPendingBroadcastProcessLocked(int pid) { + return mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid; + } + + public void enqueueParallelBroadcastLocked(BroadcastRecord r) { + mParallelBroadcasts.add(r); + } + + public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { + mOrderedBroadcasts.add(r); + } + + public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) { + for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { + if (r.intent.filterEquals(mParallelBroadcasts.get(i).intent)) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "***** DROPPING PARALLEL [" + + mQueueName + "]: " + r.intent); + mParallelBroadcasts.set(i, r); + return true; + } + } + return false; + } + + public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) { + for (int i=mOrderedBroadcasts.size()-1; i>0; i--) { + if (r.intent.filterEquals(mOrderedBroadcasts.get(i).intent)) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "***** DROPPING ORDERED [" + + mQueueName + "]: " + r.intent); + mOrderedBroadcasts.set(i, r); + return true; + } + } + return false; + } + + public boolean sendPendingBroadcastsLocked(ProcessRecord app) { + boolean didSomething = false; + final BroadcastRecord br = mPendingBroadcast; + if (br != null && br.curApp.pid == app.pid) { + try { + mPendingBroadcast = null; + processCurBroadcastLocked(br, app); + didSomething = true; + } catch (Exception e) { + Slog.w(TAG, "Exception in new application when starting receiver " + + br.curComponent.flattenToShortString(), e); + logBroadcastReceiverDiscardLocked(br); + finishReceiverLocked(br, br.resultCode, br.resultData, + br.resultExtras, br.resultAbort, true); + scheduleBroadcastsLocked(); + // We need to reset the state if we fails to start the receiver. + br.state = BroadcastRecord.IDLE; + throw new RuntimeException(e.getMessage()); + } + } + return didSomething; + } + + public void skipPendingBroadcastLocked(int pid) { + final BroadcastRecord br = mPendingBroadcast; + if (br != null && br.curApp.pid == pid) { + br.state = BroadcastRecord.IDLE; + br.nextReceiver = mPendingBroadcastRecvIndex; + mPendingBroadcast = null; + scheduleBroadcastsLocked(); + } + } + + public void skipCurrentReceiverLocked(ProcessRecord app) { + boolean reschedule = false; + BroadcastRecord r = app.curReceiver; + if (r != null) { + // The current broadcast is waiting for this app's receiver + // to be finished. Looks like that's not going to happen, so + // let the broadcast continue. + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + reschedule = true; + } + + r = mPendingBroadcast; + if (r != null && r.curApp == app) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "[" + mQueueName + "] skip & discard pending app " + r); + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + reschedule = true; + } + if (reschedule) { + scheduleBroadcastsLocked(); + } + } + + public void scheduleBroadcastsLocked() { + if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts [" + + mQueueName + "]: current=" + + mBroadcastsScheduled); + + if (mBroadcastsScheduled) { + return; + } + mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this)); + mBroadcastsScheduled = true; + } + + public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) { + if (mOrderedBroadcasts.size() > 0) { + final BroadcastRecord r = mOrderedBroadcasts.get(0); + if (r != null && r.receiver == receiver) { + return r; + } + } + return null; + } + + public boolean finishReceiverLocked(BroadcastRecord r, int resultCode, + String resultData, Bundle resultExtras, boolean resultAbort, + boolean explicit) { + int state = r.state; + r.state = BroadcastRecord.IDLE; + if (state == BroadcastRecord.IDLE) { + if (explicit) { + Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE"); + } + } + r.receiver = null; + r.intent.setComponent(null); + if (r.curApp != null) { + r.curApp.curReceiver = null; + } + if (r.curFilter != null) { + r.curFilter.receiverList.curBroadcast = null; + } + r.curFilter = null; + r.curApp = null; + r.curComponent = null; + r.curReceiver = null; + mPendingBroadcast = null; + + r.resultCode = resultCode; + r.resultData = resultData; + r.resultExtras = resultExtras; + r.resultAbort = resultAbort; + + // We will process the next receiver right now if this is finishing + // an app receiver (which is always asynchronous) or after we have + // come back from calling a receiver. + return state == BroadcastRecord.APP_RECEIVE + || state == BroadcastRecord.CALL_DONE_RECEIVE; + } + + private final void processNextBroadcast(boolean fromMsg) { + synchronized(ActivityManagerService.this) { + BroadcastRecord r; + + if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast [" + + mQueueName + "]: " + + mParallelBroadcasts.size() + " broadcasts, " + + mOrderedBroadcasts.size() + " ordered broadcasts"); + + updateCpuStats(); + + if (fromMsg) { + mBroadcastsScheduled = false; + } + + // First, deliver any non-serialized broadcasts right away. + while (mParallelBroadcasts.size() > 0) { + r = mParallelBroadcasts.remove(0); + r.dispatchTime = SystemClock.uptimeMillis(); + r.dispatchClockTime = System.currentTimeMillis(); + final int N = r.receivers.size(); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast [" + + mQueueName + "] " + r); + for (int i=0; i<N; i++) { + Object target = r.receivers.get(i); + if (DEBUG_BROADCAST) Slog.v(TAG, + "Delivering non-ordered on [" + mQueueName + "] to registered " + + target + ": " + r); + deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); + } + addBroadcastToHistoryLocked(r); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast [" + + mQueueName + "] " + r); + } + + // Now take care of the next serialized one... + + // If we are waiting for a process to come up to handle the next + // broadcast, then do nothing at this point. Just in case, we + // check that the process we're waiting for still exists. + if (mPendingBroadcast != null) { + if (DEBUG_BROADCAST_LIGHT) { + Slog.v(TAG, "processNextBroadcast [" + + mQueueName + "]: waiting for " + + mPendingBroadcast.curApp); + } + + boolean isDead; + synchronized (mPidsSelfLocked) { + isDead = (mPidsSelfLocked.get(mPendingBroadcast.curApp.pid) == null); + } + if (!isDead) { + // It's still alive, so keep waiting + return; + } else { + Slog.w(TAG, "pending app [" + + mQueueName + "]" + mPendingBroadcast.curApp + + " died before responding to broadcast"); + mPendingBroadcast.state = BroadcastRecord.IDLE; + mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; + mPendingBroadcast = null; + } + } + + boolean looped = false; + + do { + if (mOrderedBroadcasts.size() == 0) { + // No more broadcasts pending, so all done! + scheduleAppGcsLocked(); + if (looped) { + // If we had finished the last ordered broadcast, then + // make sure all processes have correct oom and sched + // adjustments. + updateOomAdjLocked(); + } + return; + } + r = mOrderedBroadcasts.get(0); + boolean forceReceive = false; + + // Ensure that even if something goes awry with the timeout + // detection, we catch "hung" broadcasts here, discard them, + // and continue to make progress. + // + // This is only done if the system is ready so that PRE_BOOT_COMPLETED + // receivers don't get executed with timeouts. They're intended for + // one time heavy lifting after system upgrades and can take + // significant amounts of time. + int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; + if (mProcessesReady && r.dispatchTime > 0) { + long now = SystemClock.uptimeMillis(); + if ((numReceivers > 0) && + (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) { + Slog.w(TAG, "Hung broadcast [" + + mQueueName + "] discarded after timeout failure:" + + " now=" + now + + " dispatchTime=" + r.dispatchTime + + " startTime=" + r.receiverTime + + " intent=" + r.intent + + " numReceivers=" + numReceivers + + " nextReceiver=" + r.nextReceiver + + " state=" + r.state); + broadcastTimeoutLocked(false); // forcibly finish this broadcast + forceReceive = true; + r.state = BroadcastRecord.IDLE; + } + } + + if (r.state != BroadcastRecord.IDLE) { + if (DEBUG_BROADCAST) Slog.d(TAG, + "processNextBroadcast(" + + mQueueName + ") called when not idle (state=" + + r.state + ")"); + return; + } + + if (r.receivers == null || r.nextReceiver >= numReceivers + || r.resultAbort || forceReceive) { + // No more receivers for this broadcast! Send the final + // result if requested... + if (r.resultTo != null) { + try { + if (DEBUG_BROADCAST) { + int seq = r.intent.getIntExtra("seq", -1); + Slog.i(TAG, "Finishing broadcast [" + + mQueueName + "] " + r.intent.getAction() + + " seq=" + seq + " app=" + r.callerApp); + } + performReceiveLocked(r.callerApp, r.resultTo, + new Intent(r.intent), r.resultCode, + r.resultData, r.resultExtras, false, false); + // Set this to null so that the reference + // (local and remote) isnt kept in the mBroadcastHistory. + r.resultTo = null; + } catch (RemoteException e) { + Slog.w(TAG, "Failure [" + + mQueueName + "] sending broadcast result of " + + r.intent, e); + } + } + + if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG"); + cancelBroadcastTimeoutLocked(); + + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast " + + r); + + // ... and on to the next... + addBroadcastToHistoryLocked(r); + mOrderedBroadcasts.remove(0); + r = null; + looped = true; + continue; + } + } while (r == null); + + // Get the next receiver... + int recIdx = r.nextReceiver++; + + // Keep track of when this receiver started, and make sure there + // is a timeout message pending to kill it if need be. + r.receiverTime = SystemClock.uptimeMillis(); + if (recIdx == 0) { + r.dispatchTime = r.receiverTime; + r.dispatchClockTime = System.currentTimeMillis(); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast [" + + mQueueName + "] " + r); + } + if (! mPendingBroadcastTimeoutMessage) { + long timeoutTime = r.receiverTime + mTimeoutPeriod; + if (DEBUG_BROADCAST) Slog.v(TAG, + "Submitting BROADCAST_TIMEOUT_MSG [" + + mQueueName + "] for " + r + " at " + timeoutTime); + setBroadcastTimeoutLocked(timeoutTime); + } + + Object nextReceiver = r.receivers.get(recIdx); + if (nextReceiver instanceof BroadcastFilter) { + // Simple case: this is a registered receiver who gets + // a direct call. + BroadcastFilter filter = (BroadcastFilter)nextReceiver; + if (DEBUG_BROADCAST) Slog.v(TAG, + "Delivering ordered [" + + mQueueName + "] to registered " + + filter + ": " + r); + deliverToRegisteredReceiverLocked(r, filter, r.ordered); + if (r.receiver == null || !r.ordered) { + // The receiver has already finished, so schedule to + // process the next one. + if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing [" + + mQueueName + "]: ordered=" + + r.ordered + " receiver=" + r.receiver); + r.state = BroadcastRecord.IDLE; + scheduleBroadcastsLocked(); + } + return; + } + + // Hard case: need to instantiate the receiver, possibly + // starting its application process to host it. + + ResolveInfo info = + (ResolveInfo)nextReceiver; + + boolean skip = false; + int perm = checkComponentPermission(info.activityInfo.permission, + r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, + info.activityInfo.exported); + if (perm != PackageManager.PERMISSION_GRANTED) { + if (!info.activityInfo.exported) { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ")" + + " is not exported from uid " + info.activityInfo.applicationInfo.uid + + " due to receiver " + info.activityInfo.packageName + + "/" + info.activityInfo.name); + } else { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ")" + + " requires " + info.activityInfo.permission + + " due to receiver " + info.activityInfo.packageName + + "/" + info.activityInfo.name); + } + skip = true; + } + if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && + r.requiredPermission != null) { + try { + perm = AppGlobals.getPackageManager(). + checkPermission(r.requiredPermission, + info.activityInfo.applicationInfo.packageName); + } catch (RemoteException e) { + perm = PackageManager.PERMISSION_DENIED; + } + if (perm != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: receiving " + + r.intent + " to " + + info.activityInfo.applicationInfo.packageName + + " requires " + r.requiredPermission + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + } + } + if (r.curApp != null && r.curApp.crashing) { + // If the target process is crashing, just skip it. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Skipping deliver ordered [" + + mQueueName + "] " + r + " to " + r.curApp + + ": process crashing"); + skip = true; + } + + if (skip) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "Skipping delivery of ordered [" + + mQueueName + "] " + r + " for whatever reason"); + r.receiver = null; + r.curFilter = null; + r.state = BroadcastRecord.IDLE; + scheduleBroadcastsLocked(); + return; + } + + r.state = BroadcastRecord.APP_RECEIVE; + String targetProcess = info.activityInfo.processName; + r.curComponent = new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + if (r.callingUid != Process.SYSTEM_UID) { + info.activityInfo = getActivityInfoForUser(info.activityInfo, UserId + .getUserId(r.callingUid)); + } + r.curReceiver = info.activityInfo; + if (DEBUG_MU && r.callingUid > UserId.PER_USER_RANGE) { + Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, " + + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = " + + info.activityInfo.applicationInfo.uid); + } + + // Broadcast is being executed, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + r.curComponent.getPackageName(), false); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + r.curComponent.getPackageName() + ": " + e); + } + + // Is this receiver's application already running? + ProcessRecord app = getProcessRecordLocked(targetProcess, + info.activityInfo.applicationInfo.uid); + if (app != null && app.thread != null) { + try { + app.addPackage(info.activityInfo.packageName); + processCurBroadcastLocked(r, app); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when sending broadcast to " + + r.curComponent, e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + + // Not running -- get it started, to be executed when the app comes up. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Need to start app [" + + mQueueName + "] " + targetProcess + " for broadcast " + r); + if ((r.curApp=startProcessLocked(targetProcess, + info.activityInfo.applicationInfo, true, + r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, + "broadcast", r.curComponent, + (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0)) + == null) { + // Ah, this recipient is unavailable. Finish it if necessary, + // and mark the broadcast record as ready for the next. + Slog.w(TAG, "Unable to launch app " + + info.activityInfo.applicationInfo.packageName + "/" + + info.activityInfo.applicationInfo.uid + " for broadcast " + + r.intent + ": process is bad"); + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + scheduleBroadcastsLocked(); + r.state = BroadcastRecord.IDLE; + return; + } + + mPendingBroadcast = r; + mPendingBroadcastRecvIndex = recIdx; + } + } + + final void setBroadcastTimeoutLocked(long timeoutTime) { + if (! mPendingBroadcastTimeoutMessage) { + Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this); + mHandler.sendMessageAtTime(msg, timeoutTime); + mPendingBroadcastTimeoutMessage = true; + } + } + + final void cancelBroadcastTimeoutLocked() { + if (mPendingBroadcastTimeoutMessage) { + mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this); + mPendingBroadcastTimeoutMessage = false; + } + } + + final void broadcastTimeoutLocked(boolean fromMsg) { + if (fromMsg) { + mPendingBroadcastTimeoutMessage = false; + } + + if (mOrderedBroadcasts.size() == 0) { + return; + } + + long now = SystemClock.uptimeMillis(); + BroadcastRecord r = mOrderedBroadcasts.get(0); + if (fromMsg) { + if (mDidDexOpt) { + // Delay timeouts until dexopt finishes. + mDidDexOpt = false; + long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod; + setBroadcastTimeoutLocked(timeoutTime); + return; + } + if (! mProcessesReady) { + // Only process broadcast timeouts if the system is ready. That way + // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended + // to do heavy lifting for system up. + return; + } + + long timeoutTime = r.receiverTime + mTimeoutPeriod; + if (timeoutTime > now) { + // We can observe premature timeouts because we do not cancel and reset the + // broadcast timeout message after each receiver finishes. Instead, we set up + // an initial timeout then kick it down the road a little further as needed + // when it expires. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Premature timeout [" + + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for " + + timeoutTime); + setBroadcastTimeoutLocked(timeoutTime); + return; + } + } + + Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver + + ", started " + (now - r.receiverTime) + "ms ago"); + r.receiverTime = now; + r.anrCount++; + + // Current receiver has passed its expiration date. + if (r.nextReceiver <= 0) { + Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0"); + return; + } + + ProcessRecord app = null; + String anrMessage = null; + + Object curReceiver = r.receivers.get(r.nextReceiver-1); + Slog.w(TAG, "Receiver during timeout: " + curReceiver); + logBroadcastReceiverDiscardLocked(r); + if (curReceiver instanceof BroadcastFilter) { + BroadcastFilter bf = (BroadcastFilter)curReceiver; + if (bf.receiverList.pid != 0 + && bf.receiverList.pid != MY_PID) { + synchronized (ActivityManagerService.this.mPidsSelfLocked) { + app = ActivityManagerService.this.mPidsSelfLocked.get( + bf.receiverList.pid); + } + } + } else { + app = r.curApp; + } + + if (app != null) { + anrMessage = "Broadcast of " + r.intent.toString(); + } + + if (mPendingBroadcast == r) { + mPendingBroadcast = null; + } + + // Move on to the next receiver. + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + scheduleBroadcastsLocked(); + + if (anrMessage != null) { + // Post the ANR to the handler since we do not want to process ANRs while + // potentially holding our lock. + mHandler.post(new AppNotResponding(app, anrMessage)); + } + } + + private final void addBroadcastToHistoryLocked(BroadcastRecord r) { + if (r.callingUid < 0) { + // This was from a registerReceiver() call; ignore it. + return; + } + System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1, + MAX_BROADCAST_HISTORY-1); + r.finishTime = SystemClock.uptimeMillis(); + mBroadcastHistory[0] = r; + } + + final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { + if (r.nextReceiver > 0) { + Object curReceiver = r.receivers.get(r.nextReceiver-1); + if (curReceiver instanceof BroadcastFilter) { + BroadcastFilter bf = (BroadcastFilter) curReceiver; + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver - 1, + System.identityHashCode(bf)); + } else { + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver - 1, + ((ResolveInfo)curReceiver).toString()); + } + } else { + Slog.w(TAG, "Discarding broadcast before first receiver is invoked: " + + r); + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver, + "NONE"); + } + } + + final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll, String dumpPackage, boolean needSep) { + if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 + || mPendingBroadcast != null) { + boolean printed = false; + for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { + BroadcastRecord br = mParallelBroadcasts.get(i); + if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + needSep = false; + } + printed = true; + pw.println(" Active broadcasts [" + mQueueName + "]:"); + } + pw.println(" Broadcast #" + i + ":"); + br.dump(pw, " "); + } + printed = false; + needSep = true; + for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) { + BroadcastRecord br = mOrderedBroadcasts.get(i); + if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + } + needSep = true; + pw.println(" Active ordered broadcasts [" + mQueueName + "]:"); + } + pw.println(" Ordered Broadcast #" + i + ":"); + mOrderedBroadcasts.get(i).dump(pw, " "); + } + if (dumpPackage == null || (mPendingBroadcast != null + && dumpPackage.equals(mPendingBroadcast.callerPackage))) { + if (needSep) { + pw.println(); + } + pw.println(" Pending broadcast [" + mQueueName + "]:"); + if (mPendingBroadcast != null) { + mPendingBroadcast.dump(pw, " "); + } else { + pw.println(" (null)"); + } + needSep = true; + } + } + + boolean printed = false; + for (int i=0; i<MAX_BROADCAST_HISTORY; i++) { + BroadcastRecord r = mBroadcastHistory[i]; + if (r == null) { + break; + } + if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + } + needSep = true; + pw.println(" Historical broadcasts [" + mQueueName + "]:"); + printed = true; + } + if (dumpAll) { + pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); + r.dump(pw, " "); + } else { + if (i >= 50) { + pw.println(" ..."); + break; + } + pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r); + } + } + + return needSep; + } + } + + final BroadcastQueue mFgBroadcastQueue = new BroadcastQueue("foreground", BROADCAST_FG_TIMEOUT); + final BroadcastQueue mBgBroadcastQueue = new BroadcastQueue("background", BROADCAST_BG_TIMEOUT); + // Convenient for easy iteration over the queues. Foreground is first + // so that dispatch of foreground broadcasts gets precedence. + final BroadcastQueue[] mBroadcastQueues = { mFgBroadcastQueue, mBgBroadcastQueue }; + + BroadcastQueue broadcastQueueForIntent(Intent intent) { + final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0; + if (DEBUG_BACKGROUND_BROADCAST) { + Slog.i(TAG, "Broadcast intent " + intent + " on " + + (isFg ? "foreground" : "background") + + " queue"); + } + return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue; + } + + BroadcastRecord broadcastRecordForReceiverLocked(IBinder receiver) { + for (BroadcastQueue queue : mBroadcastQueues) { + BroadcastRecord r = queue.getMatchingOrderedReceiver(receiver); + if (r != null) { + return r; + } + } + return null; + } /** * Activity we have told the window manager to have key focus. @@ -456,25 +1264,6 @@ public final class ActivityManagerService extends ActivityManagerNative private final StringBuilder mStrictModeBuffer = new StringBuilder(); /** - * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler. - */ - private boolean mPendingBroadcastTimeoutMessage; - - /** - * Intent broadcast that we have tried to start, but are - * waiting for its application's process to be created. We only - * need one (instead of a list) because we always process broadcasts - * one at a time, so no others can be started while waiting for this - * one. - */ - BroadcastRecord mPendingBroadcast = null; - - /** - * The receiver index that is pending, to restart the broadcast if needed. - */ - int mPendingBroadcastRecvIndex; - - /** * Keeps track of all IIntentReceivers that have been registered for * broadcasts. Hash keys are the receiver IBinder, hash value is * a ReceiverList. @@ -513,17 +1302,7 @@ public final class ActivityManagerService extends ActivityManagerNative final HashMap<String, ArrayList<Intent>> mStickyBroadcasts = new HashMap<String, ArrayList<Intent>>(); - /** - * All currently running services. - */ - final HashMap<ComponentName, ServiceRecord> mServices = - new HashMap<ComponentName, ServiceRecord>(); - - /** - * All currently running services indexed by the Intent used to start them. - */ - final HashMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = - new HashMap<Intent.FilterComparison, ServiceRecord>(); + final ServiceMap mServiceMap = new ServiceMap(); /** * All currently bound service connections. Keys are the IBinder of @@ -589,6 +1368,8 @@ public final class ActivityManagerService extends ActivityManagerNative final HashMap<ComponentName, ContentProviderRecord> mProvidersByClass = new HashMap<ComponentName, ContentProviderRecord>(); + final ProviderMap mProviderMap = new ProviderMap(); + /** * List of content providers who have clients waiting for them. The * application is currently being launched and the provider will be @@ -619,6 +1400,7 @@ public final class ActivityManagerService extends ActivityManagerNative uid = _uid; } } + private static ThreadLocal<Identity> sCallerIdentity = new ThreadLocal<Identity>(); /** @@ -910,11 +1692,12 @@ public final class ActivityManagerService extends ActivityManagerNative Intent intent = new Intent("android.intent.action.ANR"); if (!mProcessesReady) { - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND); } broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, - false, false, MY_PID, Process.SYSTEM_UID); + false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); Dialog d = new AppNotRespondingDialog(ActivityManagerService.this, mContext, proc, (ActivityRecord)data.get("activity")); @@ -987,11 +1770,13 @@ public final class ActivityManagerService extends ActivityManagerNative case BROADCAST_INTENT_MSG: { if (DEBUG_BROADCAST) Slog.v( TAG, "Received BROADCAST_INTENT_MSG"); - processNextBroadcast(true); + BroadcastQueue queue = (BroadcastQueue) msg.obj; + queue.processNextBroadcast(true); } break; case BROADCAST_TIMEOUT_MSG: { + final BroadcastQueue queue = (BroadcastQueue) msg.obj; synchronized (ActivityManagerService.this) { - broadcastTimeoutLocked(true); + queue.broadcastTimeoutLocked(true); } } break; case SERVICE_TIMEOUT_MSG: { @@ -1811,6 +2596,7 @@ public final class ActivityManagerService extends ActivityManagerNative processName); return procs != null ? procs.valueAt(0) : null; } + // uid = applyUserId(uid); ProcessRecord proc = mProcessNames.get(processName, uid); return proc; } @@ -1940,6 +2726,7 @@ public final class ActivityManagerService extends ActivityManagerNative try { int uid = app.info.uid; + int[] gids = null; try { gids = mContext.getPackageManager().getPackageGids( @@ -2051,7 +2838,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean startHomeActivityLocked() { + boolean startHomeActivityLocked(int userId) { if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL && mTopAction == null) { // We are running in factory test mode, but unable to find @@ -2074,6 +2861,8 @@ public final class ActivityManagerService extends ActivityManagerNative aInfo.applicationInfo.packageName, aInfo.name)); // Don't do this if the home app is currently being // instrumented. + aInfo = new ActivityInfo(aInfo); + aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId); ProcessRecord app = getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid); if (app == null || app.instrumentationClass == null) { @@ -2082,11 +2871,10 @@ public final class ActivityManagerService extends ActivityManagerNative null, null, 0, 0, 0, false, false, null); } } - - + return true; } - + /** * Starts the "new version setup screen" if appropriate. */ @@ -2250,10 +3038,23 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + int userId = 0; + if (intent.getCategories() != null && intent.getCategories().contains(Intent.CATEGORY_HOME)) { + // Requesting home, set the identity to the current user + // HACK! + userId = mCurrentUserId; + } else { + // TODO: Fix this in a better way - calls coming from SystemUI should probably carry + // the current user's userId + if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) { + userId = 0; + } else { + userId = Binder.getOrigCallingUser(); + } + } return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler, - null, null); + grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, + debug, profileFile, profileFd, autoStopProfiler, null, null, userId); } public final WaitResult startActivityAndWait(IApplicationThread caller, @@ -2262,10 +3063,11 @@ public final class ActivityManagerService extends ActivityManagerNative String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { WaitResult res = new WaitResult(); + int userId = Binder.getOrigCallingUser(); mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler, - res, null); + res, null, userId); return res; } @@ -2274,9 +3076,11 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, + int ret = mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, null, null, false, null, config); + requestCode, onlyIfNeeded, + debug, null, null, false, null, config, Binder.getOrigCallingUser()); + return ret; } public int startActivityIntentSender(IApplicationThread caller, @@ -2304,9 +3108,9 @@ public final class ActivityManagerService extends ActivityManagerNative mAppSwitchesAllowedTime = 0; } } - - return pir.sendInner(0, fillInIntent, resolvedType, null, + int ret = pir.sendInner(0, fillInIntent, resolvedType, null, null, resultTo, resultWho, requestCode, flagsMask, flagsValues); + return ret; } public boolean startNextMatchingActivity(IBinder callingActivity, @@ -2409,20 +3213,24 @@ public final class ActivityManagerService extends ActivityManagerNative // This is so super not safe, that only the system (or okay root) // can do it. + int userId = Binder.getOrigCallingUser(); final int callingUid = Binder.getCallingUid(); if (callingUid != 0 && callingUid != Process.myUid()) { throw new SecurityException( "startActivityInPackage only available to the system"); } - return mMainStack.startActivityMayWait(null, uid, intent, resolvedType, + int ret = mMainStack.startActivityMayWait(null, uid, intent, resolvedType, null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, - null, null, false, null, null); + null, null, false, null, null, userId); + return ret; } public final int startActivities(IApplicationThread caller, Intent[] intents, String[] resolvedTypes, IBinder resultTo) { - return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo); + int ret = mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo, + Binder.getOrigCallingUser()); + return ret; } public final int startActivitiesInPackage(int uid, @@ -2435,8 +3243,9 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException( "startActivityInPackage only available to the system"); } - - return mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo); + int ret = mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo, + UserId.getUserId(uid)); + return ret; } final void addRecentTaskLocked(TaskRecord task) { @@ -2448,8 +3257,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Remove any existing entries that are the same kind of task. for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); - if ((task.affinity != null && task.affinity.equals(tr.affinity)) - || (task.intent != null && task.intent.filterEquals(tr.intent))) { + if (task.userId == tr.userId + && ((task.affinity != null && task.affinity.equals(tr.affinity)) + || (task.intent != null && task.intent.filterEquals(tr.intent)))) { mRecentTasks.remove(i); i--; N--; @@ -2808,7 +3618,6 @@ public final class ActivityManagerService extends ActivityManagerNative private final int getLRURecordIndexForAppLocked(IApplicationThread thread) { IBinder threadBinder = thread.asBinder(); - // Find the application record. for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord rec = mLruProcesses.get(i); @@ -3185,7 +3994,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public boolean clearApplicationUserData(final String packageName, - final IPackageDataObserver observer) { + final IPackageDataObserver observer, final int userId) { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); @@ -3220,7 +4029,7 @@ public final class ActivityManagerService extends ActivityManagerNative Uri.fromParts("package", packageName, null)); intent.putExtra(Intent.EXTRA_UID, pkgUid); broadcastIntentInPackage("android", Process.SYSTEM_UID, intent, - null, null, 0, null, null, null, false, false); + null, null, 0, null, null, null, false, false, userId); } catch (RemoteException e) { } } finally { @@ -3315,7 +4124,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - + final int userId = Binder.getOrigCallingUser(); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); @@ -3323,6 +4132,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { try { pkgUid = pm.getPackageUid(packageName); + // Convert the uid to the one for the calling user + pkgUid = UserId.getUid(userId, pkgUid); } catch (RemoteException e) { } if (pkgUid == -1) { @@ -3405,7 +4216,7 @@ public final class ActivityManagerService extends ActivityManagerNative } broadcastIntentLocked(null, null, intent, null, - null, 0, null, null, null, false, false, -1, uid); + null, 0, null, null, null, false, false, -1, uid, 0 /* TODO: Verify */); } Binder.restoreCallingIdentity(origId); } @@ -3465,7 +4276,8 @@ public final class ActivityManagerService extends ActivityManagerNative intent.putExtra(Intent.EXTRA_UID, uid); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, - false, false, MY_PID, Process.SYSTEM_UID); + false, false, + MY_PID, Process.SYSTEM_UID, UserId.getUserId(uid)); } private final boolean killPackageProcessesLocked(String packageName, int uid, @@ -3569,7 +4381,8 @@ public final class ActivityManagerService extends ActivityManagerNative } ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord service : mServices.values()) { + int userId = UserId.getUserId(uid); + for (ServiceRecord service : mServiceMap.getAllServices(userId)) { if (service.packageName.equals(name) && (service.app == null || evenPersistent || !service.app.persistent)) { if (!doit) { @@ -3708,12 +4521,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Can't happen; the backup manager is local } } - if (mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid) { + if (isPendingBroadcastProcessLocked(pid)) { Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping"); - mPendingBroadcast.state = BroadcastRecord.IDLE; - mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; - mPendingBroadcast = null; - scheduleBroadcastsLocked(); + skipPendingBroadcastLocked(pid); } } else { Slog.w(TAG, "Spurious process start timeout - pid not known for " + app); @@ -3912,23 +4722,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // Check if the next broadcast receiver is in this process... - BroadcastRecord br = mPendingBroadcast; - if (!badApp && br != null && br.curApp == app) { + // Check if a next-broadcast receiver is in this process... + if (!badApp && isPendingBroadcastProcessLocked(pid)) { try { - mPendingBroadcast = null; - processCurBroadcastLocked(br, app); - didSomething = true; + didSomething = sendPendingBroadcastsLocked(app); } catch (Exception e) { - Slog.w(TAG, "Exception in new application when starting receiver " - + br.curComponent.flattenToShortString(), e); + // If the app died trying to launch the receiver we declare it 'bad' badApp = true; - logBroadcastReceiverDiscardLocked(br); - finishReceiverLocked(br.receiver, br.resultCode, br.resultData, - br.resultExtras, br.resultAbort, true); - scheduleBroadcastsLocked(); - // We need to reset the state if we fails to start the receiver. - br.state = BroadcastRecord.IDLE; } } @@ -4046,11 +4846,12 @@ public final class ActivityManagerService extends ActivityManagerNative // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); SystemProperties.set("dev.bootcomplete", "1"); + /* TODO: Send this to all users that are to be logged in on startup */ broadcastIntentLocked(null, null, new Intent(Intent.ACTION_BOOT_COMPLETED, null), null, null, 0, null, null, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - false, false, MY_PID, Process.SYSTEM_UID); + false, false, MY_PID, Process.SYSTEM_UID, Binder.getOrigCallingUser()); } } } @@ -4191,7 +4992,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { int uid = AppGlobals.getPackageManager() .getPackageUid(packageName); - if (uid != Binder.getCallingUid()) { + if (UserId.getAppId(callingUid) != uid) { String msg = "Permission Denial: getIntentSender() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() @@ -4202,7 +5003,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } - return getIntentSenderLocked(type, packageName, callingUid, + if (DEBUG_MU) + Slog.i(TAG_MU, "Getting intent sender for origCallingUid=" + + Binder.getOrigCallingUid()); + return getIntentSenderLocked(type, packageName, Binder.getOrigCallingUid(), token, resultWho, requestCode, intents, resolvedTypes, flags); } catch (RemoteException e) { @@ -4214,6 +5018,8 @@ public final class ActivityManagerService extends ActivityManagerNative IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { + if (DEBUG_MU) + Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid); ActivityRecord activity = null; if (type == INTENT_SENDER_ACTIVITY_RESULT) { activity = mMainStack.isInStackLocked(token); @@ -4457,7 +5263,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // If there is a uid that owns whatever is being accessed, it has // blanket access to it regardless of the permissions it requires. - if (owningUid >= 0 && uid == owningUid) { + if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) { return PackageManager.PERMISSION_GRANTED; } // If the target is not exported, then nobody else can get to it. @@ -4491,7 +5297,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (permission == null) { return PackageManager.PERMISSION_DENIED; } - return checkComponentPermission(permission, pid, uid, -1, true); + return checkComponentPermission(permission, pid, UserId.getAppId(uid), -1, true); } /** @@ -4501,7 +5307,7 @@ public final class ActivityManagerService extends ActivityManagerNative int checkCallingPermission(String permission) { return checkPermission(permission, Binder.getCallingPid(), - Binder.getCallingUid()); + UserId.getAppId(Binder.getCallingUid())); } /** @@ -4615,6 +5421,7 @@ public final class ActivityManagerService extends ActivityManagerNative pid = tlsIdentity.pid; } + uid = UserId.getAppId(uid); // Our own process gets to do everything. if (pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; @@ -4657,7 +5464,8 @@ public final class ActivityManagerService extends ActivityManagerNative String name = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(name); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, + UserId.getUserId(callingUid)); if (cpr != null) { pi = cpr.info; } else { @@ -4913,7 +5721,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(authority); + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, + UserId.getUserId(callingUid)); if (cpr != null) { pi = cpr.info; } else { @@ -5007,7 +5816,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(authority); + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, + UserId.getUserId(r.info.uid)); if (cpr != null) { pi = cpr.info; } else { @@ -5246,6 +6056,12 @@ public final class ActivityManagerService extends ActivityManagerNative public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags) { + final int callingUid = Binder.getCallingUid(); + // If it's the system uid asking, then use the current user id. + // TODO: Make sure that there aren't any other legitimate calls from the system uid that + // require the entire list. + final int callingUserId = callingUid == Process.SYSTEM_UID + ? mCurrentUserId : UserId.getUserId(callingUid); synchronized (this) { enforceCallingPermission(android.Manifest.permission.GET_TASKS, "getRecentTasks()"); @@ -5258,11 +6074,14 @@ public final class ActivityManagerService extends ActivityManagerNative maxNum < N ? maxNum : N); for (int i=0; i<N && maxNum > 0; i++) { TaskRecord tr = mRecentTasks.get(i); + // Only add calling user's recent tasks + if (tr.userId != callingUserId) continue; // Return the entry if desired by the caller. We always return // the first entry, because callers always expect this to be the - // forground app. We may filter others if the caller has + // foreground app. We may filter others if the caller has // not supplied RECENT_WITH_EXCLUDED and there is some reason // we should exclude the entry. + if (i == 0 || ((flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0) || (tr.intent == null) @@ -5351,7 +6170,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Find any running services associated with this app. ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord sr : mServices.values()) { + for (ServiceRecord sr : mServiceMap.getAllServices(root.userId)) { if (sr.packageName.equals(component.getPackageName())) { services.add(sr); } @@ -5722,17 +6541,23 @@ public final class ActivityManagerService extends ActivityManagerNative STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { } + if (DEBUG_MU) + Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.info.uid); + int userId = UserId.getUserId(app.info.uid); if (providers != null) { final int N = providers.size(); for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); + ComponentName comp = new ComponentName(cpi.packageName, cpi.name); - ContentProviderRecord cpr = mProvidersByClass.get(comp); + ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId); if (cpr == null) { cpr = new ContentProviderRecord(cpi, app.info, comp); - mProvidersByClass.put(comp, cpr); + mProviderMap.putProviderByClass(comp, cpr); } + if (DEBUG_MU) + Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid); app.pubProviders.put(cpi.name, cpr); app.addPackage(cpi.applicationInfo.packageName); ensurePackageDexOpt(cpi.applicationInfo.packageName); @@ -5842,8 +6667,8 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } - private final ContentProviderHolder getContentProviderImpl( - IApplicationThread caller, String name) { + private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, + String name) { ContentProviderRecord cpr; ProviderInfo cpi = null; @@ -5860,7 +6685,8 @@ public final class ActivityManagerService extends ActivityManagerNative } // First check if this content provider has been published... - cpr = mProvidersByName.get(name); + int userId = UserId.getUserId(r != null ? r.info.uid : Binder.getCallingUid()); + cpr = mProviderMap.getProviderByName(name, userId); boolean providerRunning = cpr != null; if (providerRunning) { cpi = cpr.info; @@ -5945,6 +6771,9 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, + Binder.getOrigCallingUser()); + String msg; if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) { throw new SecurityException(msg); @@ -5960,7 +6789,7 @@ public final class ActivityManagerService extends ActivityManagerNative } ComponentName comp = new ComponentName(cpi.packageName, cpi.name); - cpr = mProvidersByClass.get(comp); + cpr = mProviderMap.getProviderByClass(comp, Binder.getOrigCallingUser()); final boolean firstClass = cpr == null; if (firstClass) { try { @@ -5974,6 +6803,7 @@ public final class ActivityManagerService extends ActivityManagerNative + cpi.name); return null; } + ai = getAppInfoForUser(ai, Binder.getOrigCallingUser()); cpr = new ContentProviderRecord(cpi, ai, comp); } catch (RemoteException ex) { // pm is in same process, this will never happen. @@ -6042,9 +6872,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Make sure the provider is published (the same provider class // may be published under multiple names). if (firstClass) { - mProvidersByClass.put(comp, cpr); + mProviderMap.putProviderByClass(comp, cpr); } - mProvidersByName.put(name, cpr); + mProviderMap.putProviderByName(name, cpr); incProviderCount(r, cpr); } } @@ -6063,6 +6893,10 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } try { + if (DEBUG_MU) { + Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp=" + + cpr.launchingApp); + } cpr.wait(); } catch (InterruptedException ex) { } @@ -6080,7 +6914,8 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - return getContentProviderImpl(caller, name); + ContentProviderHolder contentProvider = getContentProviderImpl(caller, name); + return contentProvider; } private ContentProviderHolder getContentProviderExternal(String name) { @@ -6093,7 +6928,8 @@ public final class ActivityManagerService extends ActivityManagerNative */ public void removeContentProvider(IApplicationThread caller, String name) { synchronized (this) { - ContentProviderRecord cpr = mProvidersByName.get(name); + int userId = UserId.getUserId(Binder.getCallingUid()); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId); if(cpr == null) { // remove from mProvidersByClass if (DEBUG_PROVIDER) Slog.v(TAG, name + @@ -6108,8 +6944,11 @@ public final class ActivityManagerService extends ActivityManagerNative } //update content provider record entry info ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name); - ContentProviderRecord localCpr = mProvidersByClass.get(comp); - if (localCpr.proc == r) { + ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId); + if (DEBUG_PROVIDER) Slog.v(TAG, "Removing provider requested by " + + r.info.processName + " from process " + + localCpr.appInfo.processName); + if (localCpr.launchingApp == r) { //should not happen. taken care of as a local provider Slog.w(TAG, "removeContentProvider called on local provider: " + cpr.info.name + " in process " + r.processName); @@ -6124,7 +6963,8 @@ public final class ActivityManagerService extends ActivityManagerNative private void removeContentProviderExternal(String name) { synchronized (this) { - ContentProviderRecord cpr = mProvidersByName.get(name); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, + Binder.getOrigCallingUser()); if(cpr == null) { //remove from mProvidersByClass if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list"); @@ -6133,7 +6973,8 @@ public final class ActivityManagerService extends ActivityManagerNative //update content provider record entry info ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name); - ContentProviderRecord localCpr = mProvidersByClass.get(comp); + ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, + Binder.getOrigCallingUser()); localCpr.externals--; if (localCpr.externals < 0) { Slog.e(TAG, "Externals < 0 for content provider " + localCpr); @@ -6150,6 +6991,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { final ProcessRecord r = getRecordForAppLocked(caller); + if (DEBUG_MU) + Slog.v(TAG_MU, "ProcessRecord uid = " + r.info.uid); if (r == null) { throw new SecurityException( "Unable to find app for caller " + caller @@ -6166,12 +7009,14 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } ContentProviderRecord dst = r.pubProviders.get(src.info.name); + if (DEBUG_MU) + Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid); if (dst != null) { ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name); - mProvidersByClass.put(comp, dst); + mProviderMap.putProviderByClass(comp, dst); String names[] = dst.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - mProvidersByName.put(names[j], dst); + mProviderMap.putProviderByName(names[j], dst); } int NL = mLaunchingProviders.size(); @@ -6916,8 +7761,10 @@ public final class ActivityManagerService extends ActivityManagerNative }; } Slog.i(TAG, "Sending system update to: " + intent.getComponent()); + /* TODO: Send this to all users */ broadcastIntentLocked(null, null, intent, null, finisher, - 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID); + 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID, + Process.SYSTEM_UID); if (finisher != null) { mWaitingUpdate = true; } @@ -7227,28 +8074,8 @@ public final class ActivityManagerService extends ActivityManagerNative } void skipCurrentReceiverLocked(ProcessRecord app) { - boolean reschedule = false; - BroadcastRecord r = app.curReceiver; - if (r != null) { - // The current broadcast is waiting for this app's receiver - // to be finished. Looks like that's not going to happen, so - // let the broadcast continue. - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - reschedule = true; - } - r = mPendingBroadcast; - if (r != null && r.curApp == app) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "skip & discard pending app " + r); - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - reschedule = true; - } - if (reschedule) { - scheduleBroadcastsLocked(); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.skipCurrentReceiverLocked(app); } } @@ -8563,8 +9390,14 @@ public final class ActivityManagerService extends ActivityManagerNative if ("all".equals(name)) { synchronized (this) { - for (ServiceRecord r1 : mServices.values()) { - services.add(r1); + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + for (ServiceRecord r1 : mServiceMap.getAllServices(user.id)) { + services.add(r1); + } + } + } catch (RemoteException re) { } } } else { @@ -8582,18 +9415,24 @@ public final class ActivityManagerService extends ActivityManagerNative } synchronized (this) { - for (ServiceRecord r1 : mServices.values()) { - if (componentName != null) { - if (r1.name.equals(componentName)) { - services.add(r1); - } - } else if (name != null) { - if (r1.name.flattenToString().contains(name)) { - services.add(r1); + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + for (ServiceRecord r1 : mServiceMap.getAllServices(user.id)) { + if (componentName != null) { + if (r1.name.equals(componentName)) { + services.add(r1); + } + } else if (name != null) { + if (r1.name.flattenToString().contains(name)) { + services.add(r1); + } + } else if (System.identityHashCode(r1) == objectId) { + services.add(r1); + } } - } else if (System.identityHashCode(r1) == objectId) { - services.add(r1); } + } catch (RemoteException re) { } } } @@ -8971,84 +9810,11 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = true; } } - - if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 - || mPendingBroadcast != null) { - boolean printed = false; - for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { - BroadcastRecord br = mParallelBroadcasts.get(i); - if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Active broadcasts:"); - } - pw.println(" Broadcast #" + i + ":"); - br.dump(pw, " "); - } - printed = false; - for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) { - BroadcastRecord br = mOrderedBroadcasts.get(i); - if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Active ordered broadcasts:"); - } - pw.println(" Ordered Broadcast #" + i + ":"); - mOrderedBroadcasts.get(i).dump(pw, " "); - } - if (dumpPackage == null || (mPendingBroadcast != null - && dumpPackage.equals(mPendingBroadcast.callerPackage))) { - if (needSep) { - pw.println(); - } - pw.println(" Pending broadcast:"); - if (mPendingBroadcast != null) { - mPendingBroadcast.dump(pw, " "); - } else { - pw.println(" (null)"); - } - needSep = true; - } - } - boolean printed = false; - for (int i=0; i<MAX_BROADCAST_HISTORY; i++) { - BroadcastRecord r = mBroadcastHistory[i]; - if (r == null) { - break; - } - if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Historical broadcasts:"); - printed = true; - } - if (dumpAll) { - pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); - r.dump(pw, " "); - } else { - if (i >= 50) { - pw.println(" ..."); - break; - } - pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r); - } + for (BroadcastQueue q : mBroadcastQueues) { + needSep = q.dumpLocked(fd, pw, args, opti, dumpAll, dumpPackage, needSep); } + needSep = true; if (mStickyBroadcasts != null && dumpPackage == null) { @@ -9085,7 +9851,10 @@ public final class ActivityManagerService extends ActivityManagerNative if (dumpAll) { pw.println(); - pw.println(" mBroadcastsScheduled=" + mBroadcastsScheduled); + for (BroadcastQueue queue : mBroadcastQueues) { + pw.println(" mBroadcastsScheduled [" + queue.mQueueName + "]=" + + queue.mBroadcastsScheduled); + } pw.println(" mHandler:"); mHandler.dump(new PrintWriterPrinter(pw), " "); needSep = true; @@ -9105,75 +9874,87 @@ public final class ActivityManagerService extends ActivityManagerNative matcher.build(args, opti); pw.println("ACTIVITY MANAGER SERVICES (dumpsys activity services)"); - if (mServices.size() > 0) { - boolean printed = false; - long nowReal = SystemClock.elapsedRealtime(); - Iterator<ServiceRecord> it = mServices.values().iterator(); - needSep = false; - while (it.hasNext()) { - ServiceRecord r = it.next(); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { - continue; - } - if (!printed) { - pw.println(" Active services:"); - printed = true; - } - if (needSep) { - pw.println(); - } - pw.print(" * "); pw.println(r); - if (dumpAll) { - r.dump(pw, " "); - needSep = true; - } else { - pw.print(" app="); pw.println(r.app); - pw.print(" created="); - TimeUtils.formatDuration(r.createTime, nowReal, pw); - pw.print(" started="); pw.print(r.startRequested); - pw.print(" connections="); pw.println(r.connections.size()); - if (r.connections.size() > 0) { - pw.println(" Connections:"); - for (ArrayList<ConnectionRecord> clist : r.connections.values()) { - for (int i=0; i<clist.size(); i++) { - ConnectionRecord conn = clist.get(i); - pw.print(" "); - pw.print(conn.binding.intent.intent.getIntent().toShortString( - false, false, false)); - pw.print(" -> "); - ProcessRecord proc = conn.binding.client; - pw.println(proc != null ? proc.toShortString() : "null"); + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + if (mServiceMap.getAllServices(user.id).size() > 0) { + boolean printed = false; + long nowReal = SystemClock.elapsedRealtime(); + Iterator<ServiceRecord> it = mServiceMap.getAllServices( + user.id).iterator(); + needSep = false; + while (it.hasNext()) { + ServiceRecord r = it.next(); + if (!matcher.match(r, r.name)) { + continue; + } + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (!printed) { + pw.println(" Active services:"); + printed = true; + } + if (needSep) { + pw.println(); + } + pw.print(" * "); + pw.println(r); + if (dumpAll) { + r.dump(pw, " "); + needSep = true; + } else { + pw.print(" app="); + pw.println(r.app); + pw.print(" created="); + TimeUtils.formatDuration(r.createTime, nowReal, pw); + pw.print(" started="); + pw.print(r.startRequested); + pw.print(" connections="); + pw.println(r.connections.size()); + if (r.connections.size() > 0) { + pw.println(" Connections:"); + for (ArrayList<ConnectionRecord> clist : r.connections.values()) { + for (int i = 0; i < clist.size(); i++) { + ConnectionRecord conn = clist.get(i); + pw.print(" "); + pw.print(conn.binding.intent.intent.getIntent() + .toShortString(false, false, false)); + pw.print(" -> "); + ProcessRecord proc = conn.binding.client; + pw.println(proc != null ? proc.toShortString() : "null"); + } + } } } - } - } - if (dumpClient && r.app != null && r.app.thread != null) { - pw.println(" Client:"); - pw.flush(); - try { - TransferPipe tp = new TransferPipe(); - try { - r.app.thread.dumpService( - tp.getWriteFd().getFileDescriptor(), r, args); - tp.setBufferPrefix(" "); - // Short timeout, since blocking here can - // deadlock with the application. - tp.go(fd, 2000); - } finally { - tp.kill(); + if (dumpClient && r.app != null && r.app.thread != null) { + pw.println(" Client:"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), + r, args); + tp.setBufferPrefix(" "); + // Short timeout, since blocking here can + // deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(" Failure while dumping the service: " + e); + } catch (RemoteException e) { + pw.println(" Got a RemoteException while dumping the service"); + } + needSep = true; } - } catch (IOException e) { - pw.println(" Failure while dumping the service: " + e); - } catch (RemoteException e) { - pw.println(" Got a RemoteException while dumping the service"); } - needSep = true; + needSep = printed; } } - needSep = printed; + } catch (RemoteException re) { + } if (mPendingServices.size() > 0) { @@ -9283,76 +10064,8 @@ public final class ActivityManagerService extends ActivityManagerNative matcher.build(args, opti); pw.println("ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)"); - if (mProvidersByClass.size() > 0) { - boolean printed = false; - Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it - = mProvidersByClass.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<ComponentName, ContentProviderRecord> e = it.next(); - ContentProviderRecord r = e.getValue(); - ComponentName comp = e.getKey(); - String cls = comp.getClassName(); - int end = cls.lastIndexOf('.'); - if (end > 0 && end < (cls.length()-2)) { - cls = cls.substring(end+1); - } - if (!matcher.match(r, comp)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(comp.getPackageName())) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Published content providers (by class):"); - printed = true; - } - pw.print(" * "); pw.print(cls); pw.print(" ("); - pw.print(comp.flattenToShortString()); pw.println(")"); - if (dumpAll) { - r.dump(pw, " "); - } else { - if (r.proc != null) { - pw.print(" "); pw.println(r.proc); - } else { - pw.println(); - } - if (r.clients.size() > 0) { - pw.println(" Clients:"); - for (ProcessRecord cproc : r.clients) { - pw.print(" - "); pw.println(cproc); - } - } - } - } - } - - if (dumpAll) { - if (mProvidersByName.size() > 0) { - boolean printed = false; - Iterator<Map.Entry<String, ContentProviderRecord>> it - = mProvidersByName.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<String, ContentProviderRecord> e = it.next(); - ContentProviderRecord r = e.getValue(); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.name.getPackageName())) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Authority to provider mappings:"); - printed = true; - } - pw.print(" "); pw.print(e.getKey()); pw.println(":"); - pw.print(" "); pw.println(r); - } - } - } + + mProviderMap.dumpProvidersLocked(pw, dumpAll); if (mLaunchingProviders.size() > 0) { boolean printed = false; @@ -10238,10 +10951,10 @@ public final class ActivityManagerService extends ActivityManagerNative cpr.notifyAll(); } - mProvidersByClass.remove(cpr.name); + mProviderMap.removeProviderByClass(cpr.name, UserId.getUserId(cpr.uid)); String names[] = cpr.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - mProvidersByName.remove(names[j]); + mProviderMap.removeProviderByName(names[j], UserId.getUserId(cpr.uid)); } Iterator<ProcessRecord> cit = cpr.clients.iterator(); @@ -10517,8 +11230,10 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<ActivityManager.RunningServiceInfo> res = new ArrayList<ActivityManager.RunningServiceInfo>(); - if (mServices.size() > 0) { - Iterator<ServiceRecord> it = mServices.values().iterator(); + int userId = UserId.getUserId(Binder.getCallingUid()); + if (mServiceMap.getAllServices(userId).size() > 0) { + Iterator<ServiceRecord> it + = mServiceMap.getAllServices(userId).iterator(); while (it.hasNext() && res.size() < maxNum) { res.add(makeRunningServiceInfoLocked(it.next())); } @@ -10538,7 +11253,8 @@ public final class ActivityManagerService extends ActivityManagerNative public PendingIntent getRunningServiceControlPanel(ComponentName name) { synchronized (this) { - ServiceRecord r = mServices.get(name); + int userId = UserId.getUserId(Binder.getCallingUid()); + ServiceRecord r = mServiceMap.getServiceByName(name, userId); if (r != null) { for (ArrayList<ConnectionRecord> conn : r.connections.values()) { for (int i=0; i<conn.size(); i++) { @@ -10554,7 +11270,7 @@ public final class ActivityManagerService extends ActivityManagerNative private final ServiceRecord findServiceLocked(ComponentName name, IBinder token) { - ServiceRecord r = mServices.get(name); + ServiceRecord r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); return r == token ? r : null; } @@ -10572,11 +11288,11 @@ public final class ActivityManagerService extends ActivityManagerNative String resolvedType) { ServiceRecord r = null; if (service.getComponent() != null) { - r = mServices.get(service.getComponent()); + r = mServiceMap.getServiceByName(service.getComponent(), Binder.getOrigCallingUser()); } if (r == null) { Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServicesByIntent.get(filter); + r = mServiceMap.getServiceByIntent(filter, Binder.getOrigCallingUser()); } if (r == null) { @@ -10592,7 +11308,7 @@ public final class ActivityManagerService extends ActivityManagerNative ComponentName name = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); - r = mServices.get(name); + r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); } catch (RemoteException ex) { // pm is in same process, this will never happen. } @@ -10639,11 +11355,17 @@ public final class ActivityManagerService extends ActivityManagerNative private ServiceLookupResult retrieveServiceLocked(Intent service, String resolvedType, int callingPid, int callingUid) { ServiceRecord r = null; + if (DEBUG_SERVICE) + Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType + + " origCallingUid=" + callingUid); + if (service.getComponent() != null) { - r = mServices.get(service.getComponent()); + r = mServiceMap.getServiceByName(service.getComponent(), Binder.getOrigCallingUser()); + } + if (r == null) { + Intent.FilterComparison filter = new Intent.FilterComparison(service); + r = mServiceMap.getServiceByIntent(filter, Binder.getOrigCallingUser()); } - Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServicesByIntent.get(filter); if (r == null) { try { ResolveInfo rInfo = @@ -10656,12 +11378,16 @@ public final class ActivityManagerService extends ActivityManagerNative ": not found"); return null; } - + if (Binder.getOrigCallingUser() > 0) { + sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, + Binder.getOrigCallingUser()); + } ComponentName name = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); - r = mServices.get(name); + r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); if (r == null) { - filter = new Intent.FilterComparison(service.cloneFilter()); + Intent.FilterComparison filter = new Intent.FilterComparison( + service.cloneFilter()); ServiceRestarter res = new ServiceRestarter(); BatteryStatsImpl.Uid.Pkg.Serv ss = null; BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); @@ -10672,8 +11398,8 @@ public final class ActivityManagerService extends ActivityManagerNative } r = new ServiceRecord(this, ss, name, filter, sInfo, res); res.setService(r); - mServices.put(name, r); - mServicesByIntent.put(filter, r); + mServiceMap.putServiceByName(name, UserId.getUserId(r.appInfo.uid), r); + mServiceMap.putServiceByIntent(filter, UserId.getUserId(r.appInfo.uid), r); // Make sure this component isn't in the pending list. int N = mPendingServices.size(); @@ -10820,7 +11546,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.thread == null) { throw new RemoteException(); } - + if (DEBUG_MU) + Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid + + ", ProcessRecord.uid = " + app.info.uid); r.app = app; r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); @@ -11017,6 +11745,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String appName = r.processName; ProcessRecord app = getProcessRecordLocked(appName, r.appInfo.uid); + if (DEBUG_MU) + Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app); if (app != null && app.thread != null) { try { app.addPackage(r.appInfo.packageName); @@ -11121,8 +11851,8 @@ public final class ActivityManagerService extends ActivityManagerNative System.identityHashCode(r), r.shortName, (r.app != null) ? r.app.pid : -1); - mServices.remove(r.name); - mServicesByIntent.remove(r.intent); + mServiceMap.removeServiceByName(r.name, r.userId); + mServiceMap.removeServiceByIntent(r.intent, r.userId); r.totalRestartCount = 0; unscheduleServiceRestartLocked(r); @@ -11236,6 +11966,8 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Intent"); } + if (DEBUG_SERVICE) + Slog.v(TAG, "startService: " + service + " type=" + resolvedType); synchronized(this) { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -11250,6 +11982,8 @@ public final class ActivityManagerService extends ActivityManagerNative ComponentName startServiceInPackage(int uid, Intent service, String resolvedType) { synchronized(this) { + if (DEBUG_SERVICE) + Slog.v(TAG, "startServiceInPackage: " + service + " type=" + resolvedType); final long origId = Binder.clearCallingIdentity(); ComponentName res = startServiceLocked(null, service, resolvedType, -1, uid); @@ -11453,6 +12187,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_SERVICE) Slog.v(TAG, "bindService: " + service + " type=" + resolvedType + " conn=" + connection.asBinder() + " flags=0x" + Integer.toHexString(flags)); + if (DEBUG_MU) + Slog.i(TAG_MU, "bindService uid=" + Binder.getCallingUid() + " origUid=" + + Binder.getOrigCallingUid()); final ProcessRecord callerApp = getRecordForAppLocked(caller); if (callerApp == null) { throw new SecurityException( @@ -11495,7 +12232,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, - Binder.getCallingPid(), Binder.getCallingUid()); + Binder.getCallingPid(), Binder.getOrigCallingUid()); if (res == null) { return 0; } @@ -11841,7 +12578,9 @@ public final class ActivityManagerService extends ActivityManagerNative r.callStart = false; } } - + if (DEBUG_MU) + Slog.v(TAG_MU, "before serviceDontExecutingLocked, uid=" + + Binder.getOrigCallingUid()); final long origId = Binder.clearCallingIdentity(); serviceDoneExecutingLocked(r, inStopping); Binder.restoreCallingIdentity(origId); @@ -12072,15 +12811,25 @@ public final class ActivityManagerService extends ActivityManagerNative return cur; } - private final void scheduleBroadcastsLocked() { - if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts: current=" - + mBroadcastsScheduled); + boolean isPendingBroadcastProcessLocked(int pid) { + return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid) + || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid); + } - if (mBroadcastsScheduled) { - return; + void skipPendingBroadcastLocked(int pid) { + Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping"); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.skipPendingBroadcastLocked(pid); + } + } + + // The app just attached; send any pending broadcasts that it should receive + boolean sendPendingBroadcastsLocked(ProcessRecord app) { + boolean didSomething = false; + for (BroadcastQueue queue : mBroadcastQueues) { + didSomething |= queue.sendPendingBroadcastsLocked(app); } - mHandler.sendEmptyMessage(BROADCAST_INTENT_MSG); - mBroadcastsScheduled = true; + return didSomething; } public Intent registerReceiver(IApplicationThread caller, String callerPackage, @@ -12162,13 +12911,12 @@ public final class ActivityManagerService extends ActivityManagerNative int N = allSticky.size(); for (int i=0; i<N; i++) { Intent intent = (Intent)allSticky.get(i); - BroadcastRecord r = new BroadcastRecord(intent, null, + BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, null, null, -1, -1, null, receivers, null, 0, null, null, false, true, true); - if (mParallelBroadcasts.size() == 0) { - scheduleBroadcastsLocked(); - } - mParallelBroadcasts.add(r); + queue.enqueueParallelBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } } @@ -12179,38 +12927,46 @@ public final class ActivityManagerService extends ActivityManagerNative public void unregisterReceiver(IIntentReceiver receiver) { if (DEBUG_BROADCAST) Slog.v(TAG, "Unregister receiver: " + receiver); - boolean doNext = false; + final long origId = Binder.clearCallingIdentity(); + try { + boolean doTrim = false; - synchronized(this) { - ReceiverList rl + synchronized(this) { + ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); - if (rl != null) { - if (rl.curBroadcast != null) { - BroadcastRecord r = rl.curBroadcast; - doNext = finishReceiverLocked( - receiver.asBinder(), r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - } + if (rl != null) { + if (rl.curBroadcast != null) { + BroadcastRecord r = rl.curBroadcast; + final boolean doNext = finishReceiverLocked( + receiver.asBinder(), r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + if (doNext) { + doTrim = true; + r.queue.processNextBroadcast(false); + } + } - if (rl.app != null) { - rl.app.receivers.remove(rl); - } - removeReceiverLocked(rl); - if (rl.linkedToDeath) { - rl.linkedToDeath = false; - rl.receiver.asBinder().unlinkToDeath(rl, 0); + if (rl.app != null) { + rl.app.receivers.remove(rl); + } + removeReceiverLocked(rl); + if (rl.linkedToDeath) { + rl.linkedToDeath = false; + rl.receiver.asBinder().unlinkToDeath(rl, 0); + } } } - } - if (!doNext) { - return; + // If we actually concluded any broadcasts, we might now be able + // to trim the recipients' apps from our working set + if (doTrim) { + trimApplications(); + return; + } + + } finally { + Binder.restoreCallingIdentity(origId); } - - final long origId = Binder.clearCallingIdentity(); - processNextBroadcast(false); - trimApplications(); - Binder.restoreCallingIdentity(origId); } void removeReceiverLocked(ReceiverList rl) { @@ -12237,7 +12993,8 @@ public final class ActivityManagerService extends ActivityManagerNative String callerPackage, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, - boolean ordered, boolean sticky, int callingPid, int callingUid) { + boolean ordered, boolean sticky, int callingPid, int callingUid, + int userId) { intent = new Intent(intent); // By default broadcasts do not go to stopped apps. @@ -12412,10 +13169,11 @@ public final class ActivityManagerService extends ActivityManagerNative if (ai != null) { receivers = new ArrayList(); ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = ai; + ri.activityInfo = getActivityInfoForUser(ai, userId); receivers.add(ri); } } else { + // TODO: Apply userId // Need to resolve the intent to interested receivers... if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { @@ -12440,28 +13198,17 @@ public final class ActivityManagerService extends ActivityManagerNative // If we are not serializing this broadcast, then send the // registered receivers separately so they don't wait for the // components to be launched. - BroadcastRecord r = new BroadcastRecord(intent, callerApp, + final BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, ordered, sticky, false); if (DEBUG_BROADCAST) Slog.v( - TAG, "Enqueueing parallel broadcast " + r - + ": prev had " + mParallelBroadcasts.size()); - boolean replaced = false; - if (replacePending) { - for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { - if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "***** DROPPING PARALLEL: " + intent); - mParallelBroadcasts.set(i, r); - replaced = true; - break; - } - } - } + TAG, "Enqueueing parallel broadcast " + r); + final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r); if (!replaced) { - mParallelBroadcasts.add(r); - scheduleBroadcastsLocked(); + queue.enqueueParallelBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } registeredReceivers = null; NR = 0; @@ -12541,32 +13288,22 @@ public final class ActivityManagerService extends ActivityManagerNative if ((receivers != null && receivers.size() > 0) || resultTo != null) { - BroadcastRecord r = new BroadcastRecord(intent, callerApp, + BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, receivers, resultTo, resultCode, resultData, map, ordered, sticky, false); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r - + ": prev had " + mOrderedBroadcasts.size()); + + ": prev had " + queue.mOrderedBroadcasts.size()); if (DEBUG_BROADCAST) { int seq = r.intent.getIntExtra("seq", -1); Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq); } - boolean replaced = false; - if (replacePending) { - for (int i=mOrderedBroadcasts.size()-1; i>0; i--) { - if (intent.filterEquals(mOrderedBroadcasts.get(i).intent)) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "***** DROPPING ORDERED: " + intent); - mOrderedBroadcasts.set(i, r); - replaced = true; - break; - } - } - } + boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { - mOrderedBroadcasts.add(r); - scheduleBroadcastsLocked(); + queue.enqueueOrderedBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } } @@ -12605,7 +13342,7 @@ public final class ActivityManagerService extends ActivityManagerNative public final int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, - String requiredPermission, boolean serialized, boolean sticky) { + String requiredPermission, boolean serialized, boolean sticky, int userId) { synchronized(this) { intent = verifyBroadcastLocked(intent); @@ -12616,8 +13353,8 @@ public final class ActivityManagerService extends ActivityManagerNative int res = broadcastIntentLocked(callerApp, callerApp != null ? callerApp.info.packageName : null, intent, resolvedType, resultTo, - resultCode, resultData, map, requiredPermission, serialized, - sticky, callingPid, callingUid); + resultCode, resultData, map, requiredPermission, serialized, sticky, + callingPid, callingUid, userId); Binder.restoreCallingIdentity(origId); return res; } @@ -12626,21 +13363,21 @@ public final class ActivityManagerService extends ActivityManagerNative int broadcastIntentInPackage(String packageName, int uid, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, - String requiredPermission, boolean serialized, boolean sticky) { + String requiredPermission, boolean serialized, boolean sticky, int userId) { synchronized(this) { intent = verifyBroadcastLocked(intent); final long origId = Binder.clearCallingIdentity(); int res = broadcastIntentLocked(null, packageName, intent, resolvedType, resultTo, resultCode, resultData, map, requiredPermission, - serialized, sticky, -1, uid); + serialized, sticky, -1, uid, userId); Binder.restoreCallingIdentity(origId); return res; } } - public final void unbroadcastIntent(IApplicationThread caller, - Intent intent) { + // TODO: Use the userId; maybe mStickyBroadcasts need to be tied to the user. + public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -12673,54 +13410,14 @@ public final class ActivityManagerService extends ActivityManagerNative private final boolean finishReceiverLocked(IBinder receiver, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort, boolean explicit) { - if (mOrderedBroadcasts.size() == 0) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but no pending broadcasts"); - } - return false; - } - BroadcastRecord r = mOrderedBroadcasts.get(0); - if (r.receiver == null) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but none active"); - } - return false; - } - if (r.receiver != receiver) { - Slog.w(TAG, "finishReceiver called but active receiver is different"); + final BroadcastRecord r = broadcastRecordForReceiverLocked(receiver); + if (r == null) { + Slog.w(TAG, "finishReceiver called but not found on queue"); return false; } - int state = r.state; - r.state = BroadcastRecord.IDLE; - if (state == BroadcastRecord.IDLE) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but state is IDLE"); - } - } - r.receiver = null; - r.intent.setComponent(null); - if (r.curApp != null) { - r.curApp.curReceiver = null; - } - if (r.curFilter != null) { - r.curFilter.receiverList.curBroadcast = null; - } - r.curFilter = null; - r.curApp = null; - r.curComponent = null; - r.curReceiver = null; - mPendingBroadcast = null; - r.resultCode = resultCode; - r.resultData = resultData; - r.resultExtras = resultExtras; - r.resultAbort = resultAbort; - - // We will process the next receiver right now if this is finishing - // an app receiver (which is always asynchronous) or after we have - // come back from calling a receiver. - return state == BroadcastRecord.APP_RECEIVE - || state == BroadcastRecord.CALL_DONE_RECEIVE; + return r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, + explicit); } public void finishReceiver(IBinder who, int resultCode, String resultData, @@ -12732,153 +13429,25 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Bundle"); } - boolean doNext; - final long origId = Binder.clearCallingIdentity(); + try { + boolean doNext = false; + BroadcastRecord r = null; - synchronized(this) { - doNext = finishReceiverLocked( - who, resultCode, resultData, resultExtras, resultAbort, true); - } - - if (doNext) { - processNextBroadcast(false); - } - trimApplications(); - - Binder.restoreCallingIdentity(origId); - } - - private final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { - if (r.nextReceiver > 0) { - Object curReceiver = r.receivers.get(r.nextReceiver-1); - if (curReceiver instanceof BroadcastFilter) { - BroadcastFilter bf = (BroadcastFilter) curReceiver; - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver - 1, - System.identityHashCode(bf)); - } else { - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver - 1, - ((ResolveInfo)curReceiver).toString()); - } - } else { - Slog.w(TAG, "Discarding broadcast before first receiver is invoked: " - + r); - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver, - "NONE"); - } - } - - private final void setBroadcastTimeoutLocked(long timeoutTime) { - if (! mPendingBroadcastTimeoutMessage) { - Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG); - mHandler.sendMessageAtTime(msg, timeoutTime); - mPendingBroadcastTimeoutMessage = true; - } - } - - private final void cancelBroadcastTimeoutLocked() { - if (mPendingBroadcastTimeoutMessage) { - mHandler.removeMessages(BROADCAST_TIMEOUT_MSG); - mPendingBroadcastTimeoutMessage = false; - } - } - - private final void broadcastTimeoutLocked(boolean fromMsg) { - if (fromMsg) { - mPendingBroadcastTimeoutMessage = false; - } - - if (mOrderedBroadcasts.size() == 0) { - return; - } - - long now = SystemClock.uptimeMillis(); - BroadcastRecord r = mOrderedBroadcasts.get(0); - if (fromMsg) { - if (mDidDexOpt) { - // Delay timeouts until dexopt finishes. - mDidDexOpt = false; - long timeoutTime = SystemClock.uptimeMillis() + BROADCAST_TIMEOUT; - setBroadcastTimeoutLocked(timeoutTime); - return; - } - if (! mProcessesReady) { - // Only process broadcast timeouts if the system is ready. That way - // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended - // to do heavy lifting for system up. - return; - } - - long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT; - if (timeoutTime > now) { - // We can observe premature timeouts because we do not cancel and reset the - // broadcast timeout message after each receiver finishes. Instead, we set up - // an initial timeout then kick it down the road a little further as needed - // when it expires. - if (DEBUG_BROADCAST) Slog.v(TAG, - "Premature timeout @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for " - + timeoutTime); - setBroadcastTimeoutLocked(timeoutTime); - return; - } - } - - Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver - + ", started " + (now - r.receiverTime) + "ms ago"); - r.receiverTime = now; - r.anrCount++; - - // Current receiver has passed its expiration date. - if (r.nextReceiver <= 0) { - Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0"); - return; - } - - ProcessRecord app = null; - String anrMessage = null; - - Object curReceiver = r.receivers.get(r.nextReceiver-1); - Slog.w(TAG, "Receiver during timeout: " + curReceiver); - logBroadcastReceiverDiscardLocked(r); - if (curReceiver instanceof BroadcastFilter) { - BroadcastFilter bf = (BroadcastFilter)curReceiver; - if (bf.receiverList.pid != 0 - && bf.receiverList.pid != MY_PID) { - synchronized (this.mPidsSelfLocked) { - app = this.mPidsSelfLocked.get( - bf.receiverList.pid); + synchronized(this) { + r = broadcastRecordForReceiverLocked(who); + if (r != null) { + doNext = r.queue.finishReceiverLocked(r, resultCode, + resultData, resultExtras, resultAbort, true); } } - } else { - app = r.curApp; - } - - if (app != null) { - anrMessage = "Broadcast of " + r.intent.toString(); - } - if (mPendingBroadcast == r) { - mPendingBroadcast = null; - } - - // Move on to the next receiver. - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - scheduleBroadcastsLocked(); - - if (anrMessage != null) { - // Post the ANR to the handler since we do not want to process ANRs while - // potentially holding our lock. - mHandler.post(new AppNotResponding(app, anrMessage)); + if (doNext) { + r.queue.processNextBroadcast(false); + } + trimApplications(); + } finally { + Binder.restoreCallingIdentity(origId); } } @@ -13013,334 +13582,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private final void addBroadcastToHistoryLocked(BroadcastRecord r) { - if (r.callingUid < 0) { - // This was from a registerReceiver() call; ignore it. - return; - } - System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1, - MAX_BROADCAST_HISTORY-1); - r.finishTime = SystemClock.uptimeMillis(); - mBroadcastHistory[0] = r; - } - - private final void processNextBroadcast(boolean fromMsg) { - synchronized(this) { - BroadcastRecord r; - - if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast: " - + mParallelBroadcasts.size() + " broadcasts, " - + mOrderedBroadcasts.size() + " ordered broadcasts"); - - updateCpuStats(); - - if (fromMsg) { - mBroadcastsScheduled = false; - } - - // First, deliver any non-serialized broadcasts right away. - while (mParallelBroadcasts.size() > 0) { - r = mParallelBroadcasts.remove(0); - r.dispatchTime = SystemClock.uptimeMillis(); - r.dispatchClockTime = System.currentTimeMillis(); - final int N = r.receivers.size(); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast " - + r); - for (int i=0; i<N; i++) { - Object target = r.receivers.get(i); - if (DEBUG_BROADCAST) Slog.v(TAG, - "Delivering non-ordered to registered " - + target + ": " + r); - deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); - } - addBroadcastToHistoryLocked(r); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast " - + r); - } - - // Now take care of the next serialized one... - - // If we are waiting for a process to come up to handle the next - // broadcast, then do nothing at this point. Just in case, we - // check that the process we're waiting for still exists. - if (mPendingBroadcast != null) { - if (DEBUG_BROADCAST_LIGHT) { - Slog.v(TAG, "processNextBroadcast: waiting for " - + mPendingBroadcast.curApp); - } - - boolean isDead; - synchronized (mPidsSelfLocked) { - isDead = (mPidsSelfLocked.get(mPendingBroadcast.curApp.pid) == null); - } - if (!isDead) { - // It's still alive, so keep waiting - return; - } else { - Slog.w(TAG, "pending app " + mPendingBroadcast.curApp - + " died before responding to broadcast"); - mPendingBroadcast.state = BroadcastRecord.IDLE; - mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; - mPendingBroadcast = null; - } - } - - boolean looped = false; - - do { - if (mOrderedBroadcasts.size() == 0) { - // No more broadcasts pending, so all done! - scheduleAppGcsLocked(); - if (looped) { - // If we had finished the last ordered broadcast, then - // make sure all processes have correct oom and sched - // adjustments. - updateOomAdjLocked(); - } - return; - } - r = mOrderedBroadcasts.get(0); - boolean forceReceive = false; - - // Ensure that even if something goes awry with the timeout - // detection, we catch "hung" broadcasts here, discard them, - // and continue to make progress. - // - // This is only done if the system is ready so that PRE_BOOT_COMPLETED - // receivers don't get executed with timeouts. They're intended for - // one time heavy lifting after system upgrades and can take - // significant amounts of time. - int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; - if (mProcessesReady && r.dispatchTime > 0) { - long now = SystemClock.uptimeMillis(); - if ((numReceivers > 0) && - (now > r.dispatchTime + (2*BROADCAST_TIMEOUT*numReceivers))) { - Slog.w(TAG, "Hung broadcast discarded after timeout failure:" - + " now=" + now - + " dispatchTime=" + r.dispatchTime - + " startTime=" + r.receiverTime - + " intent=" + r.intent - + " numReceivers=" + numReceivers - + " nextReceiver=" + r.nextReceiver - + " state=" + r.state); - broadcastTimeoutLocked(false); // forcibly finish this broadcast - forceReceive = true; - r.state = BroadcastRecord.IDLE; - } - } - - if (r.state != BroadcastRecord.IDLE) { - if (DEBUG_BROADCAST) Slog.d(TAG, - "processNextBroadcast() called when not idle (state=" - + r.state + ")"); - return; - } - - if (r.receivers == null || r.nextReceiver >= numReceivers - || r.resultAbort || forceReceive) { - // No more receivers for this broadcast! Send the final - // result if requested... - if (r.resultTo != null) { - try { - if (DEBUG_BROADCAST) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Finishing broadcast " + r.intent.getAction() - + " seq=" + seq + " app=" + r.callerApp); - } - performReceiveLocked(r.callerApp, r.resultTo, - new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, false, false); - // Set this to null so that the reference - // (local and remote) isnt kept in the mBroadcastHistory. - r.resultTo = null; - } catch (RemoteException e) { - Slog.w(TAG, "Failure sending broadcast result of " + r.intent, e); - } - } - - if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG"); - cancelBroadcastTimeoutLocked(); - - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast " - + r); - - // ... and on to the next... - addBroadcastToHistoryLocked(r); - mOrderedBroadcasts.remove(0); - r = null; - looped = true; - continue; - } - } while (r == null); - - // Get the next receiver... - int recIdx = r.nextReceiver++; - - // Keep track of when this receiver started, and make sure there - // is a timeout message pending to kill it if need be. - r.receiverTime = SystemClock.uptimeMillis(); - if (recIdx == 0) { - r.dispatchTime = r.receiverTime; - r.dispatchClockTime = System.currentTimeMillis(); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast " - + r); - } - if (! mPendingBroadcastTimeoutMessage) { - long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT; - if (DEBUG_BROADCAST) Slog.v(TAG, - "Submitting BROADCAST_TIMEOUT_MSG for " + r + " at " + timeoutTime); - setBroadcastTimeoutLocked(timeoutTime); - } - - Object nextReceiver = r.receivers.get(recIdx); - if (nextReceiver instanceof BroadcastFilter) { - // Simple case: this is a registered receiver who gets - // a direct call. - BroadcastFilter filter = (BroadcastFilter)nextReceiver; - if (DEBUG_BROADCAST) Slog.v(TAG, - "Delivering ordered to registered " - + filter + ": " + r); - deliverToRegisteredReceiverLocked(r, filter, r.ordered); - if (r.receiver == null || !r.ordered) { - // The receiver has already finished, so schedule to - // process the next one. - if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing: ordered=" - + r.ordered + " receiver=" + r.receiver); - r.state = BroadcastRecord.IDLE; - scheduleBroadcastsLocked(); - } - return; - } - - // Hard case: need to instantiate the receiver, possibly - // starting its application process to host it. - - ResolveInfo info = - (ResolveInfo)nextReceiver; - - boolean skip = false; - int perm = checkComponentPermission(info.activityInfo.permission, - r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, - info.activityInfo.exported); - if (perm != PackageManager.PERMISSION_GRANTED) { - if (!info.activityInfo.exported) { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ")" - + " is not exported from uid " + info.activityInfo.applicationInfo.uid - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); - } else { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ")" - + " requires " + info.activityInfo.permission - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); - } - skip = true; - } - if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && - r.requiredPermission != null) { - try { - perm = AppGlobals.getPackageManager(). - checkPermission(r.requiredPermission, - info.activityInfo.applicationInfo.packageName); - } catch (RemoteException e) { - perm = PackageManager.PERMISSION_DENIED; - } - if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: receiving " - + r.intent + " to " - + info.activityInfo.applicationInfo.packageName - + " requires " + r.requiredPermission - + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - skip = true; - } - } - if (r.curApp != null && r.curApp.crashing) { - // If the target process is crashing, just skip it. - if (DEBUG_BROADCAST) Slog.v(TAG, - "Skipping deliver ordered " + r + " to " + r.curApp - + ": process crashing"); - skip = true; - } - - if (skip) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "Skipping delivery of ordered " + r + " for whatever reason"); - r.receiver = null; - r.curFilter = null; - r.state = BroadcastRecord.IDLE; - scheduleBroadcastsLocked(); - return; - } - - r.state = BroadcastRecord.APP_RECEIVE; - String targetProcess = info.activityInfo.processName; - r.curComponent = new ComponentName( - info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - r.curReceiver = info.activityInfo; - - // Broadcast is being executed, its package can't be stopped. - try { - AppGlobals.getPackageManager().setPackageStoppedState( - r.curComponent.getPackageName(), false); - } catch (RemoteException e) { - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Failed trying to unstop package " - + r.curComponent.getPackageName() + ": " + e); - } - - // Is this receiver's application already running? - ProcessRecord app = getProcessRecordLocked(targetProcess, - info.activityInfo.applicationInfo.uid); - if (app != null && app.thread != null) { - try { - app.addPackage(info.activityInfo.packageName); - processCurBroadcastLocked(r, app); - return; - } catch (RemoteException e) { - Slog.w(TAG, "Exception when sending broadcast to " - + r.curComponent, e); - } - - // If a dead object exception was thrown -- fall through to - // restart the application. - } - - // Not running -- get it started, to be executed when the app comes up. - if (DEBUG_BROADCAST) Slog.v(TAG, - "Need to start app " + targetProcess + " for broadcast " + r); - if ((r.curApp=startProcessLocked(targetProcess, - info.activityInfo.applicationInfo, true, - r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, - "broadcast", r.curComponent, - (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0)) - == null) { - // Ah, this recipient is unavailable. Finish it if necessary, - // and mark the broadcast record as ready for the next. - Slog.w(TAG, "Unable to launch app " - + info.activityInfo.applicationInfo.packageName + "/" - + info.activityInfo.applicationInfo.uid + " for broadcast " - + r.intent + ": process is bad"); - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - scheduleBroadcastsLocked(); - r.state = BroadcastRecord.IDLE; - return; - } - - mPendingBroadcast = r; - mPendingBroadcastRecvIndex = recIdx; - } - } - // ========================================================= // INSTRUMENTATION // ========================================================= @@ -13612,12 +13853,12 @@ public final class ActivityManagerService extends ActivityManagerNative intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, - null, false, false, MY_PID, Process.SYSTEM_UID); + null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) { broadcastIntentLocked(null, null, new Intent(Intent.ACTION_LOCALE_CHANGED), null, null, 0, null, null, - null, false, false, MY_PID, Process.SYSTEM_UID); + null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); } } } @@ -13663,6 +13904,28 @@ public final class ActivityManagerService extends ActivityManagerNative // LIFETIME MANAGEMENT // ========================================================= + // Returns which broadcast queue the app is the current [or imminent] receiver + // on, or 'null' if the app is not an active broadcast recipient. + private BroadcastQueue isReceivingBroadcast(ProcessRecord app) { + BroadcastRecord r = app.curReceiver; + if (r != null) { + return r.queue; + } + + // It's not the current receiver, but it might be starting up to become one + synchronized (this) { + for (BroadcastQueue queue : mBroadcastQueues) { + r = queue.mPendingBroadcast; + if (r != null && r.curApp == app) { + // found it; report which queue it's in + return queue; + } + } + } + + return null; + } + private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP, boolean recursed, boolean doingAll) { if (mAdjSeq == app.adjSeq) { @@ -13728,6 +13991,7 @@ public final class ActivityManagerService extends ActivityManagerNative // important to least, and assign an appropriate OOM adjustment. int adj; int schedGroup; + BroadcastQueue queue; if (app == TOP_APP) { // The last app on the list is the foreground app. adj = ProcessList.FOREGROUND_APP_ADJ; @@ -13739,12 +14003,14 @@ public final class ActivityManagerService extends ActivityManagerNative adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "instrumentation"; - } else if (app.curReceiver != null || - (mPendingBroadcast != null && mPendingBroadcast.curApp == app)) { + } else if ((queue = isReceivingBroadcast(app)) != null) { // An app that is currently receiving a broadcast also - // counts as being in the foreground. + // counts as being in the foreground for OOM killer purposes. + // It's placed in a sched group based on the nature of the + // broadcast as reflected by which queue it's active in. adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; + schedGroup = (queue == mFgBroadcastQueue) + ? Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE; app.adjType = "broadcast"; } else if (app.executingServices.size() > 0) { // An app that is currently executing a service callback also @@ -14185,8 +14451,13 @@ public final class ActivityManagerService extends ActivityManagerNative * Returns true if things are idle enough to perform GCs. */ private final boolean canGcNowLocked() { - return mParallelBroadcasts.size() == 0 - && mOrderedBroadcasts.size() == 0 + boolean processingBroadcasts = false; + for (BroadcastQueue q : mBroadcastQueues) { + if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) { + processingBroadcasts = true; + } + } + return !processingBroadcasts && (mSleeping || (mMainStack.mResumedActivity != null && mMainStack.mResumedActivity.idle)); } @@ -14930,8 +15201,158 @@ public final class ActivityManagerService extends ActivityManagerNative // Multi-user methods - public boolean switchUser(int userid) { - // TODO + private int mCurrentUserId; + private SparseIntArray mLoggedInUsers = new SparseIntArray(5); + + public boolean switchUser(int userId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.myUid()) { + Slog.e(TAG, "Trying to switch user from unauthorized app"); + return false; + } + if (mCurrentUserId == userId) + return true; + + synchronized (this) { + // Check if user is already logged in, otherwise check if user exists first before + // adding to the list of logged in users. + if (mLoggedInUsers.indexOfKey(userId) < 0) { + if (!userExists(userId)) { + return false; + } + mLoggedInUsers.append(userId, userId); + } + + mCurrentUserId = userId; + boolean haveActivities = mMainStack.switchUser(userId); + if (!haveActivities) { + startHomeActivityLocked(userId); + } + } return true; } + + private boolean userExists(int userId) { + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + if (user.id == userId) { + return true; + } + } + } catch (RemoteException re) { + // Won't happen, in same process + } + + return false; + } + + + private int applyUserId(int uid, int userId) { + return UserId.getUid(userId, uid); + } + + private ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { + if (info == null) return null; + ApplicationInfo newInfo = new ApplicationInfo(info); + newInfo.uid = applyUserId(info.uid, userId); + if (newInfo.uid >= Process.FIRST_APPLICATION_UID) { + newInfo.dataDir = USER_DATA_DIR + userId + "/" + + info.packageName; + } + return newInfo; + } + + ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) { + if (aInfo == null || aInfo.applicationInfo.uid < Process.FIRST_APPLICATION_UID + || userId < 1) { + return aInfo; + } + + ActivityInfo info = new ActivityInfo(aInfo); + info.applicationInfo = getAppInfoForUser(info.applicationInfo, userId); + return info; + } + + static class ServiceMap { + + private final SparseArray<HashMap<ComponentName, ServiceRecord>> mServicesByNamePerUser + = new SparseArray<HashMap<ComponentName, ServiceRecord>>(); + private final SparseArray<HashMap<Intent.FilterComparison, ServiceRecord>> + mServicesByIntentPerUser = new SparseArray< + HashMap<Intent.FilterComparison, ServiceRecord>>(); + + ServiceRecord getServiceByName(ComponentName name, int callingUser) { + // TODO: Deal with global services + if (DEBUG_MU) + Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser); + return getServices(callingUser).get(name); + } + + ServiceRecord getServiceByName(ComponentName name) { + return getServiceByName(name, -1); + } + + ServiceRecord getServiceByIntent(Intent.FilterComparison filter, int callingUser) { + // TODO: Deal with global services + if (DEBUG_MU) + Slog.v(TAG_MU, "getServiceByIntent(" + filter + "), callingUser = " + callingUser); + return getServicesByIntent(callingUser).get(filter); + } + + ServiceRecord getServiceByIntent(Intent.FilterComparison filter) { + return getServiceByIntent(filter, -1); + } + + void putServiceByName(ComponentName name, int callingUser, ServiceRecord value) { + // TODO: Deal with global services + getServices(callingUser).put(name, value); + } + + void putServiceByIntent(Intent.FilterComparison filter, int callingUser, + ServiceRecord value) { + // TODO: Deal with global services + getServicesByIntent(callingUser).put(filter, value); + } + + void removeServiceByName(ComponentName name, int callingUser) { + // TODO: Deal with global services + ServiceRecord removed = getServices(callingUser).remove(name); + if (DEBUG_MU) + Slog.v(TAG, "removeServiceByName user=" + callingUser + " name=" + name + + " removed=" + removed); + } + + void removeServiceByIntent(Intent.FilterComparison filter, int callingUser) { + // TODO: Deal with global services + ServiceRecord removed = getServicesByIntent(callingUser).remove(filter); + if (DEBUG_MU) + Slog.v(TAG_MU, "removeServiceByIntent user=" + callingUser + " intent=" + filter + + " removed=" + removed); + } + + Collection<ServiceRecord> getAllServices(int callingUser) { + // TODO: Deal with global services + return getServices(callingUser).values(); + } + + private HashMap<ComponentName, ServiceRecord> getServices(int callingUser) { + HashMap map = mServicesByNamePerUser.get(callingUser); + if (map == null) { + map = new HashMap<ComponentName, ServiceRecord>(); + mServicesByNamePerUser.put(callingUser, map); + } + return map; + } + + private HashMap<Intent.FilterComparison, ServiceRecord> getServicesByIntent( + int callingUser) { + HashMap map = mServicesByIntentPerUser.get(callingUser); + if (map == null) { + map = new HashMap<Intent.FilterComparison, ServiceRecord>(); + mServicesByIntentPerUser.put(callingUser, map); + } + return map; + } + } } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index c819114f3866..cdab6c6f67b3 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.os.Build; @@ -34,6 +35,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -55,6 +57,7 @@ final class ActivityRecord { final IApplicationToken.Stub appToken; // window manager token final ActivityInfo info; // all about me final int launchedFromUid; // always the uid who started the activity. + final int userId; // Which user is this running for? final Intent intent; // the original intent that generated us final ComponentName realActivity; // the intent component, or target of an alias. final String shortComponentName; // the short component name of the intent @@ -124,6 +127,7 @@ final class ActivityRecord { pw.print(prefix); pw.print("packageName="); pw.print(packageName); pw.print(" processName="); pw.println(processName); pw.print(prefix); pw.print("launchedFromUid="); pw.print(launchedFromUid); + pw.print(prefix); pw.print("userId="); pw.print(userId); pw.print(" app="); pw.println(app); pw.print(prefix); pw.println(intent.toInsecureString()); pw.print(prefix); pw.print("frontOfTask="); pw.print(frontOfTask); @@ -281,6 +285,7 @@ final class ActivityRecord { appToken = new Token(this); info = aInfo; launchedFromUid = _launchedFromUid; + userId = UserId.getUserId(aInfo.applicationInfo.uid); intent = _intent; shortComponentName = _intent.getComponent().flattenToShortString(); resolvedType = _resolvedType; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 6c1195387f4e..c3ae6a134961 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -60,6 +60,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -274,6 +275,8 @@ final class ActivityStack { int mThumbnailWidth = -1; int mThumbnailHeight = -1; + private int mCurrentUser; + static final int SLEEP_TIMEOUT_MSG = 8; static final int PAUSE_TIMEOUT_MSG = 9; static final int IDLE_TIMEOUT_MSG = 10; @@ -365,6 +368,7 @@ final class ActivityStack { } final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { + // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); @@ -377,6 +381,7 @@ final class ActivityStack { } final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { + // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); @@ -398,6 +403,7 @@ final class ActivityStack { * @return Returns the HistoryRecord of the next activity on the stack. */ final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { + // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); @@ -444,10 +450,11 @@ final class ActivityStack { TaskRecord cp = null; + final int userId = UserId.getUserId(info.applicationInfo.uid); final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { ActivityRecord r = mHistory.get(i); - if (!r.finishing && r.task != cp + if (!r.finishing && r.task != cp && r.userId == userId && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { cp = r.task; //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString() @@ -487,12 +494,13 @@ final class ActivityStack { if (info.targetActivity != null) { cls = new ComponentName(info.packageName, info.targetActivity); } + final int userId = UserId.getUserId(info.applicationInfo.uid); final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { ActivityRecord r = mHistory.get(i); if (!r.finishing) { - if (r.intent.getComponent().equals(cls)) { + if (r.intent.getComponent().equals(cls) && r.userId == userId) { //Slog.i(TAG, "Found matching class!"); //dump(); //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); @@ -511,6 +519,43 @@ final class ActivityStack { mService.mHandler.sendMessage(msg); } + /* + * Move the activities around in the stack to bring a user to the foreground. + * @return whether there are any activities for the specified user. + */ + final boolean switchUser(int userId) { + synchronized (mService) { + mCurrentUser = userId; + + // Only one activity? Nothing to do... + if (mHistory.size() < 2) + return false; + + boolean haveActivities = false; + // Check if the top activity is from the new user. + ActivityRecord top = mHistory.get(mHistory.size() - 1); + if (top.userId == userId) return true; + // Otherwise, move the user's activities to the top. + int N = mHistory.size(); + int i = 0; + while (i < N) { + ActivityRecord r = mHistory.get(i); + if (r.userId == userId) { + ActivityRecord moveToTop = mHistory.remove(i); + mHistory.add(moveToTop); + // No need to check the top one now + N--; + haveActivities = true; + } else { + i++; + } + } + // Transition from the old top to the new top + resumeTopActivityLocked(top); + return haveActivities; + } + } + final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app, boolean andResume, boolean checkConfig) throws RemoteException { @@ -1272,7 +1317,7 @@ final class ActivityStack { // There are no more activities! Let's just start up the // Launcher... if (mMainStack) { - return mService.startHomeActivityLocked(); + return mService.startHomeActivityLocked(0); } } @@ -1384,6 +1429,7 @@ final class ActivityStack { // Launching this app's activity, make sure the app is no longer // considered stopped. try { + // TODO: Apply to the correct userId AppGlobals.getPackageManager().setPackageStoppedState( next.packageName, false); } catch (RemoteException e1) { @@ -2354,7 +2400,7 @@ final class ActivityStack { } } } - + ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); @@ -2420,7 +2466,8 @@ final class ActivityStack { int grantedMode, boolean onlyIfNeeded, boolean doResume) { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; - + final int userId = r.userId; + int launchFlags = intent.getFlags(); // We'll invoke onUserLeaving before onPause only if the launching @@ -2648,7 +2695,7 @@ final class ActivityStack { // once. ActivityRecord top = topRunningNonDelayedActivityLocked(notTop); if (top != null && r.resultTo == null) { - if (top.realActivity.equals(r.realActivity)) { + if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { if (top.app != null && top.app.thread != null) { if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP @@ -2821,12 +2868,12 @@ final class ActivityStack { int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler, WaitResult outResult, Configuration config) { + boolean autoStopProfiler, + WaitResult outResult, Configuration config, int userId) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } - boolean componentSpecified = intent.getComponent() != null; // Don't modify the client's object! @@ -2835,6 +2882,7 @@ final class ActivityStack { // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug, profileFile, profileFd, autoStopProfiler); + aInfo = mService.getActivityInfoForUser(aInfo, userId); synchronized (mService) { int callingPid; @@ -2915,6 +2963,7 @@ final class ActivityStack { PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS); aInfo = rInfo != null ? rInfo.activityInfo : null; + aInfo = mService.getActivityInfoForUser(aInfo, userId); } catch (RemoteException e) { aInfo = null; } @@ -2977,7 +3026,8 @@ final class ActivityStack { } final int startActivities(IApplicationThread caller, int callingUid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + Intent[] intents, + String[] resolvedTypes, IBinder resultTo, int userId) { if (intents == null) { throw new NullPointerException("intents is null"); } @@ -3022,6 +3072,8 @@ final class ActivityStack { // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false, null, null, false); + // TODO: New, check if this is correct + aInfo = mService.getActivityInfoForUser(aInfo, userId); if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index bcb013404ea9..6738e4fc76c0 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -59,6 +59,7 @@ class BroadcastRecord extends Binder { IBinder receiver; // who is currently running, null if none. int state; int anrCount; // has this broadcast record hit any ANRs? + ActivityManagerService.BroadcastQueue queue; // the outbound queue handling this broadcast static final int IDLE = 0; static final int APP_RECEIVE = 1; @@ -161,11 +162,13 @@ class BroadcastRecord extends Binder { } } - BroadcastRecord(Intent _intent, ProcessRecord _callerApp, String _callerPackage, + BroadcastRecord(ActivityManagerService.BroadcastQueue _queue, + Intent _intent, ProcessRecord _callerApp, String _callerPackage, int _callingPid, int _callingUid, String _requiredPermission, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky, boolean _initialSticky) { + queue = _queue; intent = _intent; callerApp = _callerApp; callerPackage = _callerPackage; diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index abd2a1f1207d..3b6a97c41ecc 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserId; import android.util.Slog; import java.io.PrintWriter; @@ -248,7 +249,8 @@ class PendingIntentRecord extends IIntentSender.Stub { owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType, finishedReceiver, code, null, null, - requiredPermission, (finishedReceiver != null), false); + requiredPermission, (finishedReceiver != null), false, UserId + .getUserId(uid)); sendFinish = false; } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java new file mode 100644 index 000000000000..44e7eccc7dcf --- /dev/null +++ b/services/java/com/android/server/am/ProviderMap.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Process; +import android.os.UserId; +import android.util.Slog; +import android.util.SparseArray; + +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Keeps track of content providers by authority (name) and class. It separates the mapping by + * user and ones that are not user-specific (system providers). + */ +public class ProviderMap { + + private static final String TAG = "ProviderMap"; + + private static final boolean DBG = false; + + private final HashMap<String, ContentProviderRecord> mGlobalByName + = new HashMap<String, ContentProviderRecord>(); + private final HashMap<ComponentName, ContentProviderRecord> mGlobalByClass + = new HashMap<ComponentName, ContentProviderRecord>(); + + private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser + = new SparseArray<HashMap<String, ContentProviderRecord>>(); + private final SparseArray<HashMap<ComponentName, ContentProviderRecord>> mProvidersByClassPerUser + = new SparseArray<HashMap<ComponentName, ContentProviderRecord>>(); + + ContentProviderRecord getProviderByName(String name) { + return getProviderByName(name, -1); + } + + ContentProviderRecord getProviderByName(String name, int userId) { + if (DBG) { + Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid()); + } + // Try to find it in the global list + ContentProviderRecord record = mGlobalByName.get(name); + if (record != null) { + return record; + } + + // Check the current user's list + return getProvidersByName(userId).get(name); + } + + ContentProviderRecord getProviderByClass(ComponentName name) { + return getProviderByClass(name, -1); + } + + ContentProviderRecord getProviderByClass(ComponentName name, int userId) { + if (DBG) { + Slog.i(TAG, "getProviderByClass: " + name + ", callingUid = " + Binder.getCallingUid()); + } + // Try to find it in the global list + ContentProviderRecord record = mGlobalByClass.get(name); + if (record != null) { + return record; + } + + // Check the current user's list + return getProvidersByClass(userId).get(name); + } + + void putProviderByName(String name, ContentProviderRecord record) { + if (DBG) { + Slog.i(TAG, "putProviderByName: " + name + " , callingUid = " + Binder.getCallingUid() + + ", record uid = " + record.appInfo.uid); + } + if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) { + mGlobalByName.put(name, record); + } else { + final int userId = UserId.getUserId(record.appInfo.uid); + getProvidersByName(userId).put(name, record); + } + } + + void putProviderByClass(ComponentName name, ContentProviderRecord record) { + if (DBG) { + Slog.i(TAG, "putProviderByClass: " + name + " , callingUid = " + Binder.getCallingUid() + + ", record uid = " + record.appInfo.uid); + } + if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) { + mGlobalByClass.put(name, record); + } else { + final int userId = UserId.getUserId(record.appInfo.uid); + getProvidersByClass(userId).put(name, record); + } + } + + void removeProviderByName(String name, int optionalUserId) { + if (mGlobalByName.containsKey(name)) { + if (DBG) + Slog.i(TAG, "Removing from globalByName name=" + name); + mGlobalByName.remove(name); + } else { + // TODO: Verify this works, i.e., the caller happens to be from the correct user + if (DBG) + Slog.i(TAG, + "Removing from providersByName name=" + name + " user=" + + (optionalUserId == -1 ? Binder.getOrigCallingUser() : optionalUserId)); + getProvidersByName(optionalUserId).remove(name); + } + } + + void removeProviderByClass(ComponentName name, int optionalUserId) { + if (mGlobalByClass.containsKey(name)) { + if (DBG) + Slog.i(TAG, "Removing from globalByClass name=" + name); + mGlobalByClass.remove(name); + } else { + if (DBG) + Slog.i(TAG, + "Removing from providersByClass name=" + name + " user=" + + (optionalUserId == -1 ? Binder.getOrigCallingUser() : optionalUserId)); + getProvidersByClass(optionalUserId).remove(name); + } + } + + private HashMap<String, ContentProviderRecord> getProvidersByName(int optionalUserId) { + final int userId = optionalUserId >= 0 + ? optionalUserId : Binder.getOrigCallingUser(); + final HashMap<String, ContentProviderRecord> map = mProvidersByNamePerUser.get(userId); + if (map == null) { + HashMap<String, ContentProviderRecord> newMap = new HashMap<String, ContentProviderRecord>(); + mProvidersByNamePerUser.put(userId, newMap); + return newMap; + } else { + return map; + } + } + + private HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int optionalUserId) { + final int userId = optionalUserId >= 0 + ? optionalUserId : Binder.getOrigCallingUser(); + final HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.get(userId); + if (map == null) { + HashMap<ComponentName, ContentProviderRecord> newMap = new HashMap<ComponentName, ContentProviderRecord>(); + mProvidersByClassPerUser.put(userId, newMap); + return newMap; + } else { + return map; + } + } + + private void dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll, + HashMap<ComponentName, ContentProviderRecord> map) { + Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<ComponentName, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); + if (dumpAll) { + pw.print(" * "); + pw.println(r); + r.dump(pw, " "); + } else { + pw.print(" * "); + pw.print(r.name.toShortString()); + /* + if (r.app != null) { + pw.println(":"); + pw.print(" "); + pw.println(r.app); + } else { + pw.println(); + } + */ + } + } + } + + private void dumpProvidersByNameLocked(PrintWriter pw, + HashMap<String, ContentProviderRecord> map) { + Iterator<Map.Entry<String, ContentProviderRecord>> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); + pw.print(" "); + pw.print(e.getKey()); + pw.print(": "); + pw.println(r); + } + } + + void dumpProvidersLocked(PrintWriter pw, boolean dumpAll) { + boolean needSep = false; + if (mGlobalByClass.size() > 0) { + if (needSep) + pw.println(" "); + pw.println(" Published content providers (by class):"); + dumpProvidersByClassLocked(pw, dumpAll, mGlobalByClass); + pw.println(" "); + } + + if (mProvidersByClassPerUser.size() > 1) { + for (int i = 0; i < mProvidersByClassPerUser.size(); i++) { + HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i); + pw.println(" User " + mProvidersByClassPerUser.keyAt(i) + ":"); + dumpProvidersByClassLocked(pw, dumpAll, map); + pw.println(" "); + } + } else if (mProvidersByClassPerUser.size() == 1) { + HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(0); + dumpProvidersByClassLocked(pw, dumpAll, map); + } + needSep = true; + + if (dumpAll) { + pw.println(" "); + pw.println(" Authority to provider mappings:"); + dumpProvidersByNameLocked(pw, mGlobalByName); + + for (int i = 0; i < mProvidersByNamePerUser.size(); i++) { + if (i > 0) { + pw.println(" User " + mProvidersByNamePerUser.keyAt(i) + ":"); + } + dumpProvidersByNameLocked(pw, mProvidersByNamePerUser.valueAt(i)); + } + } + } +} diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 257113bbe671..75ba9474b780 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -25,11 +25,13 @@ import android.app.NotificationManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.Slog; import android.util.TimeUtils; @@ -60,6 +62,7 @@ class ServiceRecord extends Binder { // all information about the service. final ApplicationInfo appInfo; // information about service's app. + final int userId; // user that this service is running as final String packageName; // the package implementing intent's component final String processName; // process where this component wants to run final String permission;// permission needed to access service @@ -289,6 +292,7 @@ class ServiceRecord extends Binder { this.restarter = restarter; createTime = SystemClock.elapsedRealtime(); lastActivity = SystemClock.uptimeMillis(); + userId = UserId.getUserId(appInfo.uid); } public AppBindRecord retrieveAppBindingLocked(Intent intent, diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index de3129bda384..47ec21805638 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -19,7 +19,9 @@ package com.android.server.am; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.os.UserId; import java.io.PrintWriter; @@ -37,6 +39,7 @@ class TaskRecord extends ThumbnailHolder { boolean askedCompatMode;// Have asked the user about compat mode for this task. String stringName; // caching of toString() result. + int userId; // user for which this task was created TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; @@ -84,13 +87,17 @@ class TaskRecord extends ThumbnailHolder { origActivity = new ComponentName(info.packageName, info.name); } } - + if (intent != null && (intent.getFlags()&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { // Once we are set to an Intent with this flag, we count this // task as having a true root activity. rootWasReset = true; } + + if (info.applicationInfo != null) { + userId = UserId.getUserId(info.applicationInfo.uid); + } } void dump(PrintWriter pw, String prefix) { @@ -154,6 +161,8 @@ class TaskRecord extends ThumbnailHolder { } else { sb.append(" ??"); } + sb.append(" U "); + sb.append(userId); sb.append('}'); return stringName = sb.toString(); } diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index a71ccb5e536c..a890068b08c9 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -48,6 +48,7 @@ import static android.net.NetworkTemplate.MATCH_MOBILE_4G; import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; @@ -154,10 +155,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int VERSION_ADDED_SNOOZE = 2; private static final int VERSION_ADDED_RESTRICT_BACKGROUND = 3; private static final int VERSION_ADDED_METERED = 4; - - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; + private static final int VERSION_SPLIT_SNOOZE = 5; // @VisibleForTesting public static final int TYPE_WARNING = 0x1; @@ -176,6 +174,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final String ATTR_WARNING_BYTES = "warningBytes"; private static final String ATTR_LIMIT_BYTES = "limitBytes"; private static final String ATTR_LAST_SNOOZE = "lastSnooze"; + private static final String ATTR_LAST_WARNING_SNOOZE = "lastWarningSnooze"; + private static final String ATTR_LAST_LIMIT_SNOOZE = "lastLimitSnooze"; private static final String ATTR_METERED = "metered"; private static final String ATTR_UID = "uid"; private static final String ATTR_POLICY = "policy"; @@ -184,7 +184,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // @VisibleForTesting public static final String ACTION_ALLOW_BACKGROUND = - "com.android.server.action.ACTION_ALLOW_BACKGROUND"; + "com.android.server.net.action.ALLOW_BACKGROUND"; + public static final String ACTION_SNOOZE_WARNING = + "com.android.server.net.action.SNOOZE_WARNING"; private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS; @@ -193,6 +195,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 3; private static final int MSG_PROCESS_DIED = 4; private static final int MSG_LIMIT_REACHED = 5; + private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6; private final Context mContext; private final IActivityManager mActivityManager; @@ -333,6 +336,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND); mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler); + // listen for snooze warning from notifications + final IntentFilter snoozeWarningFilter = new IntentFilter(ACTION_SNOOZE_WARNING); + mContext.registerReceiver(mSnoozeWarningReceiver, snoozeWarningFilter, + MANAGE_NETWORK_POLICY, mHandler); + } private IProcessObserver mProcessObserver = new IProcessObserver.Stub() { @@ -418,6 +426,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { }; /** + * Receiver that watches for {@link Notification} control of + * {@link NetworkPolicy#lastWarningSnooze}. + */ + private BroadcastReceiver mSnoozeWarningReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // on background handler thread, and verified MANAGE_NETWORK_POLICY + // permission above. + + final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE); + performSnooze(template, TYPE_WARNING); + } + }; + + /** * Observer that watches for {@link INetworkManagementService} alerts. */ private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() { @@ -458,7 +481,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long totalBytes = getTotalBytes(policy.template, start, end); if (policy.isOverLimit(totalBytes)) { - if (policy.lastSnooze >= start) { + if (policy.lastLimitSnooze >= start) { enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes); } else { enqueueNotification(policy, TYPE_LIMIT, totalBytes); @@ -468,7 +491,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else { notifyUnderLimitLocked(policy.template); - if (policy.warningBytes != WARNING_DISABLED && totalBytes >= policy.warningBytes) { + if (policy.isOverWarning(totalBytes) && policy.lastWarningSnooze < start) { enqueueNotification(policy, TYPE_WARNING, totalBytes); } } @@ -534,7 +557,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final String tag = buildNotificationTag(policy, type); final Notification.Builder builder = new Notification.Builder(mContext); builder.setOnlyAlertOnce(true); - builder.setOngoing(true); + builder.setWhen(0L); final Resources res = mContext.getResources(); switch (type) { @@ -547,9 +570,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { builder.setContentTitle(title); builder.setContentText(body); - final Intent intent = buildViewDataUsageIntent(policy.template); + final Intent snoozeIntent = buildSnoozeWarningIntent(policy.template); + builder.setDeleteIntent(PendingIntent.getBroadcast( + mContext, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + + final Intent viewIntent = buildViewDataUsageIntent(policy.template); builder.setContentIntent(PendingIntent.getActivity( - mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + break; } case TYPE_LIMIT: { @@ -574,6 +602,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { break; } + builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_disabled); builder.setTicker(title); builder.setContentTitle(title); @@ -608,6 +637,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { break; } + builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_error); builder.setTicker(title); builder.setContentTitle(title); @@ -720,10 +750,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long totalBytes = getTotalBytes(policy.template, start, end); // disable data connection when over limit and not snoozed - final boolean overLimit = policy.isOverLimit(totalBytes) && policy.lastSnooze < start; - final boolean enabled = !overLimit; + final boolean overLimitWithoutSnooze = policy.isOverLimit(totalBytes) + && policy.lastLimitSnooze < start; + final boolean networkEnabled = !overLimitWithoutSnooze; - setNetworkTemplateEnabled(policy.template, enabled); + setNetworkTemplateEnabled(policy.template, networkEnabled); } } @@ -827,7 +858,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // metered network, but no policy limit; we still need to // restrict apps, so push really high quota. quotaBytes = Long.MAX_VALUE; - } else if (policy.lastSnooze >= start) { + } else if (policy.lastLimitSnooze >= start) { // snoozing past quota, but we still need to restrict apps, // so push really high quota. quotaBytes = Long.MAX_VALUE; @@ -896,8 +927,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int cycleDay = time.monthDay; final NetworkTemplate template = buildTemplateMobileAll(subscriberId); - mNetworkPolicy.put(template, new NetworkPolicy( - template, cycleDay, warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, true)); + mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay, warningBytes, + LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true)); writePolicyLocked(); } } @@ -935,11 +966,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY); final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES); final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES); - final long lastSnooze; - if (version >= VERSION_ADDED_SNOOZE) { - lastSnooze = readLongAttribute(in, ATTR_LAST_SNOOZE); + final long lastLimitSnooze; + if (version >= VERSION_SPLIT_SNOOZE) { + lastLimitSnooze = readLongAttribute(in, ATTR_LAST_LIMIT_SNOOZE); + } else if (version >= VERSION_ADDED_SNOOZE) { + lastLimitSnooze = readLongAttribute(in, ATTR_LAST_SNOOZE); } else { - lastSnooze = SNOOZE_NEVER; + lastLimitSnooze = SNOOZE_NEVER; } final boolean metered; if (version >= VERSION_ADDED_METERED) { @@ -955,11 +988,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { metered = false; } } + final long lastWarningSnooze; + if (version >= VERSION_SPLIT_SNOOZE) { + lastWarningSnooze = readLongAttribute(in, ATTR_LAST_WARNING_SNOOZE); + } else { + lastWarningSnooze = SNOOZE_NEVER; + } final NetworkTemplate template = new NetworkTemplate( networkTemplate, subscriberId); - mNetworkPolicy.put(template, new NetworkPolicy( - template, cycleDay, warningBytes, limitBytes, lastSnooze, metered)); + mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay, + warningBytes, limitBytes, lastWarningSnooze, lastLimitSnooze, + metered)); } else if (TAG_UID_POLICY.equals(tag)) { final int uid = readIntAttribute(in, ATTR_UID); @@ -1014,7 +1054,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { out.startDocument(null, true); out.startTag(null, TAG_POLICY_LIST); - writeIntAttribute(out, ATTR_VERSION, VERSION_ADDED_METERED); + writeIntAttribute(out, ATTR_VERSION, VERSION_SPLIT_SNOOZE); writeBooleanAttribute(out, ATTR_RESTRICT_BACKGROUND, mRestrictBackground); // write all known network policies @@ -1030,7 +1070,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay); writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes); writeLongAttribute(out, ATTR_LIMIT_BYTES, policy.limitBytes); - writeLongAttribute(out, ATTR_LAST_SNOOZE, policy.lastSnooze); + writeLongAttribute(out, ATTR_LAST_WARNING_SNOOZE, policy.lastWarningSnooze); + writeLongAttribute(out, ATTR_LAST_LIMIT_SNOOZE, policy.lastLimitSnooze); writeBooleanAttribute(out, ATTR_METERED, policy.metered); out.endTag(null, TAG_NETWORK_POLICY); } @@ -1141,9 +1182,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void snoozePolicy(NetworkTemplate template) { + public void snoozeLimit(NetworkTemplate template) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + performSnooze(template, TYPE_LIMIT); + } + private void performSnooze(NetworkTemplate template, int type) { maybeRefreshTrustedTime(); final long currentTime = currentTimeMillis(); synchronized (mRulesLock) { @@ -1153,7 +1197,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { throw new IllegalArgumentException("unable to find policy for " + template); } - policy.lastSnooze = currentTime; + switch (type) { + case TYPE_WARNING: + policy.lastWarningSnooze = currentTime; + break; + case TYPE_LIMIT: + policy.lastLimitSnooze = currentTime; + break; + default: + throw new IllegalArgumentException("unexpected type"); + } updateNetworkEnabledLocked(); updateNetworkRulesLocked(); @@ -1173,6 +1226,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { updateNotificationsLocked(); writePolicyLocked(); } + + mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED, restrictBackground ? 1 : 0, 0) + .sendToTarget(); } @Override @@ -1246,12 +1302,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } synchronized (mRulesLock) { - if (argSet.contains("unsnooze")) { + if (argSet.contains("--unsnooze")) { for (NetworkPolicy policy : mNetworkPolicy.values()) { - policy.lastSnooze = SNOOZE_NEVER; + policy.clearSnooze(); } + + updateNetworkEnabledLocked(); + updateNetworkRulesLocked(); + updateNotificationsLocked(); writePolicyLocked(); - fout.println("Wiped snooze timestamps"); + + fout.println("Cleared snooze timestamps"); return; } @@ -1516,6 +1577,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } return true; } + case MSG_RESTRICT_BACKGROUND_CHANGED: { + final boolean restrictBackground = msg.arg1 != 0; + final int length = mListeners.beginBroadcast(); + for (int i = 0; i < length; i++) { + final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); + if (listener != null) { + try { + listener.onRestrictBackgroundChanged(restrictBackground); + } catch (RemoteException e) { + } + } + } + mListeners.finishBroadcast(); + } default: { return false; } @@ -1599,6 +1674,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return new Intent(ACTION_ALLOW_BACKGROUND); } + private static Intent buildSnoozeWarningIntent(NetworkTemplate template) { + final Intent intent = new Intent(ACTION_SNOOZE_WARNING); + intent.putExtra(EXTRA_NETWORK_TEMPLATE, template); + return intent; + } + private static Intent buildNetworkOverLimitIntent(NetworkTemplate template) { final Intent intent = new Intent(); intent.setComponent(new ComponentName( diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java index e7ba35888d97..240cc1c87501 100644 --- a/services/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/java/com/android/server/net/NetworkStatsRecorder.java @@ -51,6 +51,7 @@ import java.util.Map; public class NetworkStatsRecorder { private static final String TAG = "NetworkStatsRecorder"; private static final boolean LOGD = true; + private static final boolean LOGV = false; private final FileRotator mRotator; private final NonMonotonicObserver<String> mObserver; @@ -170,7 +171,7 @@ public class NetworkStatsRecorder { mLastSnapshot = snapshot; - if (LOGD && unknownIfaces.size() > 0) { + if (LOGV && unknownIfaces.size() > 0) { Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats"); } } diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index c9b79e8e891c..13c0640e5fad 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -34,6 +34,7 @@ import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION; import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE; import static android.provider.Settings.Secure.NETSTATS_DEV_PERSIST_BYTES; @@ -116,11 +117,11 @@ import java.util.HashSet; */ public class NetworkStatsService extends INetworkStatsService.Stub { private static final String TAG = "NetworkStats"; - private static final boolean LOGD = true; - private static final boolean LOGV = true; + private static final boolean LOGV = false; private static final int MSG_PERFORM_POLL = 1; private static final int MSG_UPDATE_IFACES = 2; + private static final int MSG_REGISTER_GLOBAL_ALERT = 3; /** Flags to control detail level of poll event. */ private static final int FLAG_PERSIST_NETWORK = 0x1; @@ -152,10 +153,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private PendingIntent mPollIntent; - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES; - private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES; - private static final String PREFIX_DEV = "dev"; private static final String PREFIX_UID = "uid"; private static final String PREFIX_UID_TAG = "uid_tag"; @@ -510,7 +507,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void forceUpdate() { mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - performPoll(FLAG_PERSIST_ALL); + + final long token = Binder.clearCallingIdentity(); + try { + performPoll(FLAG_PERSIST_ALL); + } finally { + Binder.restoreCallingIdentity(token); + } } /** @@ -594,7 +597,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget(); // re-arm global alert for next update - registerGlobalAlert(); + mHandler.obtainMessage(MSG_REGISTER_GLOBAL_ALERT).sendToTarget(); } } }; @@ -854,8 +857,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { argSet.add(arg); } - // usage: dumpsys netstats --full --uid --tag + // usage: dumpsys netstats --full --uid --tag --poll --checkin final boolean poll = argSet.contains("--poll") || argSet.contains("poll"); + final boolean checkin = argSet.contains("--checkin"); final boolean fullHistory = argSet.contains("--full") || argSet.contains("full"); final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail"); final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail"); @@ -869,6 +873,17 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return; } + if (checkin) { + // list current stats files to verify rotation + pw.println("Current files:"); + pw.increaseIndent(); + for (String file : mBaseDir.list()) { + pw.println(file); + } + pw.decreaseIndent(); + return; + } + pw.println("Active interfaces:"); pw.increaseIndent(); for (String iface : mActiveIfaces.keySet()) { @@ -945,6 +960,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { updateIfaces(); return true; } + case MSG_REGISTER_GLOBAL_ALERT: { + registerGlobalAlert(); + return true; + } default: { return false; } diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index 11ccd60e566c..9b1973e2825a 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -277,6 +277,27 @@ class Installer { return execute(builder.toString()); } + /** + * Clone all the package data directories from srcUserId to targetUserId. If copyData is true, + * some of the data is also copied, otherwise just empty directories are created with the + * correct access rights. + * @param srcUserId user to copy the data directories from + * @param targetUserId user to copy the data directories to + * @param copyData whether the data itself is to be copied. If false, empty directories are + * created. + * @return success/error code + */ + public int cloneUserData(int srcUserId, int targetUserId, boolean copyData) { + StringBuilder builder = new StringBuilder("cloneuserdata"); + builder.append(' '); + builder.append(srcUserId); + builder.append(' '); + builder.append(targetUserId); + builder.append(' '); + builder.append(copyData ? '1' : '0'); + return execute(builder.toString()); + } + public boolean ping() { if (execute("ping") < 0) { return false; diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 090ca64dbca1..38c128cbaeb9 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -92,6 +92,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserId; import android.security.SystemKeyStore; import android.util.DisplayMetrics; import android.util.EventLog; @@ -1743,12 +1744,16 @@ public class PackageManagerService extends IPackageManager.Stub { } public ActivityInfo getActivityInfo(ComponentName component, int flags) { + return getActivityInfo(component, flags, Binder.getOrigCallingUser()); + } + + ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { synchronized (mPackages) { PackageParser.Activity a = mActivities.mActivities.get(component); if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); if (a != null && mSettings.isEnabledLPr(a.info, flags)) { - return PackageParser.generateActivityInfo(a, flags); + return PackageParser.generateActivityInfo(a, flags, userId); } if (mResolveComponentName.equals(component)) { return mResolveActivity; @@ -1758,36 +1763,48 @@ public class PackageManagerService extends IPackageManager.Stub { } public ActivityInfo getReceiverInfo(ComponentName component, int flags) { + return getReceiverInfo(component, flags, Binder.getOrigCallingUser()); + } + + ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) { synchronized (mPackages) { PackageParser.Activity a = mReceivers.mActivities.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getReceiverInfo " + component + ": " + a); if (a != null && mSettings.isEnabledLPr(a.info, flags)) { - return PackageParser.generateActivityInfo(a, flags); + return PackageParser.generateActivityInfo(a, flags, userId); } } return null; } public ServiceInfo getServiceInfo(ComponentName component, int flags) { + return getServiceInfo(component, flags, Binder.getOrigCallingUser()); + } + + ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { synchronized (mPackages) { PackageParser.Service s = mServices.mServices.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getServiceInfo " + component + ": " + s); if (s != null && mSettings.isEnabledLPr(s.info, flags)) { - return PackageParser.generateServiceInfo(s, flags); + return PackageParser.generateServiceInfo(s, flags, userId); } } return null; } public ProviderInfo getProviderInfo(ComponentName component, int flags) { + return getProviderInfo(component, flags, UserId.getUserId(Binder.getCallingUid())); + } + + ProviderInfo getProviderInfo(ComponentName component, int flags, int userId) { synchronized (mPackages) { PackageParser.Provider p = mProvidersByComponent.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); if (p != null && mSettings.isEnabledLPr(p.info, flags)) { - return PackageParser.generateProviderInfo(p, flags); + return PackageParser.generateProviderInfo(p, flags, userId); } } return null; @@ -1850,7 +1867,7 @@ public class PackageManagerService extends IPackageManager.Stub { public int checkUidPermission(String permName, int uid) { synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(uid); + Object obj = mSettings.getUserIdLPr(UserId.getAppId(uid)); if (obj != null) { GrantedPermissions gp = (GrantedPermissions)obj; if (gp.grantedPermissions.contains(permName)) { @@ -1881,7 +1898,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (permName != null) { BasePermission bp = findPermissionTreeLP(permName); if (bp != null) { - if (bp.uid == Binder.getCallingUid()) { + if (bp.uid == UserId.getAppId(Binder.getCallingUid())) { return bp; } throw new SecurityException("Calling uid " @@ -2010,6 +2027,9 @@ public class PackageManagerService extends IPackageManager.Stub { } public int checkUidSignatures(int uid1, int uid2) { + // Map to base uids. + uid1 = UserId.getAppId(uid1); + uid2 = UserId.getAppId(uid2); // reader synchronized (mPackages) { Signature[] s1; @@ -2067,6 +2087,7 @@ public class PackageManagerService extends IPackageManager.Stub { } public String[] getPackagesForUid(int uid) { + uid = UserId.getAppId(uid); // reader synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(uid); @@ -2091,7 +2112,7 @@ public class PackageManagerService extends IPackageManager.Stub { public String getNameForUid(int uid) { // reader synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(uid); + Object obj = mSettings.getUserIdLPr(UserId.getAppId(uid)); if (obj instanceof SharedUserSetting) { final SharedUserSetting sus = (SharedUserSetting) obj; return sus.name + ":" + sus.userId; @@ -2110,7 +2131,7 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, false); - if(suid == null) { + if (suid == null) { return -1; } return suid.userId; @@ -2252,6 +2273,9 @@ public class PackageManagerService extends IPackageManager.Stub { comp = intent.getComponent(); } } + + final int userId = UserId.getUserId(Binder.getCallingUid()); + if (comp != null) { final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); final ActivityInfo ai = getActivityInfo(comp, flags); @@ -2603,6 +2627,7 @@ public class PackageManagerService extends IPackageManager.Stub { Arrays.sort(keys); int i = getContinuationPoint(keys, lastRead); final int N = keys.length; + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i < N) { final String packageName = keys[i++]; @@ -2616,7 +2641,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { final PackageParser.Package p = mPackages.get(packageName); if (p != null) { - ai = PackageParser.generateApplicationInfo(p, flags); + ai = PackageParser.generateApplicationInfo(p, flags, userId); } } @@ -2639,12 +2664,13 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Package> i = mPackages.values().iterator(); + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i.hasNext()) { final PackageParser.Package p = i.next(); if (p.applicationInfo != null && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0 && (!mSafeMode || isSystemApp(p))) { - finalList.add(PackageParser.generateApplicationInfo(p, flags)); + finalList.add(PackageParser.generateApplicationInfo(p, flags, userId)); } } } @@ -2660,7 +2686,8 @@ public class PackageManagerService extends IPackageManager.Stub { && mSettings.isEnabledLPr(provider.info, flags) && (!mSafeMode || (provider.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0) - ? PackageParser.generateProviderInfo(provider, flags) + ? PackageParser.generateProviderInfo(provider, flags, + UserId.getUserId(Binder.getCallingUid())) : null; } } @@ -2674,7 +2701,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() .iterator(); - + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i.hasNext()) { Map.Entry<String, PackageParser.Provider> entry = i.next(); PackageParser.Provider p = entry.getValue(); @@ -2683,7 +2710,7 @@ public class PackageManagerService extends IPackageManager.Stub { && (!mSafeMode || (p.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0)) { outNames.add(entry.getKey()); - outInfo.add(PackageParser.generateProviderInfo(p, 0)); + outInfo.add(PackageParser.generateProviderInfo(p, 0, userId)); } } } @@ -2696,19 +2723,21 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i.hasNext()) { final PackageParser.Provider p = i.next(); if (p.info.authority != null && (processName == null || (p.info.processName.equals(processName) - && p.info.applicationInfo.uid == uid)) + && UserId.getAppId(p.info.applicationInfo.uid) + == UserId.getAppId(uid))) && mSettings.isEnabledLPr(p.info, flags) && (!mSafeMode || (p.info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) { if (finalList == null) { finalList = new ArrayList<ProviderInfo>(3); } - finalList.add(PackageParser.generateProviderInfo(p, flags)); + finalList.add(PackageParser.generateProviderInfo(p, flags, userId)); } } } @@ -4461,8 +4490,8 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } final ResolveInfo res = new ResolveInfo(); - res.activityInfo = PackageParser.generateActivityInfo(activity, - mFlags); + res.activityInfo = PackageParser.generateActivityInfo(activity, mFlags, + Binder.getOrigCallingUser()); if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = info; } @@ -4637,8 +4666,8 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } final ResolveInfo res = new ResolveInfo(); - res.serviceInfo = PackageParser.generateServiceInfo(service, - mFlags); + res.serviceInfo = PackageParser.generateServiceInfo(service, mFlags, + Binder.getOrigCallingUser()); if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = filter; } @@ -4742,8 +4771,10 @@ public class PackageManagerService extends IPackageManager.Stub { intent.setPackage(targetPkg); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + // TODO: Fix the userId argument am.broadcastIntent(null, intent, null, finishedReceiver, - 0, null, null, null, finishedReceiver != null, false); + 0, null, null, null, finishedReceiver != null, false, + Binder.getOrigCallingUser()); } catch (RemoteException ex) { } } @@ -7584,7 +7615,7 @@ public class PackageManagerService extends IPackageManager.Stub { "Unknown component: " + packageName + "/" + className); } - if (!allowedByPermission && (uid != pkgSetting.userId)) { + if (!allowedByPermission && (!UserId.isSameApp(uid, pkgSetting.userId))) { throw new SecurityException( "Permission Denial: attempt to change component state from pid=" + Binder.getCallingPid() @@ -8673,4 +8704,8 @@ public class PackageManagerService extends IPackageManager.Stub { return mSettings.getVerifierDeviceIdentityLPw(); } } + + public List<UserInfo> getUsers() { + return mUserManager.getUsers(); + } } diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java index 26877288966a..5eacf4a342b0 100644 --- a/services/java/com/android/server/pm/UserManager.java +++ b/services/java/com/android/server/pm/UserManager.java @@ -24,6 +24,7 @@ import android.content.pm.UserInfo; import android.os.Environment; import android.os.FileUtils; import android.os.SystemClock; +import android.os.UserId; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -169,9 +170,10 @@ public class UserManager { * </user> */ private void writeUser(UserInfo userInfo) { + FileOutputStream fos = null; try { final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); - final FileOutputStream fos = new FileOutputStream(mUserFile); + fos = new FileOutputStream(mUserFile); final BufferedOutputStream bos = new BufferedOutputStream(fos); // XmlSerializer serializer = XmlUtils.serializerInstance(); @@ -193,6 +195,13 @@ public class UserManager { serializer.endDocument(); } catch (IOException ioe) { Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException ioe) { + } + } } } @@ -205,8 +214,9 @@ public class UserManager { * </users> */ private void writeUserList() { + FileOutputStream fos = null; try { - final FileOutputStream fos = new FileOutputStream(mUserListFile); + fos = new FileOutputStream(mUserListFile); final BufferedOutputStream bos = new BufferedOutputStream(fos); // XmlSerializer serializer = XmlUtils.serializerInstance(); @@ -229,6 +239,13 @@ public class UserManager { serializer.endDocument(); } catch (IOException ioe) { Slog.e(LOG_TAG, "Error writing user list"); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException ioe) { + } + } } } @@ -330,7 +347,7 @@ public class UserManager { // Don't do it for the primary user, it will become recursive. if (userId == 0) continue; - mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid), + mInstaller.createUserData(packageName, UserId.getUid(userId, uid), userId); } } @@ -367,6 +384,8 @@ public class UserManager { /** * Returns the next available user id, filling in any holes in the ids. + * TODO: May not be a good idea to recycle ids, in case it results in confusion + * for data and battery stats collection, or unexpected cross-talk. * @return */ private int getNextAvailableId() { @@ -390,14 +409,8 @@ public class UserManager { 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); - } - } + mInstaller.cloneUserData(0, id, false); + final long stopTime = SystemClock.elapsedRealtime(); Log.i(LOG_TAG, "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 19d94a1fdbad..04c1c9862b41 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -95,6 +95,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseIntArray; import android.util.TypedValue; +import android.view.Choreographer; import android.view.Display; import android.view.Gravity; import android.view.IApplicationToken; @@ -141,7 +142,8 @@ import java.util.List; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub - implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { + implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs, + Choreographer.OnAnimateListener { static final String TAG = "WindowManager"; static final boolean DEBUG = false; static final boolean DEBUG_ADD_REMOVE = false; @@ -456,7 +458,7 @@ public class WindowManagerService extends IWindowManager.Stub int mDeferredRotationPauseCount; boolean mLayoutNeeded = true; - boolean mAnimationPending = false; + boolean mTraversalScheduled = false; boolean mDisplayFrozen = false; boolean mWaitingForConfig = false; boolean mWindowsFreezingScreen = false; @@ -503,14 +505,18 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics(); - H mH = new H(); + final H mH = new H(); + + final Choreographer mChoreographer = Choreographer.getInstance(); WindowState mCurrentFocus = null; WindowState mLastFocus = null; - // This just indicates the window the input method is on top of, not - // necessarily the window its input is going to. + /** This just indicates the window the input method is on top of, not + * necessarily the window its input is going to. */ WindowState mInputMethodTarget = null; + + /** If true hold off on modifying the animation layer of mInputMethodTarget */ boolean mInputMethodTargetWaitingAnim; int mInputMethodAnimLayerAdjustment; @@ -559,6 +565,7 @@ public class WindowManagerService extends IWindowManager.Stub float mWindowAnimationScale = 1.0f; float mTransitionAnimationScale = 1.0f; + float mAnimatorDurationScale = 1.0f; final InputManager mInputManager; @@ -570,6 +577,29 @@ public class WindowManagerService extends IWindowManager.Stub DragState mDragState = null; + /** Pulled out of performLayoutAndPlaceSurfacesLockedInner in order to refactor into multiple + * methods. */ + private class LayoutAndSurfaceFields { + private boolean mAnimating = false; + private boolean mWallpaperForceHidingChanged = false; + private boolean mTokenMayBeDrawn = false; + private boolean mWallpaperMayChange = false; + private boolean mForceHiding = false; + private WindowState mDetachedWallpaper = null; + private WindowState mWindowAnimationBackground = null; + private int mWindowAnimationBackgroundColor = 0; + private boolean mOrientationChangeComplete = true; + private int mAdjResult = 0; + private Session mHoldScreen = null; + private boolean mObscured = false; + private boolean mBlurring = false; + private boolean mDimming = false; + private boolean mSyswin = false; + private float mScreenBrightness = -1; + private float mButtonBrightness = -1; + } + private LayoutAndSurfaceFields mInnerFields = new LayoutAndSurfaceFields(); + final class DragInputEventReceiver extends InputEventReceiver { public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); @@ -691,6 +721,7 @@ public class WindowManagerService extends IWindowManager.Stub Looper.prepare(); WindowManagerService s = new WindowManagerService(mContext, mPM, mHaveInputMethods, mAllowBootMessages); + s.mChoreographer.addOnAnimateListener(s); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_DISPLAY); android.os.Process.setCanSelfBackground(false); @@ -774,6 +805,8 @@ public class WindowManagerService extends IWindowManager.Stub Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale); mTransitionAnimationScale = Settings.System.getFloat(context.getContentResolver(), Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale); + mAnimatorDurationScale = Settings.System.getFloat(context.getContentResolver(), + Settings.System.ANIMATOR_DURATION_SCALE, mTransitionAnimationScale); // Track changes to DevicePolicyManager state so we can enable/disable keyguard. IntentFilter filter = new IntentFilter(); @@ -1082,6 +1115,11 @@ public class WindowManagerService extends IWindowManager.Stub return false; } + /** + * Dig through the WindowStates and find the one that the Input Method will target. + * @param willMove + * @return The index+1 in mWindows of the discovered target. + */ int findDesiredInputMethodWindowIndexLocked(boolean willMove) { final ArrayList<WindowState> localmWindows = mWindows; final int N = localmWindows.size(); @@ -1114,8 +1152,10 @@ public class WindowManagerService extends IWindowManager.Stub } } + // Now w is either mWindows[0] or an IME (or null if mWindows is empty). + if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG, "Proposed new IME target: " + w); - + // Now, a special case -- if the last target's window is in the // process of exiting, and is above the new target, keep on the // last target to avoid flicker. Consider for example a Dialog with @@ -1147,8 +1187,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState highestTarget = null; int highestPos = 0; if (token.animating || token.animation != null) { - int pos = 0; - pos = localmWindows.indexOf(curTarget); + int pos = localmWindows.indexOf(curTarget); while (pos >= 0) { WindowState win = localmWindows.get(pos); if (win.mAppToken != token) { @@ -4657,6 +4696,7 @@ public class WindowManagerService extends IWindowManager.Stub switch (which) { case 0: mWindowAnimationScale = fixScale(scale); break; case 1: mTransitionAnimationScale = fixScale(scale); break; + case 2: mAnimatorDurationScale = fixScale(scale); break; } // Persist setting @@ -4676,6 +4716,9 @@ public class WindowManagerService extends IWindowManager.Stub if (scales.length >= 2) { mTransitionAnimationScale = fixScale(scales[1]); } + if (scales.length >= 3) { + mAnimatorDurationScale = fixScale(scales[2]); + } } // Persist setting @@ -4686,12 +4729,14 @@ public class WindowManagerService extends IWindowManager.Stub switch (which) { case 0: return mWindowAnimationScale; case 1: return mTransitionAnimationScale; + case 2: return mAnimatorDurationScale; } return 0; } public float[] getAnimationScales() { - return new float[] { mWindowAnimationScale, mTransitionAnimationScale }; + return new float[] { mWindowAnimationScale, mTransitionAnimationScale, + mAnimatorDurationScale }; } public int getSwitchState(int sw) { @@ -5390,7 +5435,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mScreenRotationAnimation.setRotation(rotation, mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) { - requestAnimationLocked(0); + mChoreographer.scheduleAnimation(); } } Surface.setOrientation(0, rotation); @@ -6204,7 +6249,6 @@ public class WindowManagerService extends IWindowManager.Stub final IBinder winBinder = window.asBinder(); token = new Binder(); mDragState = new DragState(this, token, surface, /*flags*/ 0, winBinder); - mDragState.mSurface = surface; token = mDragState.mToken = new Binder(); // 5 second timeout for this window to actually begin the drag @@ -6513,7 +6557,7 @@ public class WindowManagerService extends IWindowManager.Stub final class H extends Handler { public static final int REPORT_FOCUS_CHANGE = 2; public static final int REPORT_LOSING_FOCUS = 3; - public static final int ANIMATE = 4; + public static final int DO_TRAVERSAL = 4; public static final int ADD_STARTING = 5; public static final int REMOVE_STARTING = 6; public static final int FINISHED_STARTING = 7; @@ -6607,9 +6651,9 @@ public class WindowManagerService extends IWindowManager.Stub } } break; - case ANIMATE: { + case DO_TRAVERSAL: { synchronized(mWindowMap) { - mAnimationPending = false; + mTraversalScheduled = false; performLayoutAndPlaceSurfacesLocked(); } } break; @@ -6825,12 +6869,14 @@ public class WindowManagerService extends IWindowManager.Stub Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale); Settings.System.putFloat(mContext.getContentResolver(), Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale); + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.ANIMATOR_DURATION_SCALE, mAnimatorDurationScale); break; } case FORCE_GC: { synchronized(mWindowMap) { - if (mAnimationPending) { + if (mChoreographer.isAnimationScheduled()) { // If we are animating, don't do the gc now but // delay a bit so we don't interrupt the animation. mH.sendMessageDelayed(mH.obtainMessage(H.FORCE_GC), @@ -7369,7 +7415,7 @@ public class WindowManagerService extends IWindowManager.Stub try { performLayoutAndPlaceSurfacesLockedInner(recoveringMemory); - int N = mPendingRemove.size(); + final int N = mPendingRemove.size(); if (N > 0) { if (mPendingRemoveTmp.length < N) { mPendingRemoveTmp = new WindowState[N+10]; @@ -7389,7 +7435,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { mInLayout = false; if (mLayoutNeeded) { - requestAnimationLocked(0); + requestTraversalLocked(); } } if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) { @@ -7402,9 +7448,9 @@ public class WindowManagerService extends IWindowManager.Stub } } - private final int performLayoutLockedInner(boolean initial, boolean updateInputWindows) { + private final void performLayoutLockedInner(boolean initial, boolean updateInputWindows) { if (!mLayoutNeeded) { - return 0; + return; } mLayoutNeeded = false; @@ -7436,7 +7482,7 @@ public class WindowManagerService extends IWindowManager.Stub // to another window). int topAttached = -1; for (i = N-1; i >= 0; i--) { - WindowState win = mWindows.get(i); + final WindowState win = mWindows.get(i); // Don't do layout of a window if it is not visible, or // soon won't be visible, to avoid wasting time and funky @@ -7492,7 +7538,7 @@ public class WindowManagerService extends IWindowManager.Stub // XXX does not deal with windows that are attached to windows // that are themselves attached. for (i = topAttached; i >= 0; i--) { - WindowState win = mWindows.get(i); + final WindowState win = mWindows.get(i); if (win.mLayoutAttached) { if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + win @@ -7528,7 +7574,7 @@ public class WindowManagerService extends IWindowManager.Stub mInputMonitor.updateInputWindowsLw(false /*force*/); } - return mPolicy.finishLayoutLw(); + mPolicy.finishLayoutLw(); } void makeWindowFreezingScreenIfNeededLocked(WindowState w) { @@ -7550,6 +7596,1006 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * Update animations of all applications, including those associated with exiting/removed apps. + * + * @param currentTime The time which animations use for calculating transitions. + * @param innerDw Width of app window. + * @param innerDh Height of app window. + * @return true if rotation has stopped, false otherwise + */ + private boolean updateAppsAndRotationAnimationsLocked(long currentTime, + int innerDw, int innerDh) { + int i; + final int NAT = mAppTokens.size(); + for (i=0; i<NAT; i++) { + if (mAppTokens.get(i).stepAnimationLocked(currentTime, + innerDw, innerDh)) { + mInnerFields.mAnimating = true; + } + } + final int NEAT = mExitingAppTokens.size(); + for (i=0; i<NEAT; i++) { + if (mExitingAppTokens.get(i).stepAnimationLocked(currentTime, + innerDw, innerDh)) { + mInnerFields.mAnimating = true; + } + } + + boolean updateRotation = false; + if (mScreenRotationAnimation != null) { + if (mScreenRotationAnimation.isAnimating()) { + if (mScreenRotationAnimation.stepAnimation(currentTime)) { + mInnerFields.mAnimating = true; + } else { + updateRotation = true; + } + } + } + + return updateRotation; + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @param currentTime The time which animations use for calculating transitions. + * @param innerDw Width of app window. + * @param innerDh Height of app window. + */ + private void updateWindowsAndWallpaperLocked(final long currentTime, + final int innerDw, final int innerDh) { + int i; + final int N = mWindows.size(); + + for (i=N-1; i>=0; i--) { + WindowState w = mWindows.get(i); + + final WindowManager.LayoutParams attrs = w.mAttrs; + + if (w.mSurface != null) { + // Take care of the window being ready to display. + if (w.commitFinishDrawingLocked(currentTime)) { + if ((w.mAttrs.flags + & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "First draw done in potential wallpaper target " + w); + mInnerFields.mWallpaperMayChange = true; + } + } + + final boolean wasAnimating = w.mAnimating; + + int animDw = innerDw; + int animDh = innerDh; + + // If the window has moved due to its containing + // content frame changing, then we'd like to animate + // it. The checks here are ordered by what is least + // likely to be true first. + if (w.shouldAnimateMove()) { + // Frame has moved, containing content frame + // has also moved, and we're not currently animating... + // let's do something. + Animation a = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.window_move_from_decor); + w.setAnimation(a); + animDw = w.mLastFrame.left - w.mFrame.left; + animDh = w.mLastFrame.top - w.mFrame.top; + } + + // Execute animation. + final boolean nowAnimating = w.stepAnimationLocked(currentTime, + animDw, animDh); + + // If this window is animating, make a note that we have + // an animating window and take care of a request to run + // a detached wallpaper animation. + if (nowAnimating) { + if (w.mAnimation != null) { + if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 + && w.mAnimation.getDetachWallpaper()) { + mInnerFields.mDetachedWallpaper = w; + } + if (w.mAnimation.getBackgroundColor() != 0) { + if (mInnerFields.mWindowAnimationBackground == null + || (w.mAnimLayer < + mInnerFields.mWindowAnimationBackground.mAnimLayer)) { + mInnerFields.mWindowAnimationBackground = w; + mInnerFields.mWindowAnimationBackgroundColor = + w.mAnimation.getBackgroundColor(); + } + } + } + mInnerFields.mAnimating = true; + } + + // If this window's app token is running a detached wallpaper + // animation, make a note so we can ensure the wallpaper is + // displayed behind it. + if (w.mAppToken != null && w.mAppToken.animation != null + && w.mAppToken.animating) { + if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 + && w.mAppToken.animation.getDetachWallpaper()) { + mInnerFields.mDetachedWallpaper = w; + } + if (w.mAppToken.animation.getBackgroundColor() != 0) { + if (mInnerFields.mWindowAnimationBackground == null + || (w.mAnimLayer < + mInnerFields.mWindowAnimationBackground.mAnimLayer)) { + mInnerFields.mWindowAnimationBackground = w; + mInnerFields.mWindowAnimationBackgroundColor = + w.mAppToken.animation.getBackgroundColor(); + } + } + } + + if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) { + mInnerFields.mWallpaperMayChange = true; + } + + if (mPolicy.doesForceHide(w, attrs)) { + if (!wasAnimating && nowAnimating) { + if (DEBUG_VISIBILITY) Slog.v(TAG, + "Animation started that could impact force hide: " + + w); + mInnerFields.mWallpaperForceHidingChanged = true; + mFocusMayChange = true; + } else if (w.isReadyForDisplay() && w.mAnimation == null) { + mInnerFields.mForceHiding = true; + } + } else if (mPolicy.canBeForceHidden(w, attrs)) { + boolean changed; + if (mInnerFields.mForceHiding) { + changed = w.hideLw(false, false); + if (DEBUG_VISIBILITY && changed) Slog.v(TAG, + "Now policy hidden: " + w); + } else { + changed = w.showLw(false, false); + if (DEBUG_VISIBILITY && changed) Slog.v(TAG, + "Now policy shown: " + w); + if (changed) { + if (mInnerFields.mWallpaperForceHidingChanged + && w.isVisibleNow() /*w.isReadyForDisplay()*/) { + // Assume we will need to animate. If + // we don't (because the wallpaper will + // stay with the lock screen), then we will + // clean up later. + Animation a = mPolicy.createForceHideEnterAnimation(); + if (a != null) { + w.setAnimation(a); + } + } + if (mCurrentFocus == null || + mCurrentFocus.mLayer < w.mLayer) { + // We are showing on to of the current + // focus, so re-evaluate focus to make + // sure it is correct. + mFocusMayChange = true; + } + } + } + if (changed && (attrs.flags + & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + mInnerFields.mWallpaperMayChange = true; + } + } + + mPolicy.animatingWindowLw(w, attrs); + } + + final AppWindowToken atoken = w.mAppToken; + if (atoken != null && (!atoken.allDrawn || atoken.freezingScreen)) { + if (atoken.lastTransactionSequence != mTransactionSequence) { + atoken.lastTransactionSequence = mTransactionSequence; + atoken.numInterestingWindows = atoken.numDrawnWindows = 0; + atoken.startingDisplayed = false; + } + if ((w.isOnScreen() || w.mAttrs.type + == WindowManager.LayoutParams.TYPE_BASE_APPLICATION) + && !w.mExiting && !w.mDestroying) { + if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { + Slog.v(TAG, "Eval win " + w + ": isDrawn=" + + w.isDrawnLw() + + ", isAnimating=" + w.isAnimating()); + if (!w.isDrawnLw()) { + Slog.v(TAG, "Not displayed: s=" + w.mSurface + + " pv=" + w.mPolicyVisibility + + " dp=" + w.mDrawPending + + " cdp=" + w.mCommitDrawPending + + " ah=" + w.mAttachedHidden + + " th=" + atoken.hiddenRequested + + " a=" + w.mAnimating); + } + } + if (w != atoken.startingWindow) { + if (!atoken.freezingScreen || !w.mAppFreezing) { + atoken.numInterestingWindows++; + if (w.isDrawnLw()) { + atoken.numDrawnWindows++; + if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, + "tokenMayBeDrawn: " + atoken + + " freezingScreen=" + atoken.freezingScreen + + " mAppFreezing=" + w.mAppFreezing); + mInnerFields.mTokenMayBeDrawn = true; + } + } + } else if (w.isDrawnLw()) { + atoken.startingDisplayed = true; + } + } + } else if (w.mReadyToShow) { + w.performShowLocked(); + } + } // end forall windows + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @return bitmap indicating if another pass through layout must be made. + */ + private int testTokenMayBeDrawnLocked() { + int changes = 0; + // See if any windows have been drawn, so they (and others + // associated with them) can now be shown. + final int NT = mAppTokens.size(); + for (int i=0; i<NT; i++) { + AppWindowToken wtoken = mAppTokens.get(i); + if (wtoken.freezingScreen) { + int numInteresting = wtoken.numInterestingWindows; + if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { + if (DEBUG_VISIBILITY) Slog.v(TAG, + "allDrawn: " + wtoken + + " interesting=" + numInteresting + + " drawn=" + wtoken.numDrawnWindows); + wtoken.showAllWindowsLocked(); + unsetAppFreezingScreenLocked(wtoken, false, true); + if (DEBUG_ORIENTATION) Slog.i(TAG, + "Setting mOrientationChangeComplete=true because wtoken " + + wtoken + " numInteresting=" + numInteresting + + " numDrawn=" + wtoken.numDrawnWindows); + mInnerFields.mOrientationChangeComplete = true; + } + } else if (!wtoken.allDrawn) { + int numInteresting = wtoken.numInterestingWindows; + if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { + if (DEBUG_VISIBILITY) Slog.v(TAG, + "allDrawn: " + wtoken + + " interesting=" + numInteresting + + " drawn=" + wtoken.numDrawnWindows); + wtoken.allDrawn = true; + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; + + // We can now show all of the drawn windows! + if (!mOpeningApps.contains(wtoken)) { + wtoken.showAllWindowsLocked(); + } + } + } + } + + return changes; + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @return bitmap indicating if another pass through layout must be made. + */ + public int handleAppTransitionReadyLocked() { + int changes = 0; + int i; + int NN = mOpeningApps.size(); + boolean goodToGo = true; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Checking " + NN + " opening apps (frozen=" + + mDisplayFrozen + " timeout=" + + mAppTransitionTimeout + ")..."); + if (!mDisplayFrozen && !mAppTransitionTimeout) { + // If the display isn't frozen, wait to do anything until + // all of the apps are ready. Otherwise just go because + // we'll unfreeze the display when everyone is ready. + for (i=0; i<NN && goodToGo; i++) { + AppWindowToken wtoken = mOpeningApps.get(i); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Check opening app" + wtoken + ": allDrawn=" + + wtoken.allDrawn + " startingDisplayed=" + + wtoken.startingDisplayed); + if (!wtoken.allDrawn && !wtoken.startingDisplayed + && !wtoken.startingMoved) { + goodToGo = false; + } + } + } + if (goodToGo) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO"); + int transit = mNextAppTransition; + if (mSkipAppTransitionAnimation) { + transit = WindowManagerPolicy.TRANSIT_UNSET; + } + mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; + mAppTransitionReady = false; + mAppTransitionRunning = true; + mAppTransitionTimeout = false; + mStartingIconInTransition = false; + mSkipAppTransitionAnimation = false; + + mH.removeMessages(H.APP_TRANSITION_TIMEOUT); + + // If there are applications waiting to come to the + // top of the stack, now is the time to move their windows. + // (Note that we don't do apps going to the bottom + // here -- we want to keep their windows in the old + // Z-order until the animation completes.) + if (mToTopApps.size() > 0) { + NN = mAppTokens.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken = mAppTokens.get(i); + if (wtoken.sendingToTop) { + wtoken.sendingToTop = false; + moveAppWindowsLocked(wtoken, NN, false); + } + } + mToTopApps.clear(); + } + + WindowState oldWallpaper = mWallpaperTarget; + + adjustWallpaperWindowsLocked(); + mInnerFields.mWallpaperMayChange = false; + + // The top-most window will supply the layout params, + // and we will determine it below. + LayoutParams animLp = null; + int bestAnimLayer = -1; + boolean fullscreenAnim = false; + + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New wallpaper target=" + mWallpaperTarget + + ", lower target=" + mLowerWallpaperTarget + + ", upper target=" + mUpperWallpaperTarget); + int foundWallpapers = 0; + // Do a first pass through the tokens for two + // things: + // (1) Determine if both the closing and opening + // app token sets are wallpaper targets, in which + // case special animations are needed + // (since the wallpaper needs to stay static + // behind them). + // (2) Find the layout params of the top-most + // application window in the tokens, which is + // what will control the animation theme. + final int NC = mClosingApps.size(); + NN = NC + mOpeningApps.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken; + int mode; + if (i < NC) { + wtoken = mClosingApps.get(i); + mode = 1; + } else { + wtoken = mOpeningApps.get(i-NC); + mode = 2; + } + if (mLowerWallpaperTarget != null) { + if (mLowerWallpaperTarget.mAppToken == wtoken + || mUpperWallpaperTarget.mAppToken == wtoken) { + foundWallpapers |= mode; + } + } + if (wtoken.appFullscreen) { + WindowState ws = wtoken.findMainWindow(); + if (ws != null) { + animLp = ws.mAttrs; + bestAnimLayer = ws.mLayer; + fullscreenAnim = true; + } + } else if (!fullscreenAnim) { + WindowState ws = wtoken.findMainWindow(); + if (ws != null) { + if (ws.mLayer > bestAnimLayer) { + animLp = ws.mAttrs; + bestAnimLayer = ws.mLayer; + } + } + } + } + + if (foundWallpapers == 3) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Wallpaper animation!"); + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN; + break; + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + case WindowManagerPolicy.TRANSIT_TASK_CLOSE: + case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE; + break; + } + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New transit: " + transit); + } else if (oldWallpaper != null) { + // We are transitioning from an activity with + // a wallpaper to one without. + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New transit away from wallpaper: " + transit); + } else if (mWallpaperTarget != null) { + // We are transitioning from an activity without + // a wallpaper to now showing the wallpaper + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New transit into wallpaper: " + transit); + } + + // If all closing windows are obscured, then there is + // no need to do an animation. This is the case, for + // example, when this transition is being done behind + // the lock screen. + if (!mPolicy.allowAppAnimationsLw()) { + animLp = null; + } + + NN = mOpeningApps.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken = mOpeningApps.get(i); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Now opening app" + wtoken); + wtoken.reportedVisible = false; + wtoken.inPendingTransaction = false; + wtoken.animation = null; + setTokenVisibilityLocked(wtoken, animLp, true, + transit, false); + wtoken.updateReportedVisibilityLocked(); + wtoken.waitingToShow = false; + wtoken.showAllWindowsLocked(); + } + NN = mClosingApps.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken = mClosingApps.get(i); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Now closing app" + wtoken); + wtoken.inPendingTransaction = false; + wtoken.animation = null; + setTokenVisibilityLocked(wtoken, animLp, false, + transit, false); + wtoken.updateReportedVisibilityLocked(); + wtoken.waitingToHide = false; + // Force the allDrawn flag, because we want to start + // this guy's animations regardless of whether it's + // gotten drawn. + wtoken.allDrawn = true; + } + + mNextAppTransitionPackage = null; + + mOpeningApps.clear(); + mClosingApps.clear(); + + // This has changed the visibility of windows, so perform + // a new layout to get them all up-to-date. + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT + | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; + mLayoutNeeded = true; + if (!moveInputMethodWindowsIfNeededLocked(true)) { + assignLayersLocked(); + } + updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/); + mFocusMayChange = false; + } + + return changes; + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @return bitmap indicating if another pass through layout must be made. + */ + private int handleAnimatingAndTransitionLocked() { + int changes = 0; + + mAppTransitionRunning = false; + // Clear information about apps that were moving. + mToBottomApps.clear(); + + rebuildAppWindowListLocked(); + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + mInnerFields.mAdjResult |= ADJUST_WALLPAPER_LAYERS_CHANGED; + moveInputMethodWindowsIfNeededLocked(false); + mInnerFields.mWallpaperMayChange = true; + // Since the window list has been rebuilt, focus might + // have to be recomputed since the actual order of windows + // might have changed again. + mFocusMayChange = true; + + return changes; + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @return bitmap indicating if another pass through layout must be made. + */ + private int animateAwayWallpaperLocked() { + int changes = 0; + WindowState oldWallpaper = mWallpaperTarget; + if (mLowerWallpaperTarget != null + && mLowerWallpaperTarget.mAppToken != null) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "wallpaperForceHiding changed with lower=" + + mLowerWallpaperTarget); + if (DEBUG_WALLPAPER) Slog.v(TAG, + "hidden=" + mLowerWallpaperTarget.mAppToken.hidden + + " hiddenRequested=" + mLowerWallpaperTarget.mAppToken.hiddenRequested); + if (mLowerWallpaperTarget.mAppToken.hidden) { + // The lower target has become hidden before we + // actually started the animation... let's completely + // re-evaluate everything. + mLowerWallpaperTarget = mUpperWallpaperTarget = null; + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; + } + } + mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked(); + mInnerFields.mWallpaperMayChange = false; + mInnerFields.mWallpaperForceHidingChanged = false; + if (DEBUG_WALLPAPER) Slog.v(TAG, "****** OLD: " + oldWallpaper + + " NEW: " + mWallpaperTarget + + " LOWER: " + mLowerWallpaperTarget); + if (mLowerWallpaperTarget == null) { + // Whoops, we don't need a special wallpaper animation. + // Clear them out. + mInnerFields.mForceHiding = false; + for (int i=mWindows.size()-1; i>=0; i--) { + WindowState w = mWindows.get(i); + if (w.mSurface != null) { + final WindowManager.LayoutParams attrs = w.mAttrs; + if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) { + if (DEBUG_FOCUS) Slog.i(TAG, "win=" + w + " force hides other windows"); + mInnerFields.mForceHiding = true; + } else if (mPolicy.canBeForceHidden(w, attrs)) { + if (!w.mAnimating) { + // We set the animation above so it + // is not yet running. + w.clearAnimation(); + } + } + } + } + } + return changes; + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @return bitmap indicating if another pass through layout must be made. + */ + private int testWallpaperAndBackgroundLocked() { + int changes = 0; + + if (mWindowDetachedWallpaper != mInnerFields.mDetachedWallpaper) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "Detached wallpaper changed from " + mWindowDetachedWallpaper + + " to " + mInnerFields.mDetachedWallpaper); + mWindowDetachedWallpaper = mInnerFields.mDetachedWallpaper; + mInnerFields.mWallpaperMayChange = true; + } + + if (mInnerFields.mWindowAnimationBackgroundColor != 0) { + // If the window that wants black is the current wallpaper + // target, then the black goes *below* the wallpaper so we + // don't cause the wallpaper to suddenly disappear. + WindowState target = mInnerFields.mWindowAnimationBackground; + if (mWallpaperTarget == mInnerFields.mWindowAnimationBackground + || mLowerWallpaperTarget == mInnerFields.mWindowAnimationBackground + || mUpperWallpaperTarget == mInnerFields.mWindowAnimationBackground) { + for (int i=0; i<mWindows.size(); i++) { + WindowState w = mWindows.get(i); + if (w.mIsWallpaper) { + target = w; + break; + } + } + } + if (mWindowAnimationBackgroundSurface == null) { + mWindowAnimationBackgroundSurface = new DimSurface(mFxSession); + } + final int dw = mCurDisplayWidth; + final int dh = mCurDisplayHeight; + mWindowAnimationBackgroundSurface.show(dw, dh, + target.mAnimLayer - LAYER_OFFSET_DIM, + mInnerFields.mWindowAnimationBackgroundColor); + } else if (mWindowAnimationBackgroundSurface != null) { + mWindowAnimationBackgroundSurface.hide(); + } + + if (mInnerFields.mWallpaperMayChange) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "Wallpaper may change! Adjusting"); + mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked(); + } + + if ((mInnerFields.mAdjResult&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "Wallpaper layer changed: assigning layers + relayout"); + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + assignLayersLocked(); + } else if ((mInnerFields.mAdjResult&ADJUST_WALLPAPER_VISIBILITY_CHANGED) != 0) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "Wallpaper visibility changed: relayout"); + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + } + + if (mFocusMayChange) { + mFocusMayChange = false; + if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/)) { + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; + mInnerFields.mAdjResult = 0; + } + } + + return changes; + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @param w WindowState whos Surface is being prepared. + * @param recoveringMemory true if the caller will reclaim surface memory on error. + */ + public void prepareSurfaceLocked(final WindowState w, final boolean recoveringMemory) { + // XXX NOTE: The logic here could be improved. We have + // the decision about whether to resize a window separated + // from whether to hide the surface. This can cause us to + // resize a surface even if we are going to hide it. You + // can see this by (1) holding device in landscape mode on + // home screen; (2) tapping browser icon (device will rotate + // to landscape; (3) tap home. The wallpaper will be resized + // in step 2 but then immediately hidden, causing us to + // have to resize and then redraw it again in step 3. It + // would be nice to figure out how to avoid this, but it is + // difficult because we do need to resize surfaces in some + // cases while they are hidden such as when first showing a + // window. + boolean displayed = false; + + w.computeShownFrameLocked(); + + int width, height; + if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) { + // for a scaled surface, we just want to use + // the requested size. + width = w.mRequestedWidth; + height = w.mRequestedHeight; + } else { + width = w.mCompatFrame.width(); + height = w.mCompatFrame.height(); + } + + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + final boolean surfaceResized = w.mSurfaceW != width || w.mSurfaceH != height; + if (surfaceResized) { + w.mSurfaceW = width; + w.mSurfaceH = height; + } + + if (w.mSurfaceX != w.mShownFrame.left + || w.mSurfaceY != w.mShownFrame.top) { + try { + if (SHOW_TRANSACTIONS) logSurface(w, + "POS " + w.mShownFrame.left + + ", " + w.mShownFrame.top, null); + w.mSurfaceX = w.mShownFrame.left; + w.mSurfaceY = w.mShownFrame.top; + w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); + } catch (RuntimeException e) { + Slog.w(TAG, "Error positioning surface of " + w + + " pos=(" + w.mShownFrame.left + + "," + w.mShownFrame.top + ")", e); + if (!recoveringMemory) { + reclaimSomeSurfaceMemoryLocked(w, "position", true); + } + } + } + + if (surfaceResized) { + try { + if (SHOW_TRANSACTIONS) logSurface(w, + "SIZE " + width + "x" + height, null); + w.mSurfaceResized = true; + w.mSurface.setSize(width, height); + } catch (RuntimeException e) { + // If something goes wrong with the surface (such + // as running out of memory), don't take down the + // entire system. + Slog.e(TAG, "Error resizing surface of " + w + + " size=(" + width + "x" + height + ")", e); + if (!recoveringMemory) { + reclaimSomeSurfaceMemoryLocked(w, "size", true); + } + } + } + + if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { + w.mContentInsetsChanged |= + !w.mLastContentInsets.equals(w.mContentInsets); + w.mVisibleInsetsChanged |= + !w.mLastVisibleInsets.equals(w.mVisibleInsets); + boolean configChanged = + w.mConfiguration != mCurConfiguration + && (w.mConfiguration == null + || mCurConfiguration.diff(w.mConfiguration) != 0); + if (DEBUG_CONFIGURATION && configChanged) { + Slog.v(TAG, "Win " + w + " config changed: " + + mCurConfiguration); + } + if (localLOGV) Slog.v(TAG, "Resizing " + w + + ": configChanged=" + configChanged + + " last=" + w.mLastFrame + " frame=" + w.mFrame); + w.mLastFrame.set(w.mFrame); + if (w.mContentInsetsChanged + || w.mVisibleInsetsChanged + || w.mSurfaceResized + || configChanged) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) { + Slog.v(TAG, "Resize reasons: " + + " contentInsetsChanged=" + w.mContentInsetsChanged + + " visibleInsetsChanged=" + w.mVisibleInsetsChanged + + " surfaceResized=" + w.mSurfaceResized + + " configChanged=" + configChanged); + } + + w.mLastContentInsets.set(w.mContentInsets); + w.mLastVisibleInsets.set(w.mVisibleInsets); + makeWindowFreezingScreenIfNeededLocked(w); + // If the orientation is changing, then we need to + // hold off on unfreezing the display until this + // window has been redrawn; to do that, we need + // to go through the process of getting informed + // by the application when it has finished drawing. + if (w.mOrientationChanging) { + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation start waiting for draw in " + + w + ", surface " + w.mSurface); + w.mDrawPending = true; + w.mCommitDrawPending = false; + w.mReadyToShow = false; + if (w.mAppToken != null) { + w.mAppToken.allDrawn = false; + } + } + if (!mResizingWindows.contains(w)) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, + "Resizing window " + w + " to " + w.mSurfaceW + + "x" + w.mSurfaceH); + mResizingWindows.add(w); + } + } else if (w.mOrientationChanging) { + if (!w.mDrawPending && !w.mCommitDrawPending) { + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation not waiting for draw in " + + w + ", surface " + w.mSurface); + w.mOrientationChanging = false; + } + } + } + + if (w.mAttachedHidden || !w.isReadyForDisplay()) { + if (!w.mLastHidden) { + //dump(); + w.mLastHidden = true; + if (SHOW_TRANSACTIONS) logSurface(w, + "HIDE (performLayout)", null); + if (w.mSurface != null) { + w.mSurfaceShown = false; + try { + w.mSurface.hide(); + } catch (RuntimeException e) { + Slog.w(TAG, "Exception hiding surface in " + w); + } + } + } + // If we are waiting for this window to handle an + // orientation change, well, it is hidden, so + // doesn't really matter. Note that this does + // introduce a potential glitch if the window + // becomes unhidden before it has drawn for the + // new orientation. + if (w.mOrientationChanging) { + w.mOrientationChanging = false; + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation change skips hidden " + w); + } + } else if (w.mLastLayer != w.mAnimLayer + || w.mLastAlpha != w.mShownAlpha + || w.mLastDsDx != w.mDsDx + || w.mLastDtDx != w.mDtDx + || w.mLastDsDy != w.mDsDy + || w.mLastDtDy != w.mDtDy + || w.mLastHScale != w.mHScale + || w.mLastVScale != w.mVScale + || w.mLastHidden) { + displayed = true; + w.mLastAlpha = w.mShownAlpha; + w.mLastLayer = w.mAnimLayer; + w.mLastDsDx = w.mDsDx; + w.mLastDtDx = w.mDtDx; + w.mLastDsDy = w.mDsDy; + w.mLastDtDy = w.mDtDy; + w.mLastHScale = w.mHScale; + w.mLastVScale = w.mVScale; + if (SHOW_TRANSACTIONS) logSurface(w, + "alpha=" + w.mShownAlpha + " layer=" + w.mAnimLayer + + " matrix=[" + (w.mDsDx*w.mHScale) + + "," + (w.mDtDx*w.mVScale) + + "][" + (w.mDsDy*w.mHScale) + + "," + (w.mDtDy*w.mVScale) + "]", null); + if (w.mSurface != null) { + try { + w.mSurfaceAlpha = w.mShownAlpha; + w.mSurface.setAlpha(w.mShownAlpha); + w.mSurfaceLayer = w.mAnimLayer; + w.mSurface.setLayer(w.mAnimLayer); + w.mSurface.setMatrix( + w.mDsDx*w.mHScale, w.mDtDx*w.mVScale, + w.mDsDy*w.mHScale, w.mDtDy*w.mVScale); + } catch (RuntimeException e) { + Slog.w(TAG, "Error updating surface in " + w, e); + if (!recoveringMemory) { + reclaimSomeSurfaceMemoryLocked(w, "update", true); + } + } + } + + if (w.mLastHidden && !w.mDrawPending + && !w.mCommitDrawPending + && !w.mReadyToShow) { + if (SHOW_TRANSACTIONS) logSurface(w, + "SHOW (performLayout)", null); + if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w + + " during relayout"); + if (showSurfaceRobustlyLocked(w)) { + w.mHasDrawn = true; + w.mLastHidden = false; + } else { + w.mOrientationChanging = false; + } + } + if (w.mSurface != null) { + w.mToken.hasVisible = true; + } + } else { + displayed = true; + } + + if (displayed) { + if (w.mOrientationChanging) { + if (w.mDrawPending || w.mCommitDrawPending) { + mInnerFields.mOrientationChangeComplete = false; + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation continue waiting for draw in " + w); + } else { + w.mOrientationChanging = false; + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation change complete in " + w); + } + } + w.mToken.hasVisible = true; + } + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @param w WindowState this method is applied to. + * @param currentTime The time which animations use for calculating transitions. + * @param innerDw Width of app window. + * @param innerDh Height of app window. + */ + private void handleNotObscuredLocked(final WindowState w, final long currentTime, + final int innerDw, final int innerDh) { + final WindowManager.LayoutParams attrs = w.mAttrs; + final int attrFlags = attrs.flags; + final boolean canBeSeen = w.isDisplayedLw(); + + if (w.mSurface != null) { + if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) { + mInnerFields.mHoldScreen = w.mSession; + } + if (!mInnerFields.mSyswin && w.mAttrs.screenBrightness >= 0 + && mInnerFields.mScreenBrightness < 0) { + mInnerFields.mScreenBrightness = w.mAttrs.screenBrightness; + } + if (!mInnerFields.mSyswin && w.mAttrs.buttonBrightness >= 0 + && mInnerFields.mButtonBrightness < 0) { + mInnerFields.mButtonBrightness = w.mAttrs.buttonBrightness; + } + if (canBeSeen + && (attrs.type == WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG + || attrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD + || attrs.type == WindowManager.LayoutParams.TYPE_SYSTEM_ERROR)) { + mInnerFields.mSyswin = true; + } + } + + boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); + if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) { + // This window completely covers everything behind it, + // so we want to leave all of them as unblurred (for + // performance reasons). + mInnerFields.mObscured = true; + } else if (canBeSeen && (attrFlags & FLAG_BLUR_BEHIND | FLAG_DIM_BEHIND) != 0) { + if (localLOGV) Slog.v(TAG, "Win " + w + + ": blurring=" + mInnerFields.mBlurring + + " obscured=" + mInnerFields.mObscured); + if ((attrFlags&FLAG_DIM_BEHIND) != 0) { + if (!mInnerFields.mDimming) { + //Slog.i(TAG, "DIM BEHIND: " + w); + mInnerFields.mDimming = true; + if (mDimAnimator == null) { + mDimAnimator = new DimAnimator(mFxSession); + } + mDimAnimator.show(innerDw, innerDh); + mDimAnimator.updateParameters(mContext.getResources(), + w, currentTime); + } + } + if ((attrFlags & FLAG_BLUR_BEHIND) != 0) { + if (!mInnerFields.mBlurring) { + //Slog.i(TAG, "BLUR BEHIND: " + w); + mInnerFields.mBlurring = true; + if (mBlurSurface == null) { + try { + mBlurSurface = new Surface(mFxSession, 0, + "BlurSurface", + -1, 16, 16, + PixelFormat.OPAQUE, + Surface.FX_SURFACE_BLUR); + } catch (Exception e) { + Slog.e(TAG, "Exception creating Blur surface", e); + } + if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " + + mBlurSurface + ": CREATE"); + } + final int dw = mCurDisplayWidth; + final int dh = mCurDisplayHeight; + if (mBlurSurface != null) { + if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " + + mBlurSurface + ": pos=(0,0) (" + + dw + "x" + dh + "), layer=" + (w.mAnimLayer-1)); + mBlurSurface.setPosition(0, 0); + mBlurSurface.setSize(dw, dh); + mBlurSurface.setLayer(w.mAnimLayer-LAYER_OFFSET_BLUR); + if (!mBlurShown) { + try { + if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " + + mBlurSurface + ": SHOW"); + mBlurSurface.show(); + } catch (RuntimeException e) { + Slog.w(TAG, "Failure showing blur surface", e); + } + mBlurShown = true; + } + } + } + } + } + } + // "Something has changed! Let's make it correct now." private final void performLayoutAndPlaceSurfacesLockedInner( boolean recoveringMemory) { @@ -7582,15 +8628,14 @@ public class WindowManagerService extends IWindowManager.Stub mExitingAppTokens.get(i).hasVisible = false; } - boolean orientationChangeComplete = true; - Session holdScreen = null; - float screenBrightness = -1; - float buttonBrightness = -1; + mInnerFields.mOrientationChangeComplete = true; + mInnerFields.mHoldScreen = null; + mInnerFields.mScreenBrightness = -1; + mInnerFields.mButtonBrightness = -1; boolean focusDisplayed = false; - boolean animating = false; + mInnerFields.mAnimating = false; boolean createWatermark = false; boolean updateRotation = false; - boolean screenRotationFinished = false; if (mFxSession == null) { mFxSession = new SurfaceSession(); @@ -7613,7 +8658,7 @@ public class WindowManagerService extends IWindowManager.Stub } try { - boolean wallpaperForceHidingChanged = false; + mInnerFields.mWallpaperForceHidingChanged = false; int repeats = 0; int changes = 0; @@ -7624,655 +8669,93 @@ public class WindowManagerService extends IWindowManager.Stub mLayoutNeeded = false; break; } - - if ((changes&(WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER - | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG - | WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT)) != 0) { - if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { - if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { - assignLayersLocked(); - mLayoutNeeded = true; - } - } - if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { - if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout"); - if (updateOrientationFromAppTokensLocked(true)) { - mLayoutNeeded = true; - mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); - } + + if ((changes & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { + if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { + assignLayersLocked(); + mLayoutNeeded = true; } - if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { + } + if ((changes & WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { + if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout"); + if (updateOrientationFromAppTokensLocked(true)) { mLayoutNeeded = true; + mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } } - + if ((changes & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { + mLayoutNeeded = true; + } + // FIRST LOOP: Perform a layout, if needed. if (repeats < 4) { - changes = performLayoutLockedInner(repeats == 0, false /*updateInputWindows*/); - if (changes != 0) { - continue; - } + performLayoutLockedInner(repeats == 0, false /*updateInputWindows*/); } else { Slog.w(TAG, "Layout repeat skipped after too many iterations"); - changes = 0; } - - final int transactionSequence = ++mTransactionSequence; + + changes = 0; + ++mTransactionSequence; // Update animations of all applications, including those // associated with exiting/removed apps - boolean tokensAnimating = false; - final int NAT = mAppTokens.size(); - for (i=0; i<NAT; i++) { - if (mAppTokens.get(i).stepAnimationLocked(currentTime, - innerDw, innerDh)) { - tokensAnimating = true; - } - } - final int NEAT = mExitingAppTokens.size(); - for (i=0; i<NEAT; i++) { - if (mExitingAppTokens.get(i).stepAnimationLocked(currentTime, - innerDw, innerDh)) { - tokensAnimating = true; - } - } + mInnerFields.mAnimating = false; // SECOND LOOP: Execute animations and update visibility of windows. - - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: seq=" - + transactionSequence + " tokensAnimating=" - + tokensAnimating); - - animating = tokensAnimating; + updateRotation = + updateAppsAndRotationAnimationsLocked(currentTime, innerDw, innerDh); - if (mScreenRotationAnimation != null) { - if (mScreenRotationAnimation.isAnimating()) { - if (mScreenRotationAnimation.stepAnimation(currentTime)) { - animating = true; - } else { - screenRotationFinished = true; - updateRotation = true; - } - } - } + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: seq=" + + mTransactionSequence + " mAnimating=" + + mInnerFields.mAnimating); - boolean tokenMayBeDrawn = false; - boolean wallpaperMayChange = false; - boolean forceHiding = false; - WindowState windowDetachedWallpaper = null; - WindowState windowAnimationBackground = null; - int windowAnimationBackgroundColor = 0; + mInnerFields.mTokenMayBeDrawn = false; + mInnerFields.mWallpaperMayChange = false; + mInnerFields.mForceHiding = false; + mInnerFields.mDetachedWallpaper = null; + mInnerFields.mWindowAnimationBackground = null; + mInnerFields.mWindowAnimationBackgroundColor = 0; mPolicy.beginAnimationLw(dw, dh); - final int N = mWindows.size(); - - for (i=N-1; i>=0; i--) { - WindowState w = mWindows.get(i); - - final WindowManager.LayoutParams attrs = w.mAttrs; - - if (w.mSurface != null) { - // Take care of the window being ready to display. - if (w.commitFinishDrawingLocked(currentTime)) { - if ((w.mAttrs.flags - & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "First draw done in potential wallpaper target " + w); - wallpaperMayChange = true; - } - } - - final boolean wasAnimating = w.mAnimating; - - int animDw = innerDw; - int animDh = innerDh; - - // If the window has moved due to its containing - // content frame changing, then we'd like to animate - // it. The checks here are ordered by what is least - // likely to be true first. - if (w.shouldAnimateMove()) { - // Frame has moved, containing content frame - // has also moved, and we're not currently animating... - // let's do something. - Animation a = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.window_move_from_decor); - w.setAnimation(a); - animDw = w.mLastFrame.left - w.mFrame.left; - animDh = w.mLastFrame.top - w.mFrame.top; - } - - // Execute animation. - final boolean nowAnimating = w.stepAnimationLocked(currentTime, - animDw, animDh); - - // If this window is animating, make a note that we have - // an animating window and take care of a request to run - // a detached wallpaper animation. - if (nowAnimating) { - if (w.mAnimation != null) { - if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 - && w.mAnimation.getDetachWallpaper()) { - windowDetachedWallpaper = w; - } - if (w.mAnimation.getBackgroundColor() != 0) { - if (windowAnimationBackground == null || w.mAnimLayer < - windowAnimationBackground.mAnimLayer) { - windowAnimationBackground = w; - windowAnimationBackgroundColor = - w.mAnimation.getBackgroundColor(); - } - } - } - animating = true; - } - - // If this window's app token is running a detached wallpaper - // animation, make a note so we can ensure the wallpaper is - // displayed behind it. - if (w.mAppToken != null && w.mAppToken.animation != null - && w.mAppToken.animating) { - if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 - && w.mAppToken.animation.getDetachWallpaper()) { - windowDetachedWallpaper = w; - } - if (w.mAppToken.animation.getBackgroundColor() != 0) { - if (windowAnimationBackground == null || w.mAnimLayer < - windowAnimationBackground.mAnimLayer) { - windowAnimationBackground = w; - windowAnimationBackgroundColor = - w.mAppToken.animation.getBackgroundColor(); - } - } - } - - if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) { - wallpaperMayChange = true; - } - - if (mPolicy.doesForceHide(w, attrs)) { - if (!wasAnimating && nowAnimating) { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "Animation started that could impact force hide: " - + w); - wallpaperForceHidingChanged = true; - mFocusMayChange = true; - } else if (w.isReadyForDisplay() && w.mAnimation == null) { - forceHiding = true; - } - } else if (mPolicy.canBeForceHidden(w, attrs)) { - boolean changed; - if (forceHiding) { - changed = w.hideLw(false, false); - if (DEBUG_VISIBILITY && changed) Slog.v(TAG, - "Now policy hidden: " + w); - } else { - changed = w.showLw(false, false); - if (DEBUG_VISIBILITY && changed) Slog.v(TAG, - "Now policy shown: " + w); - if (changed) { - if (wallpaperForceHidingChanged - && w.isVisibleNow() /*w.isReadyForDisplay()*/) { - // Assume we will need to animate. If - // we don't (because the wallpaper will - // stay with the lock screen), then we will - // clean up later. - Animation a = mPolicy.createForceHideEnterAnimation(); - if (a != null) { - w.setAnimation(a); - } - } - if (mCurrentFocus == null || - mCurrentFocus.mLayer < w.mLayer) { - // We are showing on to of the current - // focus, so re-evaluate focus to make - // sure it is correct. - mFocusMayChange = true; - } - } - } - if (changed && (attrs.flags - & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { - wallpaperMayChange = true; - } - } - - mPolicy.animatingWindowLw(w, attrs); - } - - final AppWindowToken atoken = w.mAppToken; - if (atoken != null && (!atoken.allDrawn || atoken.freezingScreen)) { - if (atoken.lastTransactionSequence != transactionSequence) { - atoken.lastTransactionSequence = transactionSequence; - atoken.numInterestingWindows = atoken.numDrawnWindows = 0; - atoken.startingDisplayed = false; - } - if ((w.isOnScreen() || w.mAttrs.type - == WindowManager.LayoutParams.TYPE_BASE_APPLICATION) - && !w.mExiting && !w.mDestroying) { - if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { - Slog.v(TAG, "Eval win " + w + ": isDrawn=" - + w.isDrawnLw() - + ", isAnimating=" + w.isAnimating()); - if (!w.isDrawnLw()) { - Slog.v(TAG, "Not displayed: s=" + w.mSurface - + " pv=" + w.mPolicyVisibility - + " dp=" + w.mDrawPending - + " cdp=" + w.mCommitDrawPending - + " ah=" + w.mAttachedHidden - + " th=" + atoken.hiddenRequested - + " a=" + w.mAnimating); - } - } - if (w != atoken.startingWindow) { - if (!atoken.freezingScreen || !w.mAppFreezing) { - atoken.numInterestingWindows++; - if (w.isDrawnLw()) { - atoken.numDrawnWindows++; - if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, - "tokenMayBeDrawn: " + atoken - + " freezingScreen=" + atoken.freezingScreen - + " mAppFreezing=" + w.mAppFreezing); - tokenMayBeDrawn = true; - } - } - } else if (w.isDrawnLw()) { - atoken.startingDisplayed = true; - } - } - } else if (w.mReadyToShow) { - w.performShowLocked(); - } - } + updateWindowsAndWallpaperLocked(currentTime, innerDw, innerDh); changes |= mPolicy.finishAnimationLw(); - if (tokenMayBeDrawn) { - // See if any windows have been drawn, so they (and others - // associated with them) can now be shown. - final int NT = mAppTokens.size(); - for (i=0; i<NT; i++) { - AppWindowToken wtoken = mAppTokens.get(i); - if (wtoken.freezingScreen) { - int numInteresting = wtoken.numInterestingWindows; - if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "allDrawn: " + wtoken - + " interesting=" + numInteresting - + " drawn=" + wtoken.numDrawnWindows); - wtoken.showAllWindowsLocked(); - unsetAppFreezingScreenLocked(wtoken, false, true); - if (DEBUG_ORIENTATION) Slog.i(TAG, - "Setting orientationChangeComplete=true because wtoken " - + wtoken + " numInteresting=" + numInteresting - + " numDrawn=" + wtoken.numDrawnWindows); - orientationChangeComplete = true; - } - } else if (!wtoken.allDrawn) { - int numInteresting = wtoken.numInterestingWindows; - if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "allDrawn: " + wtoken - + " interesting=" + numInteresting - + " drawn=" + wtoken.numDrawnWindows); - wtoken.allDrawn = true; - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; - - // We can now show all of the drawn windows! - if (!mOpeningApps.contains(wtoken)) { - wtoken.showAllWindowsLocked(); - } - } - } - } + if (mInnerFields.mTokenMayBeDrawn) { + changes |= testTokenMayBeDrawnLocked(); } // If we are ready to perform an app transition, check through // all of the app tokens to be shown and see if they are ready // to go. if (mAppTransitionReady) { - int NN = mOpeningApps.size(); - boolean goodToGo = true; - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Checking " + NN + " opening apps (frozen=" - + mDisplayFrozen + " timeout=" - + mAppTransitionTimeout + ")..."); - if (!mDisplayFrozen && !mAppTransitionTimeout) { - // If the display isn't frozen, wait to do anything until - // all of the apps are ready. Otherwise just go because - // we'll unfreeze the display when everyone is ready. - for (i=0; i<NN && goodToGo; i++) { - AppWindowToken wtoken = mOpeningApps.get(i); - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Check opening app" + wtoken + ": allDrawn=" - + wtoken.allDrawn + " startingDisplayed=" - + wtoken.startingDisplayed); - if (!wtoken.allDrawn && !wtoken.startingDisplayed - && !wtoken.startingMoved) { - goodToGo = false; - } - } - } - if (goodToGo) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO"); - int transit = mNextAppTransition; - if (mSkipAppTransitionAnimation) { - transit = WindowManagerPolicy.TRANSIT_UNSET; - } - mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; - mAppTransitionReady = false; - mAppTransitionRunning = true; - mAppTransitionTimeout = false; - mStartingIconInTransition = false; - mSkipAppTransitionAnimation = false; - - mH.removeMessages(H.APP_TRANSITION_TIMEOUT); - - // If there are applications waiting to come to the - // top of the stack, now is the time to move their windows. - // (Note that we don't do apps going to the bottom - // here -- we want to keep their windows in the old - // Z-order until the animation completes.) - if (mToTopApps.size() > 0) { - NN = mAppTokens.size(); - for (i=0; i<NN; i++) { - AppWindowToken wtoken = mAppTokens.get(i); - if (wtoken.sendingToTop) { - wtoken.sendingToTop = false; - moveAppWindowsLocked(wtoken, NN, false); - } - } - mToTopApps.clear(); - } - - WindowState oldWallpaper = mWallpaperTarget; - - adjustWallpaperWindowsLocked(); - wallpaperMayChange = false; - - // The top-most window will supply the layout params, - // and we will determine it below. - LayoutParams animLp = null; - int bestAnimLayer = -1; - boolean fullscreenAnim = false; - - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New wallpaper target=" + mWallpaperTarget - + ", lower target=" + mLowerWallpaperTarget - + ", upper target=" + mUpperWallpaperTarget); - int foundWallpapers = 0; - // Do a first pass through the tokens for two - // things: - // (1) Determine if both the closing and opening - // app token sets are wallpaper targets, in which - // case special animations are needed - // (since the wallpaper needs to stay static - // behind them). - // (2) Find the layout params of the top-most - // application window in the tokens, which is - // what will control the animation theme. - final int NC = mClosingApps.size(); - NN = NC + mOpeningApps.size(); - for (i=0; i<NN; i++) { - AppWindowToken wtoken; - int mode; - if (i < NC) { - wtoken = mClosingApps.get(i); - mode = 1; - } else { - wtoken = mOpeningApps.get(i-NC); - mode = 2; - } - if (mLowerWallpaperTarget != null) { - if (mLowerWallpaperTarget.mAppToken == wtoken - || mUpperWallpaperTarget.mAppToken == wtoken) { - foundWallpapers |= mode; - } - } - if (wtoken.appFullscreen) { - WindowState ws = wtoken.findMainWindow(); - if (ws != null) { - animLp = ws.mAttrs; - bestAnimLayer = ws.mLayer; - fullscreenAnim = true; - } - } else if (!fullscreenAnim) { - WindowState ws = wtoken.findMainWindow(); - if (ws != null) { - if (ws.mLayer > bestAnimLayer) { - animLp = ws.mAttrs; - bestAnimLayer = ws.mLayer; - } - } - } - } - - if (foundWallpapers == 3) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Wallpaper animation!"); - switch (transit) { - case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: - case WindowManagerPolicy.TRANSIT_TASK_OPEN: - case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: - transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN; - break; - case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: - case WindowManagerPolicy.TRANSIT_TASK_CLOSE: - case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: - transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE; - break; - } - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New transit: " + transit); - } else if (oldWallpaper != null) { - // We are transitioning from an activity with - // a wallpaper to one without. - transit = WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE; - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New transit away from wallpaper: " + transit); - } else if (mWallpaperTarget != null) { - // We are transitioning from an activity without - // a wallpaper to now showing the wallpaper - transit = WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN; - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New transit into wallpaper: " + transit); - } - - // If all closing windows are obscured, then there is - // no need to do an animation. This is the case, for - // example, when this transition is being done behind - // the lock screen. - if (!mPolicy.allowAppAnimationsLw()) { - animLp = null; - } - - NN = mOpeningApps.size(); - for (i=0; i<NN; i++) { - AppWindowToken wtoken = mOpeningApps.get(i); - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Now opening app" + wtoken); - wtoken.reportedVisible = false; - wtoken.inPendingTransaction = false; - wtoken.animation = null; - setTokenVisibilityLocked(wtoken, animLp, true, - transit, false); - wtoken.updateReportedVisibilityLocked(); - wtoken.waitingToShow = false; - wtoken.showAllWindowsLocked(); - } - NN = mClosingApps.size(); - for (i=0; i<NN; i++) { - AppWindowToken wtoken = mClosingApps.get(i); - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Now closing app" + wtoken); - wtoken.inPendingTransaction = false; - wtoken.animation = null; - setTokenVisibilityLocked(wtoken, animLp, false, - transit, false); - wtoken.updateReportedVisibilityLocked(); - wtoken.waitingToHide = false; - // Force the allDrawn flag, because we want to start - // this guy's animations regardless of whether it's - // gotten drawn. - wtoken.allDrawn = true; - } - - mNextAppTransitionPackage = null; - - mOpeningApps.clear(); - mClosingApps.clear(); - - // This has changed the visibility of windows, so perform - // a new layout to get them all up-to-date. - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT - | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; - mLayoutNeeded = true; - if (!moveInputMethodWindowsIfNeededLocked(true)) { - assignLayersLocked(); - } - updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, - false /*updateInputWindows*/); - mFocusMayChange = false; - } + changes |= handleAppTransitionReadyLocked(); } - int adjResult = 0; + mInnerFields.mAdjResult = 0; - if (!animating && mAppTransitionRunning) { + if (!mInnerFields.mAnimating && mAppTransitionRunning) { // We have finished the animation of an app transition. To do // this, we have delayed a lot of operations like showing and // hiding apps, moving apps in Z-order, etc. The app token list // reflects the correct Z-order, but the window list may now // be out of sync with it. So here we will just rebuild the // entire app window list. Fun! - mAppTransitionRunning = false; - // Clear information about apps that were moving. - mToBottomApps.clear(); - - rebuildAppWindowListLocked(); - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - adjResult |= ADJUST_WALLPAPER_LAYERS_CHANGED; - moveInputMethodWindowsIfNeededLocked(false); - wallpaperMayChange = true; - // Since the window list has been rebuilt, focus might - // have to be recomputed since the actual order of windows - // might have changed again. - mFocusMayChange = true; + changes |= handleAnimatingAndTransitionLocked(); } - if (wallpaperForceHidingChanged && changes == 0 && !mAppTransitionReady) { + if (mInnerFields.mWallpaperForceHidingChanged && changes == 0 && !mAppTransitionReady) { // At this point, there was a window with a wallpaper that // was force hiding other windows behind it, but now it // is going away. This may be simple -- just animate // away the wallpaper and its window -- or it may be // hard -- the wallpaper now needs to be shown behind // something that was hidden. - WindowState oldWallpaper = mWallpaperTarget; - if (mLowerWallpaperTarget != null - && mLowerWallpaperTarget.mAppToken != null) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "wallpaperForceHiding changed with lower=" - + mLowerWallpaperTarget); - if (DEBUG_WALLPAPER) Slog.v(TAG, - "hidden=" + mLowerWallpaperTarget.mAppToken.hidden + - " hiddenRequested=" + mLowerWallpaperTarget.mAppToken.hiddenRequested); - if (mLowerWallpaperTarget.mAppToken.hidden) { - // The lower target has become hidden before we - // actually started the animation... let's completely - // re-evaluate everything. - mLowerWallpaperTarget = mUpperWallpaperTarget = null; - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; - } - } - adjResult |= adjustWallpaperWindowsLocked(); - wallpaperMayChange = false; - wallpaperForceHidingChanged = false; - if (DEBUG_WALLPAPER) Slog.v(TAG, "****** OLD: " + oldWallpaper - + " NEW: " + mWallpaperTarget - + " LOWER: " + mLowerWallpaperTarget); - if (mLowerWallpaperTarget == null) { - // Whoops, we don't need a special wallpaper animation. - // Clear them out. - forceHiding = false; - for (i=N-1; i>=0; i--) { - WindowState w = mWindows.get(i); - if (w.mSurface != null) { - final WindowManager.LayoutParams attrs = w.mAttrs; - if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) { - if (DEBUG_FOCUS) Slog.i(TAG, "win=" + w + " force hides other windows"); - forceHiding = true; - } else if (mPolicy.canBeForceHidden(w, attrs)) { - if (!w.mAnimating) { - // We set the animation above so it - // is not yet running. - w.clearAnimation(); - } - } - } - } - } - } - - if (mWindowDetachedWallpaper != windowDetachedWallpaper) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Detached wallpaper changed from " + mWindowDetachedWallpaper - + " to " + windowDetachedWallpaper); - mWindowDetachedWallpaper = windowDetachedWallpaper; - wallpaperMayChange = true; - } - - if (windowAnimationBackgroundColor != 0) { - // If the window that wants black is the current wallpaper - // target, then the black goes *below* the wallpaper so we - // don't cause the wallpaper to suddenly disappear. - WindowState target = windowAnimationBackground; - if (mWallpaperTarget == windowAnimationBackground - || mLowerWallpaperTarget == windowAnimationBackground - || mUpperWallpaperTarget == windowAnimationBackground) { - for (i=0; i<mWindows.size(); i++) { - WindowState w = mWindows.get(i); - if (w.mIsWallpaper) { - target = w; - break; - } - } - } - if (mWindowAnimationBackgroundSurface == null) { - mWindowAnimationBackgroundSurface = new DimSurface(mFxSession); - } - mWindowAnimationBackgroundSurface.show(dw, dh, - target.mAnimLayer - LAYER_OFFSET_DIM, - windowAnimationBackgroundColor); - } else if (mWindowAnimationBackgroundSurface != null) { - mWindowAnimationBackgroundSurface.hide(); - } - - if (wallpaperMayChange) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Wallpaper may change! Adjusting"); - adjResult |= adjustWallpaperWindowsLocked(); - } - - if ((adjResult&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Wallpaper layer changed: assigning layers + relayout"); - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - assignLayersLocked(); - } else if ((adjResult&ADJUST_WALLPAPER_VISIBILITY_CHANGED) != 0) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Wallpaper visibility changed: relayout"); - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + changes |= animateAwayWallpaperLocked(); } - if (mFocusMayChange) { - mFocusMayChange = false; - if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, - false /*updateInputWindows*/)) { - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; - adjResult = 0; - } - } + changes |= testWallpaperAndBackgroundLocked(); if (mLayoutNeeded) { changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; @@ -8286,278 +8769,22 @@ public class WindowManagerService extends IWindowManager.Stub final boolean someoneLosingFocus = mLosingFocus.size() != 0; - boolean obscured = false; - boolean blurring = false; - boolean dimming = false; - boolean covered = false; - boolean syswin = false; + mInnerFields.mObscured = false; + mInnerFields.mBlurring = false; + mInnerFields.mDimming = false; + mInnerFields.mSyswin = false; final int N = mWindows.size(); for (i=N-1; i>=0; i--) { WindowState w = mWindows.get(i); - boolean displayed = false; - final WindowManager.LayoutParams attrs = w.mAttrs; - final int attrFlags = attrs.flags; - if (w.mSurface != null) { - // XXX NOTE: The logic here could be improved. We have - // the decision about whether to resize a window separated - // from whether to hide the surface. This can cause us to - // resize a surface even if we are going to hide it. You - // can see this by (1) holding device in landscape mode on - // home screen; (2) tapping browser icon (device will rotate - // to landscape; (3) tap home. The wallpaper will be resized - // in step 2 but then immediately hidden, causing us to - // have to resize and then redraw it again in step 3. It - // would be nice to figure out how to avoid this, but it is - // difficult because we do need to resize surfaces in some - // cases while they are hidden such as when first showing a - // window. - - w.computeShownFrameLocked(); - if (localLOGV) Slog.v( - TAG, "Placing surface #" + i + " " + w.mSurface - + ": new=" + w.mShownFrame); - - if (w.mSurface != null) { - int width, height; - if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) { - // for a scaled surface, we just want to use - // the requested size. - width = w.mRequestedWidth; - height = w.mRequestedHeight; - } else { - width = w.mCompatFrame.width(); - height = w.mCompatFrame.height(); - } - - if (width < 1) { - width = 1; - } - if (height < 1) { - height = 1; - } - final boolean surfaceResized = w.mSurfaceW != width || w.mSurfaceH != height; - if (surfaceResized) { - w.mSurfaceW = width; - w.mSurfaceH = height; - } - - if (w.mSurfaceX != w.mShownFrame.left - || w.mSurfaceY != w.mShownFrame.top) { - try { - if (SHOW_TRANSACTIONS) logSurface(w, - "POS " + w.mShownFrame.left - + ", " + w.mShownFrame.top, null); - w.mSurfaceX = w.mShownFrame.left; - w.mSurfaceY = w.mShownFrame.top; - w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); - } catch (RuntimeException e) { - Slog.w(TAG, "Error positioning surface of " + w - + " pos=(" + w.mShownFrame.left - + "," + w.mShownFrame.top + ")", e); - if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "position", true); - } - } - } - - if (surfaceResized) { - try { - if (SHOW_TRANSACTIONS) logSurface(w, - "SIZE " + width + "x" + height, null); - w.mSurfaceResized = true; - w.mSurface.setSize(width, height); - } catch (RuntimeException e) { - // If something goes wrong with the surface (such - // as running out of memory), don't take down the - // entire system. - Slog.e(TAG, "Error resizing surface of " + w - + " size=(" + width + "x" + height + ")", e); - if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "size", true); - } - } - } - } - - if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { - w.mContentInsetsChanged |= - !w.mLastContentInsets.equals(w.mContentInsets); - w.mVisibleInsetsChanged |= - !w.mLastVisibleInsets.equals(w.mVisibleInsets); - boolean configChanged = - w.mConfiguration != mCurConfiguration - && (w.mConfiguration == null - || mCurConfiguration.diff(w.mConfiguration) != 0); - if (DEBUG_CONFIGURATION && configChanged) { - Slog.v(TAG, "Win " + w + " config changed: " - + mCurConfiguration); - } - if (localLOGV) Slog.v(TAG, "Resizing " + w - + ": configChanged=" + configChanged - + " last=" + w.mLastFrame + " frame=" + w.mFrame); - w.mLastFrame.set(w.mFrame); - if (w.mContentInsetsChanged - || w.mVisibleInsetsChanged - || w.mSurfaceResized - || configChanged) { - if (DEBUG_RESIZE || DEBUG_ORIENTATION) { - Slog.v(TAG, "Resize reasons: " - + " contentInsetsChanged=" + w.mContentInsetsChanged - + " visibleInsetsChanged=" + w.mVisibleInsetsChanged - + " surfaceResized=" + w.mSurfaceResized - + " configChanged=" + configChanged); - } - - w.mLastContentInsets.set(w.mContentInsets); - w.mLastVisibleInsets.set(w.mVisibleInsets); - makeWindowFreezingScreenIfNeededLocked(w); - // If the orientation is changing, then we need to - // hold off on unfreezing the display until this - // window has been redrawn; to do that, we need - // to go through the process of getting informed - // by the application when it has finished drawing. - if (w.mOrientationChanging) { - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation start waiting for draw in " - + w + ", surface " + w.mSurface); - w.mDrawPending = true; - w.mCommitDrawPending = false; - w.mReadyToShow = false; - if (w.mAppToken != null) { - w.mAppToken.allDrawn = false; - } - } - if (!mResizingWindows.contains(w)) { - if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, - "Resizing window " + w + " to " + w.mSurfaceW - + "x" + w.mSurfaceH); - mResizingWindows.add(w); - } - } else if (w.mOrientationChanging) { - if (!w.mDrawPending && !w.mCommitDrawPending) { - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation not waiting for draw in " - + w + ", surface " + w.mSurface); - w.mOrientationChanging = false; - } - } - } - - if (w.mAttachedHidden || !w.isReadyForDisplay()) { - if (!w.mLastHidden) { - //dump(); - w.mLastHidden = true; - if (SHOW_TRANSACTIONS) logSurface(w, - "HIDE (performLayout)", null); - if (w.mSurface != null) { - w.mSurfaceShown = false; - try { - w.mSurface.hide(); - } catch (RuntimeException e) { - Slog.w(TAG, "Exception hiding surface in " + w); - } - } - } - // If we are waiting for this window to handle an - // orientation change, well, it is hidden, so - // doesn't really matter. Note that this does - // introduce a potential glitch if the window - // becomes unhidden before it has drawn for the - // new orientation. - if (w.mOrientationChanging) { - w.mOrientationChanging = false; - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation change skips hidden " + w); - } - } else if (w.mLastLayer != w.mAnimLayer - || w.mLastAlpha != w.mShownAlpha - || w.mLastDsDx != w.mDsDx - || w.mLastDtDx != w.mDtDx - || w.mLastDsDy != w.mDsDy - || w.mLastDtDy != w.mDtDy - || w.mLastHScale != w.mHScale - || w.mLastVScale != w.mVScale - || w.mLastHidden) { - displayed = true; - w.mLastAlpha = w.mShownAlpha; - w.mLastLayer = w.mAnimLayer; - w.mLastDsDx = w.mDsDx; - w.mLastDtDx = w.mDtDx; - w.mLastDsDy = w.mDsDy; - w.mLastDtDy = w.mDtDy; - w.mLastHScale = w.mHScale; - w.mLastVScale = w.mVScale; - if (SHOW_TRANSACTIONS) logSurface(w, - "alpha=" + w.mShownAlpha + " layer=" + w.mAnimLayer - + " matrix=[" + (w.mDsDx*w.mHScale) - + "," + (w.mDtDx*w.mVScale) - + "][" + (w.mDsDy*w.mHScale) - + "," + (w.mDtDy*w.mVScale) + "]", null); - if (w.mSurface != null) { - try { - w.mSurfaceAlpha = w.mShownAlpha; - w.mSurface.setAlpha(w.mShownAlpha); - w.mSurfaceLayer = w.mAnimLayer; - w.mSurface.setLayer(w.mAnimLayer); - w.mSurface.setMatrix( - w.mDsDx*w.mHScale, w.mDtDx*w.mVScale, - w.mDsDy*w.mHScale, w.mDtDy*w.mVScale); - } catch (RuntimeException e) { - Slog.w(TAG, "Error updating surface in " + w, e); - if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "update", true); - } - } - } - - if (w.mLastHidden && !w.mDrawPending - && !w.mCommitDrawPending - && !w.mReadyToShow) { - if (SHOW_TRANSACTIONS) logSurface(w, - "SHOW (performLayout)", null); - if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w - + " during relayout"); - if (showSurfaceRobustlyLocked(w)) { - w.mHasDrawn = true; - w.mLastHidden = false; - } else { - w.mOrientationChanging = false; - } - } - if (w.mSurface != null) { - w.mToken.hasVisible = true; - } - } else { - displayed = true; - } - - if (displayed) { - if (!covered) { - if (attrs.width == LayoutParams.MATCH_PARENT - && attrs.height == LayoutParams.MATCH_PARENT) { - covered = true; - } - } - if (w.mOrientationChanging) { - if (w.mDrawPending || w.mCommitDrawPending) { - orientationChangeComplete = false; - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation continue waiting for draw in " + w); - } else { - w.mOrientationChanging = false; - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation change complete in " + w); - } - } - w.mToken.hasVisible = true; - } + prepareSurfaceLocked(w, recoveringMemory); } else if (w.mOrientationChanging) { - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation change skips hidden " + w); + if (DEBUG_ORIENTATION) { + Slog.v(TAG, "Orientation change skips hidden " + w); + } w.mOrientationChanging = false; } @@ -8572,92 +8799,12 @@ public class WindowManagerService extends IWindowManager.Stub focusDisplayed = true; } - final boolean obscuredChanged = w.mObscured != obscured; + final boolean obscuredChanged = w.mObscured != mInnerFields.mObscured; // Update effect. - if (!(w.mObscured=obscured)) { - if (w.mSurface != null) { - if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) { - holdScreen = w.mSession; - } - if (!syswin && w.mAttrs.screenBrightness >= 0 - && screenBrightness < 0) { - screenBrightness = w.mAttrs.screenBrightness; - } - if (!syswin && w.mAttrs.buttonBrightness >= 0 - && buttonBrightness < 0) { - buttonBrightness = w.mAttrs.buttonBrightness; - } - if (canBeSeen - && (attrs.type == WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG - || attrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD - || attrs.type == WindowManager.LayoutParams.TYPE_SYSTEM_ERROR)) { - syswin = true; - } - } - - boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); - if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) { - // This window completely covers everything behind it, - // so we want to leave all of them as unblurred (for - // performance reasons). - obscured = true; - } else if (canBeSeen && !obscured && - (attrFlags&FLAG_BLUR_BEHIND|FLAG_DIM_BEHIND) != 0) { - if (localLOGV) Slog.v(TAG, "Win " + w - + ": blurring=" + blurring - + " obscured=" + obscured - + " displayed=" + displayed); - if ((attrFlags&FLAG_DIM_BEHIND) != 0) { - if (!dimming) { - //Slog.i(TAG, "DIM BEHIND: " + w); - dimming = true; - if (mDimAnimator == null) { - mDimAnimator = new DimAnimator(mFxSession); - } - mDimAnimator.show(innerDw, innerDh); - mDimAnimator.updateParameters(mContext.getResources(), - w, currentTime); - } - } - if ((attrFlags&FLAG_BLUR_BEHIND) != 0) { - if (!blurring) { - //Slog.i(TAG, "BLUR BEHIND: " + w); - blurring = true; - if (mBlurSurface == null) { - try { - mBlurSurface = new Surface(mFxSession, 0, - "BlurSurface", - -1, 16, 16, - PixelFormat.OPAQUE, - Surface.FX_SURFACE_BLUR); - } catch (Exception e) { - Slog.e(TAG, "Exception creating Blur surface", e); - } - if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " - + mBlurSurface + ": CREATE"); - } - if (mBlurSurface != null) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " - + mBlurSurface + ": pos=(0,0) (" + - dw + "x" + dh + "), layer=" + (w.mAnimLayer-1)); - mBlurSurface.setPosition(0, 0); - mBlurSurface.setSize(dw, dh); - mBlurSurface.setLayer(w.mAnimLayer-LAYER_OFFSET_BLUR); - if (!mBlurShown) { - try { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " - + mBlurSurface + ": SHOW"); - mBlurSurface.show(); - } catch (RuntimeException e) { - Slog.w(TAG, "Failure showing blur surface", e); - } - mBlurShown = true; - } - } - } - } - } + w.mObscured = mInnerFields.mObscured; + if (!mInnerFields.mObscured) { + handleNotObscuredLocked(w, currentTime, innerDw, innerDh); } if (obscuredChanged && mWallpaperTarget == w) { @@ -8669,11 +8816,12 @@ public class WindowManagerService extends IWindowManager.Stub } if (mDimAnimator != null && mDimAnimator.mDimShown) { - animating |= mDimAnimator.updateSurface(dimming, currentTime, - mDisplayFrozen || !mDisplayEnabled || !mPolicy.isScreenOnFully()); + mInnerFields.mAnimating |= + mDimAnimator.updateSurface(mInnerFields.mDimming, currentTime, + mDisplayFrozen || !mDisplayEnabled || !mPolicy.isScreenOnFully()); } - if (!blurring && mBlurShown) { + if (!mInnerFields.mBlurring && mBlurShown) { if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " + mBlurSurface + ": HIDE"); try { @@ -8707,8 +8855,8 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION && mDisplayFrozen) Slog.v(TAG, "With display frozen, orientationChangeComplete=" - + orientationChangeComplete); - if (orientationChangeComplete) { + + mInnerFields.mOrientationChangeComplete); + if (mInnerFields.mOrientationChangeComplete) { if (mWindowsFreezingScreen) { mWindowsFreezingScreen = false; mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); @@ -8800,7 +8948,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean needRelayout = false; - if (!animating && mAppTransitionRunning) { + if (!mInnerFields.mAnimating && mAppTransitionRunning) { // We have finished the animation of an app transition. To do // this, we have delayed a lot of operations like showing and // hiding apps, moving apps in Z-order, etc. The app token list @@ -8822,33 +8970,32 @@ public class WindowManagerService extends IWindowManager.Stub needRelayout = adjustWallpaperWindowsLocked() != 0; } if (needRelayout) { - requestAnimationLocked(0); - } else if (animating) { - final int refreshTimeUs = (int)(1000 / mDisplay.getRefreshRate()); - requestAnimationLocked(currentTime + refreshTimeUs - SystemClock.uptimeMillis()); + requestTraversalLocked(); + } else if (mInnerFields.mAnimating) { + mChoreographer.scheduleAnimation(); } // Finally update all input windows now that the window changes have stabilized. mInputMonitor.updateInputWindowsLw(true /*force*/); - setHoldScreenLocked(holdScreen != null); + setHoldScreenLocked(mInnerFields.mHoldScreen != null); if (!mDisplayFrozen) { - if (screenBrightness < 0 || screenBrightness > 1.0f) { + if (mInnerFields.mScreenBrightness < 0 || mInnerFields.mScreenBrightness > 1.0f) { mPowerManager.setScreenBrightnessOverride(-1); } else { mPowerManager.setScreenBrightnessOverride((int) - (screenBrightness * Power.BRIGHTNESS_ON)); + (mInnerFields.mScreenBrightness * Power.BRIGHTNESS_ON)); } - if (buttonBrightness < 0 || buttonBrightness > 1.0f) { + if (mInnerFields.mButtonBrightness < 0 || mInnerFields.mButtonBrightness > 1.0f) { mPowerManager.setButtonBrightnessOverride(-1); } else { mPowerManager.setButtonBrightnessOverride((int) - (buttonBrightness * Power.BRIGHTNESS_ON)); + (mInnerFields.mButtonBrightness * Power.BRIGHTNESS_ON)); } } - if (holdScreen != mHoldingScreenOn) { - mHoldingScreenOn = holdScreen; - Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen); + if (mInnerFields.mHoldScreen != mHoldingScreenOn) { + mHoldingScreenOn = mInnerFields.mHoldScreen; + Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, mInnerFields.mHoldScreen); mH.sendMessage(m); } @@ -8858,23 +9005,22 @@ public class WindowManagerService extends IWindowManager.Stub LocalPowerManager.BUTTON_EVENT, true); mTurnOnScreen = false; } - - if (screenRotationFinished && mScreenRotationAnimation != null) { + + if (updateRotation && mScreenRotationAnimation != null) { mScreenRotationAnimation.kill(); mScreenRotationAnimation = null; } if (updateRotation) { if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation"); - boolean changed = updateRotationUncheckedLocked(false); - if (changed) { + if (updateRotationUncheckedLocked(false)) { mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } else { updateRotation = false; } } - if (orientationChangeComplete && !needRelayout && !updateRotation) { + if (mInnerFields.mOrientationChangeComplete && !needRelayout && !updateRotation) { checkDrawnWindowsLocked(); } @@ -8944,10 +9090,17 @@ public class WindowManagerService extends IWindowManager.Stub } } - void requestAnimationLocked(long delay) { - if (!mAnimationPending) { - mAnimationPending = true; - mH.sendMessageDelayed(mH.obtainMessage(H.ANIMATE), delay); + void requestTraversalLocked() { + if (!mTraversalScheduled) { + mTraversalScheduled = true; + mH.sendEmptyMessage(H.DO_TRAVERSAL); + } + } + + @Override + public void onAnimate() { + synchronized(mWindowMap) { + performLayoutAndPlaceSurfacesLocked(); } } @@ -9267,7 +9420,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation"); if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) { - requestAnimationLocked(0); + mChoreographer.scheduleAnimation(); } else { mScreenRotationAnimation = null; updateRotation = true; @@ -9759,9 +9912,10 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mLastWindowForcedOrientation"); pw.print(mLastWindowForcedOrientation); pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation); pw.print(" mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount); - pw.print(" mAnimationPending="); pw.print(mAnimationPending); + pw.print(" mTraversalScheduled="); pw.print(mTraversalScheduled); pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale); + pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale); pw.print(" mNextAppTransition=0x"); pw.print(Integer.toHexString(mNextAppTransition)); pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady); diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 1067cade647c..6868cf6b82ea 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -1593,7 +1593,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); } if (requestAnim) { - mService.requestAnimationLocked(0); + mService.mChoreographer.scheduleAnimation(); } return true; } @@ -1634,7 +1634,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } if (requestAnim) { - mService.requestAnimationLocked(0); + mService.mChoreographer.scheduleAnimation(); } return true; } diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk index 95d651acc224..42e280fa9d9e 100644 --- a/services/surfaceflinger/Android.mk +++ b/services/surfaceflinger/Android.mk @@ -23,18 +23,21 @@ LOCAL_SRC_FILES:= \ LOCAL_CFLAGS:= -DLOG_TAG=\"SurfaceFlinger\" LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES +ifeq ($(TARGET_HAS_WAITFORVSYNC), true) + LOCAL_CFLAGS += -DHAS_WAITFORVSYNC +endif + ifeq ($(TARGET_BOARD_PLATFORM), omap3) LOCAL_CFLAGS += -DNO_RGBX_8888 endif ifeq ($(TARGET_BOARD_PLATFORM), omap4) LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY + LOCAL_CFLAGS += -DUSE_TRIPLE_BUFFERING endif ifeq ($(TARGET_BOARD_PLATFORM), s5pc110) LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY -DNEVER_DEFAULT_TO_ASYNC_MODE - LOCAL_CFLAGS += -DREFRESH_RATE=56 endif - LOCAL_SHARED_LIBRARIES := \ libcutils \ libhardware \ diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp index f4afeeaa0a10..69f1aca3d944 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2012 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. @@ -14,7 +14,6 @@ * limitations under the License. */ -#include <assert.h> #include <errno.h> #include <stdlib.h> #include <stdio.h> @@ -22,15 +21,6 @@ #include <unistd.h> #include <fcntl.h> -#include <signal.h> -#include <termios.h> -#include <sys/ioctl.h> -#include <sys/mman.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/resource.h> - -#include <linux/unistd.h> #include <utils/Log.h> @@ -45,39 +35,22 @@ static char const * const kWakeFileName = "/sys/power/wait_for_fb_wake"; // ---------------------------------------------------------------------------- -DisplayHardwareBase::DisplayEventThreadBase::DisplayEventThreadBase( +DisplayHardwareBase::DisplayEventThread::DisplayEventThread( const sp<SurfaceFlinger>& flinger) : Thread(false), mFlinger(flinger) { } -DisplayHardwareBase::DisplayEventThreadBase::~DisplayEventThreadBase() { +DisplayHardwareBase::DisplayEventThread::~DisplayEventThread() { } -// ---------------------------------------------------------------------------- - -DisplayHardwareBase::DisplayEventThread::DisplayEventThread( - const sp<SurfaceFlinger>& flinger) - : DisplayEventThreadBase(flinger) -{ +status_t DisplayHardwareBase::DisplayEventThread::initCheck() const { + return ((access(kSleepFileName, R_OK) == 0 && + access(kWakeFileName, R_OK) == 0)) ? NO_ERROR : NO_INIT; } -DisplayHardwareBase::DisplayEventThread::~DisplayEventThread() -{ -} +bool DisplayHardwareBase::DisplayEventThread::threadLoop() { -bool DisplayHardwareBase::DisplayEventThread::threadLoop() -{ - int err = 0; - char buf; - int fd; - - fd = open(kSleepFileName, O_RDONLY, 0); - do { - err = read(fd, &buf, 1); - } while (err < 0 && errno == EINTR); - close(fd); - ALOGW_IF(err<0, "ANDROID_WAIT_FOR_FB_SLEEP failed (%s)", strerror(errno)); - if (err >= 0) { + if (waitForFbSleep() == NO_ERROR) { sp<SurfaceFlinger> flinger = mFlinger.promote(); ALOGD("About to give-up screen, flinger = %p", flinger.get()); if (flinger != 0) { @@ -85,39 +58,51 @@ bool DisplayHardwareBase::DisplayEventThread::threadLoop() flinger->screenReleased(0); mBarrier.wait(); } + if (waitForFbWake() == NO_ERROR) { + sp<SurfaceFlinger> flinger = mFlinger.promote(); + ALOGD("Screen about to return, flinger = %p", flinger.get()); + if (flinger != 0) { + flinger->screenAcquired(0); + } + return true; + } } - fd = open(kWakeFileName, O_RDONLY, 0); + + // error, exit the thread + return false; +} + +status_t DisplayHardwareBase::DisplayEventThread::waitForFbSleep() { + int err = 0; + char buf; + int fd = open(kSleepFileName, O_RDONLY, 0); + // if the file doesn't exist, the error will be caught in read() below do { - err = read(fd, &buf, 1); + err = read(fd, &buf, 1); } while (err < 0 && errno == EINTR); close(fd); - ALOGW_IF(err<0, "ANDROID_WAIT_FOR_FB_WAKE failed (%s)", strerror(errno)); - if (err >= 0) { - sp<SurfaceFlinger> flinger = mFlinger.promote(); - ALOGD("Screen about to return, flinger = %p", flinger.get()); - if (flinger != 0) - flinger->screenAcquired(0); - } - return true; + ALOGE_IF(err<0, "*** ANDROID_WAIT_FOR_FB_SLEEP failed (%s)", strerror(errno)); + return err < 0 ? -errno : int(NO_ERROR); } -status_t DisplayHardwareBase::DisplayEventThread::releaseScreen() const -{ - mBarrier.open(); - return NO_ERROR; +status_t DisplayHardwareBase::DisplayEventThread::waitForFbWake() { + int err = 0; + char buf; + int fd = open(kWakeFileName, O_RDONLY, 0); + // if the file doesn't exist, the error will be caught in read() below + do { + err = read(fd, &buf, 1); + } while (err < 0 && errno == EINTR); + close(fd); + ALOGE_IF(err<0, "*** ANDROID_WAIT_FOR_FB_WAKE failed (%s)", strerror(errno)); + return err < 0 ? -errno : int(NO_ERROR); } -status_t DisplayHardwareBase::DisplayEventThread::readyToRun() -{ +status_t DisplayHardwareBase::DisplayEventThread::releaseScreen() const { + mBarrier.open(); return NO_ERROR; } -status_t DisplayHardwareBase::DisplayEventThread::initCheck() const -{ - return ((access(kSleepFileName, R_OK) == 0 && - access(kWakeFileName, R_OK) == 0)) ? NO_ERROR : NO_INIT; -} - // ---------------------------------------------------------------------------- DisplayHardwareBase::DisplayHardwareBase(const sp<SurfaceFlinger>& flinger, @@ -127,35 +112,35 @@ DisplayHardwareBase::DisplayHardwareBase(const sp<SurfaceFlinger>& flinger, mDisplayEventThread = new DisplayEventThread(flinger); } -DisplayHardwareBase::~DisplayHardwareBase() -{ +void DisplayHardwareBase::startSleepManagement() const { + if (mDisplayEventThread->initCheck() == NO_ERROR) { + mDisplayEventThread->run("DisplayEventThread", PRIORITY_URGENT_DISPLAY); + } else { + ALOGW("/sys/power/wait_for_fb_{wake|sleep} don't exist"); + } +} + +DisplayHardwareBase::~DisplayHardwareBase() { // request exit mDisplayEventThread->requestExitAndWait(); } -bool DisplayHardwareBase::canDraw() const -{ +bool DisplayHardwareBase::canDraw() const { return mScreenAcquired; } -void DisplayHardwareBase::releaseScreen() const -{ +void DisplayHardwareBase::releaseScreen() const { status_t err = mDisplayEventThread->releaseScreen(); if (err >= 0) { mScreenAcquired = false; } } -void DisplayHardwareBase::acquireScreen() const -{ - status_t err = mDisplayEventThread->acquireScreen(); - if (err >= 0) { - mScreenAcquired = true; - } +void DisplayHardwareBase::acquireScreen() const { + mScreenAcquired = true; } -bool DisplayHardwareBase::isScreenAcquired() const -{ +bool DisplayHardwareBase::isScreenAcquired() const { return mScreenAcquired; } diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h index ef2df432ce18..fba211beeb7c 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2012 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. @@ -20,8 +20,6 @@ #include <stdint.h> #include <utils/RefBase.h> #include <utils/threads.h> -#include <linux/kd.h> -#include <linux/vt.h> #include "Barrier.h" namespace android { @@ -31,11 +29,13 @@ class SurfaceFlinger; class DisplayHardwareBase { public: - DisplayHardwareBase( - const sp<SurfaceFlinger>& flinger, - uint32_t displayIndex); + DisplayHardwareBase( + const sp<SurfaceFlinger>& flinger, + uint32_t displayIndex); - ~DisplayHardwareBase(); + ~DisplayHardwareBase(); + + void startSleepManagement() const; // console management void releaseScreen() const; @@ -46,34 +46,21 @@ public: private: - class DisplayEventThreadBase : public Thread { - protected: + class DisplayEventThread : public Thread { wp<SurfaceFlinger> mFlinger; - public: - DisplayEventThreadBase(const sp<SurfaceFlinger>& flinger); - virtual ~DisplayEventThreadBase(); - virtual void onFirstRef() { - run("DisplayEventThread", PRIORITY_URGENT_DISPLAY); - } - virtual status_t acquireScreen() const { return NO_ERROR; }; - virtual status_t releaseScreen() const { return NO_ERROR; }; - virtual status_t initCheck() const = 0; - }; - - class DisplayEventThread : public DisplayEventThreadBase - { mutable Barrier mBarrier; + status_t waitForFbSleep(); + status_t waitForFbWake(); public: - DisplayEventThread(const sp<SurfaceFlinger>& flinger); + DisplayEventThread(const sp<SurfaceFlinger>& flinger); virtual ~DisplayEventThread(); virtual bool threadLoop(); - virtual status_t readyToRun(); - virtual status_t releaseScreen() const; - virtual status_t initCheck() const; + status_t releaseScreen() const; + status_t initCheck() const; }; - sp<DisplayEventThreadBase> mDisplayEventThread; - mutable int mScreenAcquired; + sp<DisplayEventThread> mDisplayEventThread; + mutable int mScreenAcquired; }; }; // namespace android diff --git a/services/surfaceflinger/EventThread.cpp b/services/surfaceflinger/EventThread.cpp index 6796d7dee2a7..af0da0bdb22c 100644 --- a/services/surfaceflinger/EventThread.cpp +++ b/services/surfaceflinger/EventThread.cpp @@ -36,6 +36,7 @@ namespace android { EventThread::EventThread(const sp<SurfaceFlinger>& flinger) : mFlinger(flinger), mHw(flinger->graphicPlane(0).displayHardware()), + mLastVSyncTimestamp(0), mDeliveredEvents(0) { } @@ -44,6 +45,20 @@ void EventThread::onFirstRef() { run("EventThread", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE); } +sp<DisplayEventConnection> EventThread::createEventConnection() const { + return new DisplayEventConnection(const_cast<EventThread*>(this)); +} + +nsecs_t EventThread::getLastVSyncTimestamp() const { + Mutex::Autolock _l(mLock); + return mLastVSyncTimestamp; +} + +nsecs_t EventThread::getVSyncPeriod() const { + return mHw.getRefreshPeriod(); + +} + status_t EventThread::registerDisplayEventConnection( const sp<DisplayEventConnection>& connection) { Mutex::Autolock _l(mLock); @@ -80,8 +95,11 @@ void EventThread::setVsyncRate(uint32_t count, Mutex::Autolock _l(mLock); ConnectionInfo* info = getConnectionInfoLocked(connection); if (info) { - info->count = (count == 0) ? -1 : count; - mCondition.signal(); + const int32_t new_count = (count == 0) ? -1 : count; + if (info->count != new_count) { + info->count = new_count; + mCondition.signal(); + } } } } @@ -90,10 +108,8 @@ void EventThread::requestNextVsync( const wp<DisplayEventConnection>& connection) { Mutex::Autolock _l(mLock); ConnectionInfo* info = getConnectionInfoLocked(connection); - if (info) { - if (info->count < 0) { - info->count = 0; - } + if (info && info->count < 0) { + info->count = 0; mCondition.signal(); } } @@ -132,11 +148,12 @@ bool EventThread::threadLoop() { timestamp = mHw.waitForRefresh(); mLock.lock(); mDeliveredEvents++; + mLastVSyncTimestamp = timestamp; // now see if we still need to report this VSYNC event - bool reportVsync = false; - size_t count = mDisplayEventConnections.size(); + const size_t count = mDisplayEventConnections.size(); for (size_t i=0 ; i<count ; i++) { + bool reportVsync = false; const ConnectionInfo& info( mDisplayEventConnections.valueAt(i)); if (info.count >= 1) { @@ -157,11 +174,7 @@ bool EventThread::threadLoop() { displayEventConnections.add(mDisplayEventConnections.keyAt(i)); } } - - if (reportVsync) { - break; - } - } while (true); + } while (!displayEventConnections.size()); // dispatch vsync events to listeners... vsync.header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC; diff --git a/services/surfaceflinger/EventThread.h b/services/surfaceflinger/EventThread.h index 35bd299a8675..3a3071ef8bea 100644 --- a/services/surfaceflinger/EventThread.h +++ b/services/surfaceflinger/EventThread.h @@ -36,6 +36,7 @@ namespace android { class SurfaceFlinger; class DisplayHardware; +class DisplayEventConnection; // --------------------------------------------------------------------------- @@ -45,6 +46,8 @@ class EventThread : public Thread { public: EventThread(const sp<SurfaceFlinger>& flinger); + sp<DisplayEventConnection> createEventConnection() const; + status_t registerDisplayEventConnection( const sp<DisplayEventConnection>& connection); @@ -56,6 +59,10 @@ public: void requestNextVsync(const wp<DisplayEventConnection>& connection); + nsecs_t getLastVSyncTimestamp() const; + + nsecs_t getVSyncPeriod() const; + void dump(String8& result, char* buffer, size_t SIZE) const; private: @@ -88,6 +95,7 @@ private: // protected by mLock KeyedVector< wp<DisplayEventConnection>, ConnectionInfo > mDisplayEventConnections; + nsecs_t mLastVSyncTimestamp; // main thread only size_t mDeliveredEvents; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 8e87b88eeaf8..3e6b8725248d 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -55,6 +55,7 @@ Layer::Layer(SurfaceFlinger* flinger, mCurrentTransform(0), mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), mCurrentOpacity(true), + mRefreshPending(0), mFrameLatencyNeeded(false), mFrameLatencyOffset(0), mFormat(PIXEL_FORMAT_NONE), @@ -97,7 +98,12 @@ void Layer::onFirstRef() mSurfaceTexture = new SurfaceTextureLayer(mTextureName, this); mSurfaceTexture->setFrameAvailableListener(new FrameQueuedListener(this)); mSurfaceTexture->setSynchronousMode(true); +#ifdef USE_TRIPLE_BUFFERING +#warning "using triple buffering" + mSurfaceTexture->setBufferCountServer(3); +#else mSurfaceTexture->setBufferCountServer(2); +#endif } Layer::~Layer() @@ -108,7 +114,7 @@ Layer::~Layer() void Layer::onFrameQueued() { android_atomic_inc(&mQueuedFrames); - mFlinger->signalEvent(); + mFlinger->signalLayerUpdate(); } // called with SurfaceFlinger::mStateLock as soon as the layer is entered @@ -402,16 +408,37 @@ bool Layer::isCropped() const { // pageflip handling... // ---------------------------------------------------------------------------- +bool Layer::onPreComposition() +{ + // if there was more than one pending update, request a refresh + if (mRefreshPending >= 2) { + mRefreshPending = 0; + return true; + } + mRefreshPending = 0; + return false; +} + void Layer::lockPageFlip(bool& recomputeVisibleRegions) { if (mQueuedFrames > 0) { + + // if we've already called updateTexImage() without going through + // a composition step, we have to skip this layer at this point + // because we cannot call updateTeximage() without a corresponding + // compositionComplete() call. + // we'll trigger an update in onPreComposition(). + if (mRefreshPending++) { + return; + } + // Capture the old state of the layer for comparisons later const bool oldOpacity = isOpaque(); sp<GraphicBuffer> oldActiveBuffer = mActiveBuffer; // signal another event if we have more frames pending if (android_atomic_dec(&mQueuedFrames) > 1) { - mFlinger->signalEvent(); + mFlinger->signalLayerUpdate(); } if (mSurfaceTexture->updateTexImage() < NO_ERROR) { @@ -514,6 +541,10 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions) void Layer::unlockPageFlip( const Transform& planeTransform, Region& outDirtyRegion) { + if (mRefreshPending >= 2) { + return; + } + Region dirtyRegion(mPostedDirtyRegion); if (!dirtyRegion.isEmpty()) { mPostedDirtyRegion.clear(); @@ -547,9 +578,9 @@ void Layer::dump(String8& result, char* buffer, size_t SIZE) const snprintf(buffer, SIZE, " " "format=%2d, activeBuffer=[%4ux%4u:%4u,%3X]," - " transform-hint=0x%02x, queued-frames=%d\n", + " transform-hint=0x%02x, queued-frames=%d, mRefreshPending=%d\n", mFormat, w0, h0, s0,f0, - getTransformHint(), mQueuedFrames); + getTransformHint(), mQueuedFrames, mRefreshPending); result.append(buffer); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2dd4da130dcf..bf30608c9713 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -80,6 +80,7 @@ public: virtual wp<IBinder> getSurfaceTextureBinder() const; virtual void onLayerDisplayed(); + virtual bool onPreComposition(); // only for debugging inline const sp<GraphicBuffer>& getActiveBuffer() const { return mActiveBuffer; } @@ -115,14 +116,17 @@ private: uint32_t mCurrentTransform; uint32_t mCurrentScalingMode; bool mCurrentOpacity; + size_t mRefreshPending; bool mFrameLatencyNeeded; int mFrameLatencyOffset; + struct Statistics { Statistics() : timestamp(0), set(0), vsync(0) { } nsecs_t timestamp; // buffer timestamp nsecs_t set; // buffer displayed timestamp nsecs_t vsync; // vsync immediately before set }; + // protected by mLock Statistics mFrameStats[128]; diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp index d32fcdd62528..e76400120678 100644 --- a/services/surfaceflinger/LayerBase.cpp +++ b/services/surfaceflinger/LayerBase.cpp @@ -47,8 +47,7 @@ LayerBase::LayerBase(SurfaceFlinger* flinger, DisplayID display) mOrientation(0), mPlaneOrientation(0), mTransactionFlags(0), - mPremultipliedAlpha(true), mName("unnamed"), mDebug(false), - mInvalidate(0) + mPremultipliedAlpha(true), mName("unnamed"), mDebug(false) { const DisplayHardware& hw(flinger->graphicPlane(0).displayHardware()); mFlags = hw.getFlags(); @@ -262,23 +261,11 @@ void LayerBase::validateVisibility(const Transform& planeTransform) mTransformedBounds = tr.makeBounds(w, h); } -void LayerBase::lockPageFlip(bool& recomputeVisibleRegions) -{ +void LayerBase::lockPageFlip(bool& recomputeVisibleRegions) { } void LayerBase::unlockPageFlip( - const Transform& planeTransform, Region& outDirtyRegion) -{ - if ((android_atomic_and(~1, &mInvalidate)&1) == 1) { - outDirtyRegion.orSelf(visibleRegionScreen); - } -} - -void LayerBase::invalidate() -{ - if ((android_atomic_or(1, &mInvalidate)&1) == 0) { - mFlinger->signalEvent(); - } + const Transform& planeTransform, Region& outDirtyRegion) { } void LayerBase::drawRegion(const Region& reg) const @@ -471,16 +458,21 @@ void LayerBase::drawWithOpenGL(const Region& clip) const void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const { const Layer::State& s(drawingState()); + + snprintf(buffer, SIZE, + "+ %s %p (%s)\n", + getTypeId(), this, getName().string()); + result.append(buffer); + s.transparentRegion.dump(result, "transparentRegion"); transparentRegionScreen.dump(result, "transparentRegionScreen"); visibleRegionScreen.dump(result, "visibleRegionScreen"); + snprintf(buffer, SIZE, - "+ %s %p (%s)\n" " " "z=%9d, pos=(%g,%g), size=(%4d,%4d), " "isOpaque=%1d, needsDithering=%1d, invalidate=%1d, " "alpha=0x%02x, flags=0x%08x, tr=[%.2f, %.2f][%.2f, %.2f]\n", - getTypeId(), this, getName().string(), s.z, s.transform.tx(), s.transform.ty(), s.w, s.h, isOpaque(), needsDithering(), contentDirty, s.alpha, s.flags, diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h index 6b62c9db7836..b8f7680b2b25 100644 --- a/services/surfaceflinger/LayerBase.h +++ b/services/surfaceflinger/LayerBase.h @@ -103,8 +103,6 @@ public: Rect visibleBounds() const; void drawRegion(const Region& reg) const; - void invalidate(); - virtual sp<LayerBaseClient> getLayerBaseClient() const { return 0; } virtual sp<Layer> getLayer() const { return 0; } @@ -204,9 +202,16 @@ public: /** called with the state lock when the surface is removed from the * current list */ - virtual void onRemoved() { }; + virtual void onRemoved() { } - virtual void onLayerDisplayed() { }; + /** called after page-flip + */ + virtual void onLayerDisplayed() { } + + /** called before composition. + * returns true if the layer has pending updates. + */ + virtual bool onPreComposition() { return false; } /** always call base class first */ virtual void dump(String8& result, char* scratch, size_t size) const; @@ -275,10 +280,6 @@ protected: mutable bool mDebug; - // atomic - volatile int32_t mInvalidate; - - public: // called from class SurfaceFlinger virtual ~LayerBase(); diff --git a/services/surfaceflinger/MessageQueue.cpp b/services/surfaceflinger/MessageQueue.cpp index cbd530c822a5..290fff469d8c 100644 --- a/services/surfaceflinger/MessageQueue.cpp +++ b/services/surfaceflinger/MessageQueue.cpp @@ -18,12 +18,18 @@ #include <errno.h> #include <sys/types.h> +#include <binder/IPCThreadState.h> + #include <utils/threads.h> #include <utils/Timers.h> #include <utils/Log.h> -#include <binder/IPCThreadState.h> + +#include <gui/IDisplayEventConnection.h> +#include <gui/BitTube.h> #include "MessageQueue.h" +#include "EventThread.h" +#include "SurfaceFlinger.h" namespace android { @@ -43,36 +49,69 @@ void MessageBase::handleMessage(const Message&) { // --------------------------------------------------------------------------- +void MessageQueue::Handler::signalRefresh() { + if ((android_atomic_or(eventMaskRefresh, &mEventMask) & eventMaskRefresh) == 0) { + mQueue.mLooper->sendMessage(this, Message(MessageQueue::REFRESH)); + } +} + +void MessageQueue::Handler::signalInvalidate() { + if ((android_atomic_or(eventMaskInvalidate, &mEventMask) & eventMaskInvalidate) == 0) { + mQueue.mLooper->sendMessage(this, Message(MessageQueue::INVALIDATE)); + } +} + +void MessageQueue::Handler::handleMessage(const Message& message) { + switch (message.what) { + case INVALIDATE: + android_atomic_and(~eventMaskInvalidate, &mEventMask); + mQueue.mFlinger->onMessageReceived(message.what); + break; + case REFRESH: + android_atomic_and(~eventMaskRefresh, &mEventMask); + mQueue.mFlinger->onMessageReceived(message.what); + break; + } +} + +// --------------------------------------------------------------------------- + MessageQueue::MessageQueue() - : mLooper(new Looper(true)), mWorkPending(0) { } MessageQueue::~MessageQueue() { } +void MessageQueue::init(const sp<SurfaceFlinger>& flinger) +{ + mFlinger = flinger; + mLooper = new Looper(true); + mHandler = new Handler(*this); +} + +void MessageQueue::setEventThread(const sp<EventThread>& eventThread) +{ + mEventThread = eventThread; + mEvents = eventThread->createEventConnection(); + mEventTube = mEvents->getDataChannel(); + mLooper->addFd(mEventTube->getFd(), 0, ALOOPER_EVENT_INPUT, + MessageQueue::cb_eventReceiver, this); +} + void MessageQueue::waitMessage() { do { IPCThreadState::self()->flushCommands(); - int32_t ret = mLooper->pollOnce(-1); switch (ret) { case ALOOPER_POLL_WAKE: case ALOOPER_POLL_CALLBACK: - // callback and/or wake - if (android_atomic_and(0, &mWorkPending)) { - return; - } - continue; - - case ALOOPER_POLL_TIMEOUT: - // timeout (should not happen) continue; - case ALOOPER_POLL_ERROR: ALOGE("ALOOPER_POLL_ERROR"); + case ALOOPER_POLL_TIMEOUT: + // timeout (should not happen) continue; - default: // should not happen ALOGE("Looper::pollOnce() returned unknown status %d", ret); @@ -93,11 +132,32 @@ status_t MessageQueue::postMessage( return NO_ERROR; } -status_t MessageQueue::invalidate() { - if (android_atomic_or(1, &mWorkPending) == 0) { - mLooper->wake(); +void MessageQueue::invalidate() { +// mHandler->signalInvalidate(); + mEvents->requestNextVsync(); +} + +void MessageQueue::refresh() { + mEvents->requestNextVsync(); +} + +int MessageQueue::cb_eventReceiver(int fd, int events, void* data) { + MessageQueue* queue = reinterpret_cast<MessageQueue *>(data); + return queue->eventReceiver(fd, events); +} + +int MessageQueue::eventReceiver(int fd, int events) { + ssize_t n; + DisplayEventReceiver::Event buffer[8]; + while ((n = DisplayEventReceiver::getEvents(mEventTube, buffer, 8)) > 0) { + for (int i=0 ; i<n ; i++) { + if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { + mHandler->signalRefresh(); + break; + } + } } - return NO_ERROR; + return 1; } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/MessageQueue.h b/services/surfaceflinger/MessageQueue.h index 2317d81ab8e9..ea29e7ef729c 100644 --- a/services/surfaceflinger/MessageQueue.h +++ b/services/surfaceflinger/MessageQueue.h @@ -25,10 +25,16 @@ #include <utils/Timers.h> #include <utils/Looper.h> +#include <gui/DisplayEventReceiver.h> + #include "Barrier.h" namespace android { +class IDisplayEventConnection; +class EventThread; +class SurfaceFlinger; + // --------------------------------------------------------------------------- class MessageBase : public MessageHandler @@ -54,16 +60,48 @@ private: // --------------------------------------------------------------------------- class MessageQueue { + class Handler : public MessageHandler { + enum { + eventMaskInvalidate = 0x1, + eventMaskRefresh = 0x2 + }; + MessageQueue& mQueue; + int32_t mEventMask; + public: + Handler(MessageQueue& queue) : mQueue(queue), mEventMask(0) { } + virtual void handleMessage(const Message& message); + void signalRefresh(); + void signalInvalidate(); + }; + + friend class Handler; + + sp<SurfaceFlinger> mFlinger; sp<Looper> mLooper; - volatile int32_t mWorkPending; + sp<EventThread> mEventThread; + sp<IDisplayEventConnection> mEvents; + sp<BitTube> mEventTube; + sp<Handler> mHandler; + + + static int cb_eventReceiver(int fd, int events, void* data); + int eventReceiver(int fd, int events); public: + enum { + INVALIDATE = 0, + REFRESH = 1, + }; + MessageQueue(); ~MessageQueue(); + void init(const sp<SurfaceFlinger>& flinger); + void setEventThread(const sp<EventThread>& events); void waitMessage(); status_t postMessage(const sp<MessageBase>& message, nsecs_t reltime=0); - status_t invalidate(); + void invalidate(); + void refresh(); }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 887aee7bf440..40717f4e95db 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -125,11 +125,34 @@ void SurfaceFlinger::init() ALOGI_IF(mDebugDDMS, "DDMS debugging enabled"); } +void SurfaceFlinger::onFirstRef() +{ + mEventQueue.init(this); + + run("SurfaceFlinger", PRIORITY_URGENT_DISPLAY); + + // Wait for the main thread to be done with its initialization + mReadyToRunBarrier.wait(); +} + + SurfaceFlinger::~SurfaceFlinger() { glDeleteTextures(1, &mWormholeTexName); } +void SurfaceFlinger::binderDied(const wp<IBinder>& who) +{ + // the window manager died on us. prepare its eulogy. + + // reset screen orientation + Vector<ComposerState> state; + setTransactionState(state, eOrientationDefault, 0); + + // restart the boot-animation + property_set("ctl.start", "bootanim"); +} + sp<IMemoryHeap> SurfaceFlinger::getCblk() const { return mServerHeap; @@ -183,26 +206,6 @@ void SurfaceFlinger::bootFinished() property_set("ctl.stop", "bootanim"); } -void SurfaceFlinger::binderDied(const wp<IBinder>& who) -{ - // the window manager died on us. prepare its eulogy. - - // reset screen orientation - Vector<ComposerState> state; - setTransactionState(state, eOrientationDefault, 0); - - // restart the boot-animation - property_set("ctl.start", "bootanim"); -} - -void SurfaceFlinger::onFirstRef() -{ - run("SurfaceFlinger", PRIORITY_URGENT_DISPLAY); - - // Wait for the main thread to be done with its initialization - mReadyToRunBarrier.wait(); -} - static inline uint16_t pack565(int r, int g, int b) { return (r<<11)|(g<<5)|b; } @@ -296,6 +299,8 @@ status_t SurfaceFlinger::readyToRun() // start the EventThread mEventThread = new EventThread(this); + mEventQueue.setEventThread(mEventThread); + hw.startSleepManagement(); /* * We're now ready to accept clients... @@ -310,34 +315,6 @@ status_t SurfaceFlinger::readyToRun() } // ---------------------------------------------------------------------------- -#if 0 -#pragma mark - -#pragma mark Events Handler -#endif - -void SurfaceFlinger::waitForEvent() { - mEventQueue.waitMessage(); -} - -void SurfaceFlinger::signalEvent() { - mEventQueue.invalidate(); -} - -status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg, - nsecs_t reltime, uint32_t flags) { - return mEventQueue.postMessage(msg, reltime); -} - -status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg, - nsecs_t reltime, uint32_t flags) { - status_t res = mEventQueue.postMessage(msg, reltime); - if (res == NO_ERROR) { - msg->wait(); - } - return res; -} - -// ---------------------------------------------------------------------------- bool SurfaceFlinger::authenticateSurfaceTexture( const sp<ISurfaceTexture>& surfaceTexture) const { @@ -383,65 +360,108 @@ bool SurfaceFlinger::authenticateSurfaceTexture( // ---------------------------------------------------------------------------- sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection() { - sp<DisplayEventConnection> result(new DisplayEventConnection(mEventThread)); - return result; + return mEventThread->createEventConnection(); } // ---------------------------------------------------------------------------- -#if 0 -#pragma mark - -#pragma mark Main loop -#endif + +void SurfaceFlinger::waitForEvent() { + mEventQueue.waitMessage(); +} + +void SurfaceFlinger::signalTransaction() { + mEventQueue.invalidate(); +} + +void SurfaceFlinger::signalLayerUpdate() { + mEventQueue.invalidate(); +} + +void SurfaceFlinger::signalRefresh() { + mEventQueue.refresh(); +} + +status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg, + nsecs_t reltime, uint32_t flags) { + return mEventQueue.postMessage(msg, reltime); +} + +status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg, + nsecs_t reltime, uint32_t flags) { + status_t res = mEventQueue.postMessage(msg, reltime); + if (res == NO_ERROR) { + msg->wait(); + } + return res; +} bool SurfaceFlinger::threadLoop() { waitForEvent(); + return true; +} - // check for transactions - if (CC_UNLIKELY(mConsoleSignals)) { - handleConsoleEvents(); - } +void SurfaceFlinger::onMessageReceived(int32_t what) +{ + switch (what) { + case MessageQueue::REFRESH: { +// case MessageQueue::INVALIDATE: { + // check for transactions + if (CC_UNLIKELY(mConsoleSignals)) { + handleConsoleEvents(); + } - // if we're in a global transaction, don't do anything. - const uint32_t mask = eTransactionNeeded | eTraversalNeeded; - uint32_t transactionFlags = peekTransactionFlags(mask); - if (CC_UNLIKELY(transactionFlags)) { - handleTransaction(transactionFlags); - } + // if we're in a global transaction, don't do anything. + const uint32_t mask = eTransactionNeeded | eTraversalNeeded; + uint32_t transactionFlags = peekTransactionFlags(mask); + if (CC_UNLIKELY(transactionFlags)) { + handleTransaction(transactionFlags); + } - // post surfaces (if needed) - handlePageFlip(); + // post surfaces (if needed) + handlePageFlip(); - if (mDirtyRegion.isEmpty()) { - // nothing new to do. - return true; - } +// signalRefresh(); +// +// } break; +// +// case MessageQueue::REFRESH: { - if (CC_UNLIKELY(mHwWorkListDirty)) { - // build the h/w work list - handleWorkList(); - } + handleRefresh(); - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - if (CC_LIKELY(hw.canDraw())) { - // repaint the framebuffer (if needed) - handleRepaint(); - // inform the h/w that we're done compositing - hw.compositionComplete(); - postFramebuffer(); - } else { - // pretend we did the post - hw.compositionComplete(); - hw.waitForRefresh(); + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + +// if (mDirtyRegion.isEmpty()) { +// return; +// } + + if (CC_UNLIKELY(mHwWorkListDirty)) { + // build the h/w work list + handleWorkList(); + } + + if (CC_LIKELY(hw.canDraw())) { + // repaint the framebuffer (if needed) + handleRepaint(); + // inform the h/w that we're done compositing + hw.compositionComplete(); + postFramebuffer(); + } else { + // pretend we did the post + hw.compositionComplete(); + } + + } break; } - return true; } void SurfaceFlinger::postFramebuffer() { - // this should never happen. we do the flip anyways so we don't - // risk to cause a deadlock with hwc - ALOGW_IF(mSwapRegion.isEmpty(), "mSwapRegion is empty"); + // mSwapRegion can be empty here is some cases, for instance if a hidden + // or fully transparent window is updating. + // in that case, we need to flip anyways to not risk a deadlock with + // h/w composer. + const DisplayHardware& hw(graphicPlane(0).displayHardware()); const nsecs_t now = systemTime(); mDebugInSwapBuffers = now; @@ -718,13 +738,13 @@ void SurfaceFlinger::commitTransaction() void SurfaceFlinger::handlePageFlip() { - bool visibleRegions = mVisibleRegionsDirty; + const DisplayHardware& hw = graphicPlane(0).displayHardware(); + const Region screenRegion(hw.bounds()); + const LayerVector& currentLayers(mDrawingState.layersSortedByZ); - visibleRegions |= lockPageFlip(currentLayers); + const bool visibleRegions = lockPageFlip(currentLayers); - const DisplayHardware& hw = graphicPlane(0).displayHardware(); - const Region screenRegion(hw.bounds()); - if (visibleRegions) { + if (visibleRegions || mVisibleRegionsDirty) { Region opaqueRegion; computeVisibleRegions(currentLayers, mDirtyRegion, opaqueRegion); @@ -771,7 +791,7 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers) { const GraphicPlane& plane(graphicPlane(0)); const Transform& planeTransform(plane.transform()); - size_t count = currentLayers.size(); + const size_t count = currentLayers.size(); sp<LayerBase> const* layers = currentLayers.array(); for (size_t i=0 ; i<count ; i++) { const sp<LayerBase>& layer(layers[i]); @@ -779,6 +799,23 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers) } } +void SurfaceFlinger::handleRefresh() +{ + bool needInvalidate = false; + const LayerVector& currentLayers(mDrawingState.layersSortedByZ); + const size_t count = currentLayers.size(); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + if (layer->onPreComposition()) { + needInvalidate = true; + } + } + if (needInvalidate) { + signalLayerUpdate(); + } +} + + void SurfaceFlinger::handleWorkList() { mHwWorkListDirty = false; @@ -1176,7 +1213,7 @@ uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags) { uint32_t old = android_atomic_or(flags, &mTransactionFlags); if ((old & flags)==0) { // wake the server up - signalEvent(); + signalTransaction(); } return old; } @@ -1427,14 +1464,14 @@ void SurfaceFlinger::screenReleased(int dpy) { // this may be called by a signal handler, we can't do too much in here android_atomic_or(eConsoleReleased, &mConsoleSignals); - signalEvent(); + signalTransaction(); } void SurfaceFlinger::screenAcquired(int dpy) { // this may be called by a signal handler, we can't do too much in here android_atomic_or(eConsoleAcquired, &mConsoleSignals); - signalEvent(); + signalTransaction(); } status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) @@ -1621,11 +1658,13 @@ void SurfaceFlinger::dumpAllLocked( snprintf(buffer, SIZE, " last eglSwapBuffers() time: %f us\n" " last transaction time : %f us\n" + " transaction-flags : %08x\n" " refresh-rate : %f fps\n" " x-dpi : %f\n" " y-dpi : %f\n", mLastSwapBufferTime/1000.0, mLastTransactionTime/1000.0, + mTransactionFlags, hw.getRefreshRate(), hw.getDpiX(), hw.getDpiY()); @@ -1754,6 +1793,7 @@ status_t SurfaceFlinger::onTransact( reply->writeInt32(0); reply->writeInt32(mDebugRegion); reply->writeInt32(mDebugBackground); + reply->writeInt32(mDebugDisableHWC); return NO_ERROR; case 1013: { Mutex::Autolock _l(mStateLock); @@ -1770,7 +1810,7 @@ void SurfaceFlinger::repaintEverything() { const DisplayHardware& hw(graphicPlane(0).displayHardware()); const Rect bounds(hw.getBounds()); setInvalidateRegion(Region(bounds)); - signalEvent(); + signalTransaction(); } void SurfaceFlinger::setInvalidateRegion(const Region& reg) { @@ -2246,7 +2286,7 @@ status_t SurfaceFlinger::turnElectronBeamOnImplLocked(int32_t mode) // make sure to redraw the whole screen when the animation is done mDirtyRegion.set(hw.bounds()); - signalEvent(); + signalTransaction(); return NO_ERROR; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index c24a9de45a59..fcd93615ff1a 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -190,6 +190,8 @@ public: status_t renderScreenToTextureLocked(DisplayID dpy, GLuint* textureName, GLfloat* uOut, GLfloat* vOut); + void onMessageReceived(int32_t what); + status_t postMessageAsync(const sp<MessageBase>& msg, nsecs_t reltime=0, uint32_t flags = 0); @@ -283,7 +285,10 @@ private: public: // hack to work around gcc 4.0.3 bug const GraphicPlane& graphicPlane(int dpy) const; GraphicPlane& graphicPlane(int dpy); - void signalEvent(); + + void signalTransaction(); + void signalLayerUpdate(); + void signalRefresh(); void repaintEverything(); private: @@ -300,6 +305,7 @@ private: void handlePageFlip(); bool lockPageFlip(const LayerVector& currentLayers); void unlockPageFlip(const LayerVector& currentLayers); + void handleRefresh(); void handleWorkList(); void handleRepaint(); void postFramebuffer(); diff --git a/services/surfaceflinger/SurfaceTextureLayer.cpp b/services/surfaceflinger/SurfaceTextureLayer.cpp index 259b93766a92..49e8e6393505 100644 --- a/services/surfaceflinger/SurfaceTextureLayer.cpp +++ b/services/surfaceflinger/SurfaceTextureLayer.cpp @@ -94,6 +94,10 @@ status_t SurfaceTextureLayer::connect(int api, *outTransform = orientation; } switch(api) { + case NATIVE_WINDOW_API_CPU: + // SurfaceTextureClient supports only 2 buffers for CPU connections + this->setBufferCountServer(2); + break; case NATIVE_WINDOW_API_MEDIA: case NATIVE_WINDOW_API_CAMERA: // Camera preview and videos are rate-limited on the producer diff --git a/services/surfaceflinger/tests/vsync/vsync.cpp b/services/surfaceflinger/tests/vsync/vsync.cpp index 4f790802535c..b0d54c4e0b17 100644 --- a/services/surfaceflinger/tests/vsync/vsync.cpp +++ b/services/surfaceflinger/tests/vsync/vsync.cpp @@ -55,6 +55,8 @@ int main(int argc, char** argv) loop->addFd(myDisplayEvent.getFd(), 0, ALOOPER_EVENT_INPUT, receiver, &myDisplayEvent); + myDisplayEvent.setVsyncRate(1); + do { //printf("about to poll...\n"); int32_t ret = loop->pollOnce(-1); diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 36a25677a8d2..e863f8b7dee3 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -21,7 +21,6 @@ import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkPolicy.LIMIT_DISABLED; -import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; @@ -29,6 +28,8 @@ import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.computeNextCycleBoundary; +import static android.net.TrafficStats.KB_IN_BYTES; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT; @@ -101,10 +102,6 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { private static final long TEST_START = 1194220800000L; private static final String TEST_IFACE = "test0"; - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; - private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(); private BroadcastInterceptingContext mServiceContext; @@ -256,41 +253,49 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { } public void testPidForegroundCombined() throws Exception { + IdleFuture idle; + // push all uid into background + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false); mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false); - waitUntilIdle(); + idle.get(); assertFalse(mService.isUidForeground(UID_A)); assertFalse(mService.isUidForeground(UID_B)); // push one of the shared pids into foreground + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true); - waitUntilIdle(); + idle.get(); assertTrue(mService.isUidForeground(UID_A)); assertFalse(mService.isUidForeground(UID_B)); // and swap another uid into foreground + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false); mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true); - waitUntilIdle(); + idle.get(); assertFalse(mService.isUidForeground(UID_A)); assertTrue(mService.isUidForeground(UID_B)); // push both pid into foreground + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true); - waitUntilIdle(); + idle.get(); assertTrue(mService.isUidForeground(UID_A)); // pull one out, should still be foreground + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false); - waitUntilIdle(); + idle.get(); assertTrue(mService.isUidForeground(UID_A)); // pull final pid out, should now be background + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false); - waitUntilIdle(); + idle.get(); assertFalse(mService.isUidForeground(UID_A)); } @@ -434,7 +439,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final long expectedCycle = parseTime("2007-11-05T00:00:00.000Z"); final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 5, 1024L, 1024L, SNOOZE_NEVER, false); + sTemplateWifi, 5, 1024L, 1024L, false); final long actualCycle = computeLastCycleBoundary(currentTime, policy); assertTimeEquals(expectedCycle, actualCycle); } @@ -445,7 +450,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final long expectedCycle = parseTime("2007-10-20T00:00:00.000Z"); final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 20, 1024L, 1024L, SNOOZE_NEVER, false); + sTemplateWifi, 20, 1024L, 1024L, false); final long actualCycle = computeLastCycleBoundary(currentTime, policy); assertTimeEquals(expectedCycle, actualCycle); } @@ -456,7 +461,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final long expectedCycle = parseTime("2007-01-30T00:00:00.000Z"); final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 30, 1024L, 1024L, SNOOZE_NEVER, false); + sTemplateWifi, 30, 1024L, 1024L, false); final long actualCycle = computeLastCycleBoundary(currentTime, policy); assertTimeEquals(expectedCycle, actualCycle); } @@ -467,14 +472,14 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final long expectedCycle = parseTime("2007-02-28T23:59:59.000Z"); final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 30, 1024L, 1024L, SNOOZE_NEVER, false); + sTemplateWifi, 30, 1024L, 1024L, false); final long actualCycle = computeLastCycleBoundary(currentTime, policy); assertTimeEquals(expectedCycle, actualCycle); } public void testNextCycleSane() throws Exception { final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 31, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER, false); + sTemplateWifi, 31, WARNING_DISABLED, LIMIT_DISABLED, false); final LinkedHashSet<Long> seen = new LinkedHashSet<Long>(); // walk forwards, ensuring that cycle boundaries don't get stuck @@ -489,7 +494,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { public void testLastCycleSane() throws Exception { final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 31, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER, false); + sTemplateWifi, 31, WARNING_DISABLED, LIMIT_DISABLED, false); final LinkedHashSet<Long> seen = new LinkedHashSet<Long>(); // walk backwards, ensuring that cycle boundaries look sane @@ -547,7 +552,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { replay(); setNetworkPolicies(new NetworkPolicy( - sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, SNOOZE_NEVER, false)); + sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false)); future.get(); verifyAndReset(); } @@ -604,9 +609,8 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { future = expectMeteredIfacesChanged(); replay(); - setNetworkPolicies( - new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, - SNOOZE_NEVER, false)); + setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, + 2 * MB_IN_BYTES, false)); future.get(); verifyAndReset(); } @@ -698,7 +702,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { tagFuture = expectEnqueueNotification(); replay(); - mService.snoozePolicy(sTemplateWifi); + mService.snoozeLimit(sTemplateWifi); assertNotificationType(TYPE_LIMIT_SNOOZED, tagFuture.get()); future.get(); verifyAndReset(); @@ -736,9 +740,8 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { future = expectMeteredIfacesChanged(TEST_IFACE); replay(); - setNetworkPolicies( - new NetworkPolicy(sTemplateWifi, CYCLE_DAY, WARNING_DISABLED, LIMIT_DISABLED, - SNOOZE_NEVER, true)); + setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, WARNING_DISABLED, + LIMIT_DISABLED, true)); future.get(); verifyAndReset(); } @@ -890,10 +893,10 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { /** * Wait until {@link #mService} internal {@link Handler} is idle. */ - private void waitUntilIdle() throws Exception { + private IdleFuture expectIdle() { final IdleFuture future = new IdleFuture(); mService.addIdleHandler(future); - future.get(); + return future; } private static void assertTimeEquals(long expected, long actual) { diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java index 8f5e77e3c3fe..daf201833e05 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java @@ -31,6 +31,7 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UID_REMOVED; import static android.net.TrafficStats.UID_TETHERING; import static android.text.format.DateUtils.DAY_IN_MILLIS; @@ -92,10 +93,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { private static final String IMSI_1 = "310004"; private static final String IMSI_2 = "310260"; - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES; - private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES; - private static NetworkTemplate sTemplateWifi = buildTemplateWifi(); private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1); private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2); diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index 3128592ca004..104966932ebe 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -47,6 +47,9 @@ public class SignalStrength implements Parcelable { "none", "poor", "moderate", "good", "great" }; + /** @hide */ + public static final int INVALID_SNR = 0x7FFFFFFF; + private int mGsmSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 private int mGsmBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 private int mCdmaDbm; // This value is the RSSI value @@ -96,7 +99,7 @@ public class SignalStrength implements Parcelable { mLteSignalStrength = -1; mLteRsrp = -1; mLteRsrq = -1; - mLteRssnr = -1; + mLteRssnr = INVALID_SNR; mLteCqi = -1; isGsm = true; } @@ -136,7 +139,8 @@ public class SignalStrength implements Parcelable { int evdoDbm, int evdoEcio, int evdoSnr, boolean gsm) { this(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, - evdoDbm, evdoEcio, evdoSnr, -1, -1, -1, -1, -1, gsm); + evdoDbm, evdoEcio, evdoSnr, -1, -1, + -1, INVALID_SNR, -1, gsm); } /** @@ -289,10 +293,11 @@ public class SignalStrength implements Parcelable { int level; if (isGsm) { + // TODO Need solve the discrepancy of invalid values between + // RIL_LTE_SignalStrength and here. if ((mLteSignalStrength == -1) && (mLteRsrp == -1) && (mLteRsrq == -1) - && (mLteRssnr == -1) && (mLteCqi == -1)) { level = getGsmLevel(); } else { @@ -327,7 +332,6 @@ public class SignalStrength implements Parcelable { if ((mLteSignalStrength == -1) && (mLteRsrp == -1) && (mLteRsrq == -1) - && (mLteRssnr == -1) && (mLteCqi == -1)) { asuLevel = getGsmAsuLevel(); } else { @@ -363,7 +367,6 @@ public class SignalStrength implements Parcelable { if ((mLteSignalStrength == -1) && (mLteRsrp == -1) && (mLteRsrq == -1) - && (mLteRssnr == -1) && (mLteCqi == -1)) { dBm = getGsmDbm(); } else { @@ -566,16 +569,31 @@ public class SignalStrength implements Parcelable { */ public int getLteLevel() { int levelLteRsrp = 0; + int levelLteRssnr = 0; if (mLteRsrp == -1) levelLteRsrp = 0; - else if (mLteRsrp >= -90) levelLteRsrp = SIGNAL_STRENGTH_GREAT; - else if (mLteRsrp >= -100) levelLteRsrp = SIGNAL_STRENGTH_GOOD; - else if (mLteRsrp >= -110) levelLteRsrp = SIGNAL_STRENGTH_MODERATE; - else if (mLteRsrp >= -118) levelLteRsrp = SIGNAL_STRENGTH_POOR; - else levelLteRsrp = 0; - - if (DBG) log("Lte level: "+levelLteRsrp); - return levelLteRsrp; + else if (mLteRsrp >= -95) levelLteRsrp = SIGNAL_STRENGTH_GREAT; + else if (mLteRsrp >= -105) levelLteRsrp = SIGNAL_STRENGTH_GOOD; + else if (mLteRsrp >= -115) levelLteRsrp = SIGNAL_STRENGTH_MODERATE; + else levelLteRsrp = SIGNAL_STRENGTH_POOR; + + if (mLteRssnr == INVALID_SNR) levelLteRssnr = 0; + else if (mLteRssnr >= 45) levelLteRssnr = SIGNAL_STRENGTH_GREAT; + else if (mLteRssnr >= 10) levelLteRssnr = SIGNAL_STRENGTH_GOOD; + else if (mLteRssnr >= -30) levelLteRssnr = SIGNAL_STRENGTH_MODERATE; + else levelLteRssnr = SIGNAL_STRENGTH_POOR; + + int level; + if (mLteRsrp == -1) + level = levelLteRssnr; + else if (mLteRssnr == INVALID_SNR) + level = levelLteRsrp; + else + level = (levelLteRssnr < levelLteRsrp) ? levelLteRssnr : levelLteRsrp; + + if (DBG) log("Lte rsrp level: "+levelLteRsrp + + " snr level: " + levelLteRssnr + " level: " + level); + return level; } /** diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java index ee3985083091..a0efab20f80d 100644 --- a/telephony/java/com/android/internal/telephony/CommandsInterface.java +++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java @@ -93,11 +93,6 @@ public interface CommandsInterface { static final int USSD_MODE_NOTIFY = 0; static final int USSD_MODE_REQUEST = 1; - // SIM Refresh results, passed up from RIL. - static final int SIM_REFRESH_FILE_UPDATED = 0; // Single file updated - static final int SIM_REFRESH_INIT = 1; // SIM initialized; reload all - static final int SIM_REFRESH_RESET = 2; // SIM reset; may be locked - // GSM SMS fail cause for acknowledgeLastIncomingSMS. From TS 23.040, 9.2.3.22. static final int GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED = 0xD3; static final int GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY = 0xD4; @@ -764,6 +759,15 @@ public interface CommandsInterface { * retMsg.obj = AsyncResult ar * ar.exception carries exception on failure * ar.userObject contains the orignal value of result.obj + * ar.result is String containing IMSI on success + */ + void getIMSIForApp(String aid, Message result); + + /** + * returned message + * retMsg.obj = AsyncResult ar + * ar.exception carries exception on failure + * ar.userObject contains the orignal value of result.obj * ar.result is String containing IMEI on success */ void getIMEI(Message result); @@ -1055,6 +1059,14 @@ public interface CommandsInterface { String data, String pin2, Message response); /** + * parameters equivalent to 27.007 AT+CRSM command + * response.obj will be an AsyncResult + * response.obj.userObj will be a IccIoResult on success + */ + void iccIOForApp (int command, int fileid, String path, int p1, int p2, int p3, + String data, String pin2, String aid, Message response); + + /** * (AsyncResult)response.obj).result is an int[] with element [0] set to * 1 for "CLIP is provisioned", and 0 for "CLIP is not provisioned". * diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java index 664a0917d1a8..5d764844406d 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java @@ -225,7 +225,8 @@ public abstract class DataConnectionTracker extends Handler { // having to have different values for GSM and // CDMA. If so we can then remove the need for // getActionIntentReconnectAlarm. - protected static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = "reason"; + protected static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = + "reconnect_alarm_extra_reason"; // Used for debugging. Send the INTENT with an optional counter value with the number // of times the setup is to fail before succeeding. If the counter isn't passed the diff --git a/telephony/java/com/android/internal/telephony/IccCard.java b/telephony/java/com/android/internal/telephony/IccCard.java index f4308a0d3960..a9ef762f903f 100644 --- a/telephony/java/com/android/internal/telephony/IccCard.java +++ b/telephony/java/com/android/internal/telephony/IccCard.java @@ -48,7 +48,7 @@ public abstract class IccCard { protected String mLogTag; protected boolean mDbg; - private IccCardStatus mIccCardStatus = null; + protected IccCardStatus mIccCardStatus = null; protected State mState = null; private final Object mStateMonitor = new Object(); @@ -911,4 +911,24 @@ public abstract class IccCard { private void log(String msg) { Log.d(mLogTag, "[IccCard] " + msg); } + + protected abstract int getCurrentApplicationIndex(); + + public String getAid() { + String aid = ""; + int appIndex = getCurrentApplicationIndex(); + + if (appIndex >= 0 && appIndex < IccCardStatus.CARD_MAX_APPS) { + IccCardApplication app = mIccCardStatus.getApplication(appIndex); + if (app != null) { + aid = app.aid; + } else { + Log.e(mLogTag, "[IccCard] getAid: no current application index=" + appIndex); + } + } else { + Log.e(mLogTag, "[IccCard] getAid: Invalid Subscription Application index=" + appIndex); + } + + return aid; + } } diff --git a/telephony/java/com/android/internal/telephony/IccCardStatus.java b/telephony/java/com/android/internal/telephony/IccCardStatus.java index c751a21f24de..a3bdd76b6e00 100644 --- a/telephony/java/com/android/internal/telephony/IccCardStatus.java +++ b/telephony/java/com/android/internal/telephony/IccCardStatus.java @@ -24,7 +24,7 @@ import java.util.ArrayList; * {@hide} */ public class IccCardStatus { - static final int CARD_MAX_APPS = 8; + public static final int CARD_MAX_APPS = 8; public enum CardState { CARDSTATE_ABSENT, diff --git a/telephony/java/com/android/internal/telephony/IccFileHandler.java b/telephony/java/com/android/internal/telephony/IccFileHandler.java index 93b9b792a89b..380bfd1d05f2 100644 --- a/telephony/java/com/android/internal/telephony/IccFileHandler.java +++ b/telephony/java/com/android/internal/telephony/IccFileHandler.java @@ -145,8 +145,9 @@ public abstract class IccFileHandler extends Handler implements IccConstants { = obtainMessage(EVENT_GET_RECORD_SIZE_DONE, new LoadLinearFixedContext(fileid, recordNum, onLoaded)); - phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), - 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response); + phone.mCM.iccIOForApp(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), + 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, + phone.getIccCard().getAid(), response); } /** @@ -164,9 +165,10 @@ public abstract class IccFileHandler extends Handler implements IccConstants { onLoaded)); // TODO(): Verify when path changes are done. - phone.mCM.iccIO(COMMAND_GET_RESPONSE, IccConstants.EF_IMG, "img", + phone.mCM.iccIOForApp(COMMAND_GET_RESPONSE, IccConstants.EF_IMG, "img", recordNum, READ_RECORD_MODE_ABSOLUTE, - GET_RESPONSE_EF_IMG_SIZE_BYTES, null, null, response); + GET_RESPONSE_EF_IMG_SIZE_BYTES, null, null, + phone.getIccCard().getAid(), response); } /** @@ -182,8 +184,9 @@ public abstract class IccFileHandler extends Handler implements IccConstants { Message response = obtainMessage(EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE, new LoadLinearFixedContext(fileid, onLoaded)); - phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), - 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response); + phone.mCM.iccIOForApp(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), + 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, phone.getIccCard().getAid(), + response); } /** @@ -199,8 +202,9 @@ public abstract class IccFileHandler extends Handler implements IccConstants { Message response = obtainMessage(EVENT_GET_RECORD_SIZE_DONE, new LoadLinearFixedContext(fileid,onLoaded)); - phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), - 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response); + phone.mCM.iccIOForApp(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), + 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, + phone.getIccCard().getAid(), response); } /** @@ -217,8 +221,9 @@ public abstract class IccFileHandler extends Handler implements IccConstants { Message response = obtainMessage(EVENT_GET_BINARY_SIZE_DONE, fileid, 0, onLoaded); - phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), - 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response); + phone.mCM.iccIOForApp(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), + 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, + phone.getIccCard().getAid(), response); } /** @@ -236,8 +241,8 @@ public abstract class IccFileHandler extends Handler implements IccConstants { Message response = obtainMessage(EVENT_READ_ICON_DONE, fileid, 0, onLoaded); - phone.mCM.iccIO(COMMAND_READ_BINARY, fileid, "img", highOffset, lowOffset, - length, null, null, response); + phone.mCM.iccIOForApp(COMMAND_READ_BINARY, fileid, "img", highOffset, lowOffset, + length, null, null, phone.getIccCard().getAid(), response); } /** @@ -251,9 +256,10 @@ public abstract class IccFileHandler extends Handler implements IccConstants { */ public void updateEFLinearFixed(int fileid, int recordNum, byte[] data, String pin2, Message onComplete) { - phone.mCM.iccIO(COMMAND_UPDATE_RECORD, fileid, getEFPath(fileid), + phone.mCM.iccIOForApp(COMMAND_UPDATE_RECORD, fileid, getEFPath(fileid), recordNum, READ_RECORD_MODE_ABSOLUTE, data.length, - IccUtils.bytesToHexString(data), pin2, onComplete); + IccUtils.bytesToHexString(data), pin2, + phone.getIccCard().getAid(), onComplete); } /** @@ -262,9 +268,10 @@ public abstract class IccFileHandler extends Handler implements IccConstants { * @param data must be exactly as long as the EF */ public void updateEFTransparent(int fileid, byte[] data, Message onComplete) { - phone.mCM.iccIO(COMMAND_UPDATE_BINARY, fileid, getEFPath(fileid), + phone.mCM.iccIOForApp(COMMAND_UPDATE_BINARY, fileid, getEFPath(fileid), 0, 0, data.length, - IccUtils.bytesToHexString(data), null, onComplete); + IccUtils.bytesToHexString(data), null, + phone.getIccCard().getAid(), onComplete); } @@ -395,10 +402,11 @@ public abstract class IccFileHandler extends Handler implements IccConstants { lc.results = new ArrayList<byte[]>(lc.countRecords); } - phone.mCM.iccIO(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid), + phone.mCM.iccIOForApp(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid), lc.recordNum, READ_RECORD_MODE_ABSOLUTE, lc.recordSize, null, null, + phone.getIccCard().getAid(), obtainMessage(EVENT_READ_RECORD_DONE, lc)); break; case EVENT_GET_BINARY_SIZE_DONE: @@ -433,8 +441,9 @@ public abstract class IccFileHandler extends Handler implements IccConstants { size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8) + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff); - phone.mCM.iccIO(COMMAND_READ_BINARY, fileid, getEFPath(fileid), + phone.mCM.iccIOForApp(COMMAND_READ_BINARY, fileid, getEFPath(fileid), 0, 0, size, null, null, + phone.getIccCard().getAid(), obtainMessage(EVENT_READ_BINARY_DONE, fileid, 0, response)); break; @@ -468,10 +477,11 @@ public abstract class IccFileHandler extends Handler implements IccConstants { if (lc.recordNum > lc.countRecords) { sendResult(response, lc.results, null); } else { - phone.mCM.iccIO(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid), + phone.mCM.iccIOForApp(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid), lc.recordNum, READ_RECORD_MODE_ABSOLUTE, lc.recordSize, null, null, + phone.getIccCard().getAid(), obtainMessage(EVENT_READ_RECORD_DONE, lc)); } } diff --git a/telephony/java/com/android/internal/telephony/IccRefreshResponse.java b/telephony/java/com/android/internal/telephony/IccRefreshResponse.java new file mode 100644 index 000000000000..680670311cf3 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/IccRefreshResponse.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +/** + * See also RIL_SimRefresh in include/telephony/ril.h + * + * {@hide} + */ + +public class IccRefreshResponse { + + public static final int REFRESH_RESULT_FILE_UPDATE = 0; /* Single file was updated */ + public static final int REFRESH_RESULT_INIT = 1; /* The Icc has been initialized */ + public static final int REFRESH_RESULT_RESET = 2; /* The Icc was reset */ + + public int refreshResult; /* Sim Refresh result */ + public int efId; /* EFID */ + public String aid; /* null terminated string, e.g., + from 0xA0, 0x00 -> 0x41, + 0x30, 0x30, 0x30 */ + /* Example: a0000000871002f310ffff89080000ff */ + + @Override + public String toString() { + return "{" + refreshResult + ", " + aid +", " + efId + "}"; + } +} diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java index 718a4fd6e608..f587fe1be39f 100644 --- a/telephony/java/com/android/internal/telephony/RIL.java +++ b/telephony/java/com/android/internal/telephony/RIL.java @@ -52,6 +52,7 @@ import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import com.android.internal.telephony.gsm.SuppServiceNotification; import com.android.internal.telephony.cdma.CdmaCallWaitingNotification; import com.android.internal.telephony.cdma.CdmaInformationRecords; +import com.android.internal.telephony.IccRefreshResponse; import java.io.ByteArrayInputStream; import java.io.DataInputStream; @@ -879,9 +880,19 @@ public final class RIL extends BaseCommands implements CommandsInterface { public void getIMSI(Message result) { + getIMSIForApp(null, result); + } + + public void + getIMSIForApp(String aid, Message result) { RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMSI, result); - if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); + rr.mp.writeInt(1); + rr.mp.writeString(aid); + + if (RILJ_LOGD) riljLog(rr.serialString() + + "> getIMSI: " + requestToString(rr.mRequest) + + " aid: " + aid); send(rr); } @@ -1434,6 +1445,11 @@ public final class RIL extends BaseCommands implements CommandsInterface { public void iccIO (int command, int fileid, String path, int p1, int p2, int p3, String data, String pin2, Message result) { + iccIOForApp(command, fileid, path, p1, p2, p3, data, pin2, null, result); + } + public void + iccIOForApp (int command, int fileid, String path, int p1, int p2, int p3, + String data, String pin2, String aid, Message result) { //Note: This RIL request has not been renamed to ICC, // but this request is also valid for SIM and RUIM RILRequest rr @@ -1447,12 +1463,15 @@ public final class RIL extends BaseCommands implements CommandsInterface { rr.mp.writeInt(p3); rr.mp.writeString(data); rr.mp.writeString(pin2); + rr.mp.writeString(aid); - if (RILJ_LOGD) riljLog(rr.serialString() + "> iccIO: " + requestToString(rr.mRequest) + if (RILJ_LOGD) riljLog(rr.serialString() + "> iccIO: " + + requestToString(rr.mRequest) + " 0x" + Integer.toHexString(command) + " 0x" + Integer.toHexString(fileid) + " " + " path: " + path + "," - + p1 + "," + p2 + "," + p3); + + p1 + "," + p2 + "," + p3 + + " aid: " + aid); send(rr); } @@ -2419,7 +2438,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { case RIL_UNSOL_STK_EVENT_NOTIFY: ret = responseString(p); break; case RIL_UNSOL_STK_CALL_SETUP: ret = responseInts(p); break; case RIL_UNSOL_SIM_SMS_STORAGE_FULL: ret = responseVoid(p); break; - case RIL_UNSOL_SIM_REFRESH: ret = responseInts(p); break; + case RIL_UNSOL_SIM_REFRESH: ret = responseSimRefresh(p); break; case RIL_UNSOL_CALL_RING: ret = responseCallRing(p); break; case RIL_UNSOL_RESTRICTED_STATE_CHANGED: ret = responseInts(p); break; case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED: ret = responseVoid(p); break; @@ -2976,6 +2995,16 @@ public final class RIL extends BaseCommands implements CommandsInterface { } private Object + responseSimRefresh(Parcel p) { + IccRefreshResponse response = new IccRefreshResponse(); + + response.refreshResult = p.readInt(); + response.efId = p.readInt(); + response.aid = p.readString(); + return response; + } + + private Object responseCallList(Parcel p) { int num; int voiceSettings; diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java index 6b73cc5d4bf1..7da23d05e3d9 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java @@ -124,7 +124,9 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { @Override protected void setSignalStrengthDefaultValues() { - mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, false); + // TODO Make a constructor only has boolean gsm as parameter + mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, + -1, -1, -1, SignalStrength.INVALID_SNR, -1, false); } @Override @@ -429,8 +431,13 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { setSignalStrengthDefaultValues(); } else { int[] ints = (int[])ar.result; - int lteCqi = 99, lteRsrp = -1; - int lteRssi = 99; + + int lteRssi = -1; + int lteRsrp = -1; + int lteRsrq = -1; + int lteRssnr = SignalStrength.INVALID_SNR; + int lteCqi = -1; + int offset = 2; int cdmaDbm = (ints[offset] > 0) ? -ints[offset] : -120; int cdmaEcio = (ints[offset + 1] > 0) ? -ints[offset + 1] : -160; @@ -438,10 +445,13 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { int evdoEcio = (ints[offset + 3] > 0) ? -ints[offset + 3] : -1; int evdoSnr = ((ints[offset + 4] > 0) && (ints[offset + 4] <= 8)) ? ints[offset + 4] : -1; + if (networkType == ServiceState.RADIO_TECHNOLOGY_LTE) { - lteRssi = (ints[offset + 5] >= 0) ? ints[offset + 5] : 99; - lteRsrp = (ints[offset + 6] < 0) ? ints[offset + 6] : -1; - lteCqi = (ints[offset + 7] >= 0) ? ints[offset + 7] : 99; + lteRssi = ints[offset+5]; + lteRsrp = ints[offset+6]; + lteRsrq = ints[offset+7]; + lteRssnr = ints[offset+8]; + lteCqi = ints[offset+9]; } if (networkType != ServiceState.RADIO_TECHNOLOGY_LTE) { @@ -449,7 +459,7 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { evdoSnr, false); } else { mSignalStrength = new SignalStrength(99, -1, cdmaDbm, cdmaEcio, evdoRssi, evdoEcio, - evdoSnr, lteRssi, lteRsrp, -1, -1, lteCqi, true); + evdoSnr, lteRssi, lteRsrp, lteRsrq, lteRssnr, lteCqi, true); } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java index b57af0ede61c..8375fd04b67f 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java @@ -55,8 +55,8 @@ public final class CdmaLteUiccFileHandler extends IccFileHandler { if (fileid == EF_CSIM_EPRL) { // Entire PRL could be huge. We are only interested in // the first 4 bytes of the record. - phone.mCM.iccIO(COMMAND_READ_BINARY, fileid, getEFPath(fileid), - 0, 0, 4, null, null, + phone.mCM.iccIOForApp(COMMAND_READ_BINARY, fileid, getEFPath(fileid), + 0, 0, 4, null, null, phone.getIccCard().getAid(), obtainMessage(EVENT_READ_BINARY_DONE, fileid, 0, onLoaded)); } else { diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimCard.java b/telephony/java/com/android/internal/telephony/cdma/RuimCard.java index 02eb86ddc028..674fada9e185 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimCard.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimCard.java @@ -44,5 +44,13 @@ public final class RuimCard extends IccCard { public String getServiceProviderName () { return mPhone.mIccRecords.getServiceProviderName(); } + + @Override + protected int getCurrentApplicationIndex() { + if (mIccCardStatus == null) { + return -1; + } + return mIccCardStatus.getCdmaSubscriptionAppIndex(); + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java b/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java index 3e2a29be932b..375cc0763eef 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java @@ -57,8 +57,9 @@ public final class RuimFileHandler extends IccFileHandler { Message response = obtainMessage(EVENT_READ_ICON_DONE, fileid, 0, onLoaded); - phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, "img", 0, 0, - GET_RESPONSE_EF_IMG_SIZE_BYTES, null, null, response); + phone.mCM.iccIOForApp(COMMAND_GET_RESPONSE, fileid, "img", 0, 0, + GET_RESPONSE_EF_IMG_SIZE_BYTES, null, null, + phone.getIccCard().getAid(), response); } @Override diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java index b057e46792ad..17a200e37fed 100755 --- a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java @@ -29,6 +29,7 @@ import com.android.internal.telephony.AdnRecord; import com.android.internal.telephony.AdnRecordCache; import com.android.internal.telephony.AdnRecordLoader; import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.IccRefreshResponse; import com.android.internal.telephony.TelephonyProperties; import com.android.internal.telephony.cdma.RuimCard; import com.android.internal.telephony.MccTable; @@ -300,7 +301,7 @@ public final class RuimRecords extends IccRecords { isRecordLoadResponse = false; ar = (AsyncResult)msg.obj; if (ar.exception == null) { - handleRuimRefresh((int[])(ar.result)); + handleRuimRefresh((IccRefreshResponse)ar.result); } break; @@ -409,24 +410,30 @@ public final class RuimRecords extends IccRecords { ((CDMAPhone) phone).notifyMessageWaitingIndicator(); } - private void handleRuimRefresh(int[] result) { - if (result == null || result.length == 0) { - if (DBG) log("handleRuimRefresh without input"); + private void handleRuimRefresh(IccRefreshResponse refreshResponse) { + if (refreshResponse == null) { + if (DBG) log("handleRuimRefresh received without input"); return; } - switch ((result[0])) { - case CommandsInterface.SIM_REFRESH_FILE_UPDATED: + if (refreshResponse.aid != null && + !refreshResponse.aid.equals(phone.getIccCard().getAid())) { + // This is for different app. Ignore. + return; + } + + switch (refreshResponse.refreshResult) { + case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE: if (DBG) log("handleRuimRefresh with SIM_REFRESH_FILE_UPDATED"); adnCache.reset(); fetchRuimRecords(); break; - case CommandsInterface.SIM_REFRESH_INIT: + case IccRefreshResponse.REFRESH_RESULT_INIT: if (DBG) log("handleRuimRefresh with SIM_REFRESH_INIT"); // need to reload all files (that we care about) fetchRuimRecords(); break; - case CommandsInterface.SIM_REFRESH_RESET: + case IccRefreshResponse.REFRESH_RESULT_RESET: if (DBG) log("handleRuimRefresh with SIM_REFRESH_RESET"); phone.mCM.setRadioPower(false, null); /* Note: no need to call setRadioPower(true). Assuming the desired diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 8f04dbae8811..66e948793a8c 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -131,7 +131,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { private static final String INTENT_RECONNECT_ALARM = "com.android.internal.telephony.gprs-reconnect"; - private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "type"; + private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type"; private static final String INTENT_DATA_STALL_ALARM = "com.android.internal.telephony.gprs-data-stall"; diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java index 92e16ce649f0..0ebeabedacfc 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java @@ -675,7 +675,9 @@ final class GsmServiceStateTracker extends ServiceStateTracker { } private void setSignalStrengthDefaultValues() { - mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, true); + // TODO Make a constructor only has boolean gsm as parameter + mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, + -1, -1, -1, SignalStrength.INVALID_SNR, -1, true); } /** @@ -1013,7 +1015,7 @@ final class GsmServiceStateTracker extends ServiceStateTracker { int lteSignalStrength = -1; int lteRsrp = -1; int lteRsrq = -1; - int lteRssnr = -1; + int lteRssnr = SignalStrength.INVALID_SNR; int lteCqi = -1; if (ar.exception != null) { diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java index 7c423c746ac1..de8401eab6be 100755 --- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java +++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java @@ -39,6 +39,7 @@ import com.android.internal.telephony.MccTable; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneBase; import com.android.internal.telephony.SmsMessageBase; +import com.android.internal.telephony.IccRefreshResponse; import java.util.ArrayList; @@ -1049,7 +1050,7 @@ public class SIMRecords extends IccRecords { ar = (AsyncResult)msg.obj; if (DBG) log("Sim REFRESH with exception: " + ar.exception); if (ar.exception == null) { - handleSimRefresh((int[])(ar.result)); + handleSimRefresh((IccRefreshResponse)ar.result); } break; case EVENT_GET_CFIS_DONE: @@ -1130,27 +1131,31 @@ public class SIMRecords extends IccRecords { } } - private void handleSimRefresh(int[] result) { - if (result == null || result.length == 0) { - if (DBG) log("handleSimRefresh without input"); + private void handleSimRefresh(IccRefreshResponse refreshResponse){ + if (refreshResponse == null) { + if (DBG) log("handleSimRefresh received without input"); return; } - switch ((result[0])) { - case CommandsInterface.SIM_REFRESH_FILE_UPDATED: - if (DBG) log("handleSimRefresh with SIM_REFRESH_FILE_UPDATED"); - // result[1] contains the EFID of the updated file. - int efid = result[1]; - handleFileUpdate(efid); + if (refreshResponse.aid != null && + !refreshResponse.aid.equals(phone.getIccCard().getAid())) { + // This is for different app. Ignore. + return; + } + + switch (refreshResponse.refreshResult) { + case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE: + if (DBG) log("handleSimRefresh with SIM_FILE_UPDATED"); + handleFileUpdate(refreshResponse.efId); break; - case CommandsInterface.SIM_REFRESH_INIT: - if (DBG) log("handleSimRefresh with SIM_REFRESH_INIT"); + case IccRefreshResponse.REFRESH_RESULT_INIT: + if (DBG) log("handleSimRefresh with SIM_REFRESH_INIT"); // need to reload all files (that we care about) adnCache.reset(); fetchSimRecords(); break; - case CommandsInterface.SIM_REFRESH_RESET: - if (DBG) log("handleSimRefresh with SIM_REFRESH_RESET"); + case IccRefreshResponse.REFRESH_RESULT_RESET: + if (DBG) log("handleSimRefresh with SIM_REFRESH_RESET"); phone.mCM.setRadioPower(false, null); /* Note: no need to call setRadioPower(true). Assuming the desired * radio power state is still ON (as tracked by ServiceStateTracker), @@ -1162,7 +1167,7 @@ public class SIMRecords extends IccRecords { break; default: // unknown refresh operation - if (DBG) log("handleSimRefresh with unknown operation"); + if (DBG) log("handleSimRefresh with unknown operation"); break; } } @@ -1304,7 +1309,7 @@ public class SIMRecords extends IccRecords { logv("fetchSimRecords " + recordsToLoad); - phone.mCM.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE)); + phone.mCM.getIMSIForApp(phone.getIccCard().getAid(), obtainMessage(EVENT_GET_IMSI_DONE)); recordsToLoad++; iccFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE)); diff --git a/telephony/java/com/android/internal/telephony/gsm/SimCard.java b/telephony/java/com/android/internal/telephony/gsm/SimCard.java index e34e10a67991..0e68e07e222d 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SimCard.java +++ b/telephony/java/com/android/internal/telephony/gsm/SimCard.java @@ -39,4 +39,11 @@ public final class SimCard extends IccCard { return mPhone.mIccRecords.getServiceProviderName(); } + @Override + protected int getCurrentApplicationIndex() { + if (mIccCardStatus == null) { + return -1; + } + return mIccCardStatus.getGsmUmtsSubscriptionAppIndex(); + } } diff --git a/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java b/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java index 920198418424..99f4e0f09053 100644 --- a/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java +++ b/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java @@ -84,6 +84,9 @@ class SipCommandInterface extends BaseCommands implements CommandsInterface { public void getIMSI(Message result) { } + public void getIMSIForApp(String aid, Message result) { + } + public void getIMEI(Message result) { } @@ -213,6 +216,9 @@ class SipCommandInterface extends BaseCommands implements CommandsInterface { public void iccIO (int command, int fileid, String path, int p1, int p2, int p3, String data, String pin2, Message result) { } + public void iccIOForApp (int command, int fileid, String path, int p1, int p2, + int p3, String data, String pin2, String aid, Message result) { + } public void getCLIR(Message result) { } diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java index 60d9d24f6a97..4f61509f918d 100644 --- a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java +++ b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java @@ -504,6 +504,9 @@ public final class SimulatedCommands extends BaseCommands resultSuccess(result, null); } + public void getIMSI(Message result) { + getIMSIForApp(null, result); + } /** * returned message * retMsg.obj = AsyncResult ar @@ -511,7 +514,7 @@ public final class SimulatedCommands extends BaseCommands * ar.userObject contains the original value of result.obj * ar.result is String containing IMSI on success */ - public void getIMSI(Message result) { + public void getIMSIForApp(String aid, Message result) { resultSuccess(result, "012345678901234"); } @@ -1042,13 +1045,18 @@ public final class SimulatedCommands extends BaseCommands unimplemented(result); } + public void iccIO(int command, int fileid, String path, int p1, int p2, int p3, String data, + String pin2, Message response) { + iccIOForApp(command, fileid, path, p1, p2, p3, data,pin2, null, response); + } + /** * parameters equivalent to 27.007 AT+CRSM command * response.obj will be an AsyncResult * response.obj.userObj will be a SimIoResult on success */ - public void iccIO (int command, int fileid, String path, int p1, int p2, - int p3, String data, String pin2, Message result) { + public void iccIOForApp (int command, int fileid, String path, int p1, int p2, + int p3, String data, String pin2, String aid, Message result) { unimplemented(result); } diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java index a385f5575b48..5b7627295902 100644 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java @@ -511,8 +511,9 @@ public class PhoneNumberUtilsTest extends AndroidTestCase { @SmallTest public void testFormatNumber() { assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "US")); - assertEquals("123-4567", PhoneNumberUtils.formatNumber("1234567", "US")); - assertEquals("(800) 466-4114", PhoneNumberUtils.formatNumber("800-GOOG-114", "US")); + assertEquals("223-4567", PhoneNumberUtils.formatNumber("2234567", "US")); + // formatNumber doesn't format alpha numbers, but keep them as they are. + assertEquals("800-GOOG-114", PhoneNumberUtils.formatNumber("800-GOOG-114", "US")); } @SmallTest @@ -592,9 +593,12 @@ public class PhoneNumberUtilsTest extends AndroidTestCase { // addressing that, they are also classified as "potential" emergency numbers in the US. assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "US")); assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("11212345", "US")); + // A valid mobile phone number from Singapore shouldn't be classified as an emergency number // in Singapore, as 911 is not an emergency number there. - assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91121234", "SG")); + // This test fails on devices that have ecclist property preloaded with 911. + // assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91121234", "SG")); + // A valid fixed-line phone number from Brazil shouldn't be classified as an emergency number // in Brazil, as 112 is not an emergency number there. assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("1121234567", "BR")); diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java index d2e573ce0564..6f0175e48c08 100644 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java @@ -64,7 +64,7 @@ public class PhoneNumberWatcherTest extends AndroidTestCase { assertEquals(result1, number.toString()); assertEquals(result1.length(), Selection.getSelectionEnd(number)); // Remove last 5 chars - final String result2 = "(650) 123"; + final String result2 = "650-123"; textWatcher.beforeTextChanged(number, number.length() - 4, 4, 0); number.delete(number.length() - 5, number.length()); Selection.setSelection(number, number.length()); @@ -75,26 +75,26 @@ public class PhoneNumberWatcherTest extends AndroidTestCase { } public void testInsertChars() { - final String init = "(650) 23"; - final String expected1 = "(650) 123"; + final String init = "650-23"; + final String expected1 = "650-123"; TextWatcher textWatcher = getTextWatcher(); // Insert one char SpannableStringBuilder number = new SpannableStringBuilder(init); - textWatcher.beforeTextChanged(number, 4, 0, 1); - number.insert(4, "1"); // (6501) 23 - Selection.setSelection(number, 5); // make the cursor at right of 1 - textWatcher.onTextChanged(number, 4, 0, 1); + textWatcher.beforeTextChanged(number, 3, 0, 1); + number.insert(3, "1"); // 6501-23 + Selection.setSelection(number, 4); // make the cursor at right of 1 + textWatcher.onTextChanged(number, 3, 0, 1); textWatcher.afterTextChanged(number); assertEquals(expected1, number.toString()); // the cursor should still at the right of '1' - assertEquals(7, Selection.getSelectionEnd(number)); + assertEquals(5, Selection.getSelectionEnd(number)); // Insert multiple chars final String expected2 = "(650) 145-6723"; - textWatcher.beforeTextChanged(number, 7, 0, 4); - number.insert(7, "4567"); // change to (650) 1456723 - Selection.setSelection(number, 11); // the cursor is at the right of '7'. + textWatcher.beforeTextChanged(number, 5, 0, 4); + number.insert(5, "4567"); // change to 650-1456723 + Selection.setSelection(number, 9); // the cursor is at the right of '7'. textWatcher.onTextChanged(number, 7, 0, 4); textWatcher.afterTextChanged(number); assertEquals(expected2, number.toString()); @@ -168,7 +168,7 @@ public class PhoneNumberWatcherTest extends AndroidTestCase { textWatcher.onTextChanged(number, 0, len, 0); textWatcher.afterTextChanged(number); - final String expected2 = "(650) 123-4"; + final String expected2 = "650-1234"; number = new SpannableStringBuilder(init); textWatcher.beforeTextChanged(number, 9, 0, 1); number.insert(9, "4"); // (650) 1234 diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java index b385cee0138a..ea6836decbca 100644 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java @@ -612,4 +612,13 @@ class UsimDataDownloadCommands extends BaseCommands { @Override public void getVoiceRadioTechnology(Message response) { } + + @Override + public void getIMSIForApp(String aid, Message result) { + } + + @Override + public void iccIOForApp(int command, int fileid, String path, int p1, int p2, int p3, + String data, String pin2, String aid, Message response) { + } } diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java index 4ff943e23532..a8c388e33053 100644 --- a/test-runner/src/android/test/mock/MockContentProvider.java +++ b/test-runner/src/android/test/mock/MockContentProvider.java @@ -21,7 +21,7 @@ import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentValues; import android.content.Context; -import android.content.ICancelationSignal; +import android.content.ICancellationSignal; import android.content.IContentProvider; import android.content.OperationApplicationException; import android.content.pm.PathPermission; @@ -93,7 +93,7 @@ public class MockContentProvider extends ContentProvider { @Override public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, - String sortOrder, ICancelationSignal cancelationSignal) throws RemoteException { + String sortOrder, ICancellationSignal cancellationSignal) throws RemoteException { return MockContentProvider.this.query(url, projection, selection, selectionArgs, sortOrder); } @@ -127,7 +127,7 @@ public class MockContentProvider extends ContentProvider { } @Override - public ICancelationSignal createCancelationSignal() throws RemoteException { + public ICancellationSignal createCancellationSignal() throws RemoteException { return null; } } diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java index 41bc27d0e5b4..1aa0448e7fa1 100644 --- a/test-runner/src/android/test/mock/MockIContentProvider.java +++ b/test-runner/src/android/test/mock/MockIContentProvider.java @@ -21,7 +21,7 @@ import android.content.ContentProviderResult; import android.content.ContentValues; import android.content.EntityIterator; import android.content.IContentProvider; -import android.content.ICancelationSignal; +import android.content.ICancellationSignal; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; @@ -73,7 +73,7 @@ public class MockIContentProvider implements IContentProvider { } public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, - String sortOrder, ICancelationSignal cancelationSignal) { + String sortOrder, ICancellationSignal cancellationSignal) { throw new UnsupportedOperationException("unimplemented mock method"); } @@ -106,7 +106,7 @@ public class MockIContentProvider implements IContentProvider { } @Override - public ICancelationSignal createCancelationSignal() throws RemoteException { + public ICancellationSignal createCancellationSignal() throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } } diff --git a/tests/BiDiTests/res/layout/basic.xml b/tests/BiDiTests/res/layout/basic.xml index f503658fba1d..7d4d4db2aa34 100644 --- a/tests/BiDiTests/res/layout/basic.xml +++ b/tests/BiDiTests/res/layout/basic.xml @@ -19,6 +19,10 @@ android:layout_width="fill_parent" android:layout_height="fill_parent"> + <ScrollView + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -131,4 +135,6 @@ </LinearLayout> + </ScrollView> + </FrameLayout> diff --git a/tests/BiDiTests/res/layout/grid_layout_locale.xml b/tests/BiDiTests/res/layout/grid_layout_locale.xml new file mode 100644 index 000000000000..4198898f7771 --- /dev/null +++ b/tests/BiDiTests/res/layout/grid_layout_locale.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/grid_layout_locale" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + + <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:useDefaultMargins="true" + android:alignmentMode="alignBounds" + android:columnOrderPreserved="false" + android:columnCount="4" + android:layoutDirection="locale"> + + <TextView + android:text="Email setup" + android:textSize="32dip" + + android:layout_columnSpan="4" + android:layout_gravity="center_horizontal"/> + + <TextView + android:text="You can configure email in just a few steps:" + android:textSize="16dip" + android:layout_columnSpan="4" + android:layout_gravity="left"/> + + <TextView + android:text="Email address:" + android:layout_gravity="right"/> + + <EditText + android:ems="10"/> + + <TextView + android:text="Password:" + android:layout_column="0" + android:layout_gravity="right"/> + + <EditText + android:ems="8"/> + + <Space + android:layout_row="4" + android:layout_column="0" + android:layout_columnSpan="3" + android:layout_gravity="fill" + /> + + <Button + android:text="Next" + android:layout_row="5" + android:layout_column="3"/> + + </GridLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/tests/BiDiTests/res/layout/grid_layout_ltr.xml b/tests/BiDiTests/res/layout/grid_layout_ltr.xml new file mode 100644 index 000000000000..46ea6580cb96 --- /dev/null +++ b/tests/BiDiTests/res/layout/grid_layout_ltr.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/grid_layout_ltr" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + + <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:useDefaultMargins="true" + android:alignmentMode="alignBounds" + android:columnOrderPreserved="false" + android:columnCount="4" + android:layoutDirection="ltr"> + + <TextView + android:text="Email setup" + android:textSize="32dip" + + android:layout_columnSpan="4" + android:layout_gravity="center_horizontal"/> + + <TextView + android:text="You can configure email in just a few steps:" + android:textSize="16dip" + android:layout_columnSpan="4" + android:layout_gravity="left"/> + + <TextView + android:text="Email address:" + android:layout_gravity="right"/> + + <EditText + android:ems="10"/> + + <TextView + android:text="Password:" + android:layout_column="0" + android:layout_gravity="right"/> + + <EditText + android:ems="8"/> + + <Space + android:layout_row="4" + android:layout_column="0" + android:layout_columnSpan="3" + android:layout_gravity="fill" + /> + + <Button + android:text="Next" + android:layout_row="5" + android:layout_column="3"/> + + </GridLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/tests/BiDiTests/res/layout/grid_layout_rtl.xml b/tests/BiDiTests/res/layout/grid_layout_rtl.xml new file mode 100644 index 000000000000..947e13c84a5e --- /dev/null +++ b/tests/BiDiTests/res/layout/grid_layout_rtl.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/grid_layout_rtl" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + + <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:useDefaultMargins="true" + android:alignmentMode="alignBounds" + android:columnOrderPreserved="false" + android:columnCount="4" + android:layoutDirection="rtl"> + + <TextView + android:text="Email setup" + android:textSize="32dip" + + android:layout_columnSpan="4" + android:layout_gravity="center_horizontal"/> + + <TextView + android:text="You can configure email in just a few steps:" + android:textSize="16dip" + android:layout_columnSpan="4" + android:layout_gravity="left"/> + + <TextView + android:text="Email address:" + android:layout_gravity="right"/> + + <EditText + android:ems="10"/> + + <TextView + android:text="Password:" + android:layout_column="0" + android:layout_gravity="right"/> + + <EditText + android:ems="8"/> + + <Space + android:layout_row="4" + android:layout_column="0" + android:layout_columnSpan="3" + android:layout_gravity="fill" + /> + + <Button + android:text="Next" + android:layout_row="5" + android:layout_column="3"/> + + </GridLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/tests/BiDiTests/res/values/strings.xml b/tests/BiDiTests/res/values/strings.xml index 1d4fc84eca17..233cd0df4ea9 100644 --- a/tests/BiDiTests/res/values/strings.xml +++ b/tests/BiDiTests/res/values/strings.xml @@ -24,11 +24,11 @@ <string name="button_before_text">Start</string> <string name="button_requestlayout_text">Request Layout</string> <string name="button_alert_dialog_text">AlertDialog</string> - <string name="textview_text">This is a text for a TextView</string> - <string name="textview_ltr_text">This is a text for a LTR TextView</string> - <string name="textview_rtl_text">This is a text for a RTL TextView</string> - <string name="textview_default_text">This is a text for a default TextView</string> - <string name="textview_password_default_text">This is a text for a password TextView</string> + <string name="textview_text">TextView</string> + <string name="textview_ltr_text">LTR TextView</string> + <string name="textview_rtl_text">RTL TextView</string> + <string name="textview_default_text">Default TextView</string> + <string name="textview_password_default_text">Password TextView</string> <string name="edittext_text">mmmmmmmmmmmmmmmmmmmmmmmm</string> <string name="normal_text">Normal String</string> <string name="normal_long_text">mmmmmmmmmmmmmmmmmmmmmmmm</string> diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java index b45b98f0c9cc..4d7ace1d885b 100644 --- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java @@ -108,6 +108,10 @@ public class BiDiTestActivity extends Activity { addItem(result, "Linear RTL", BiDiTestLinearLayoutRtl.class, R.id.linear_layout_rtl); addItem(result, "Linear LOC", BiDiTestLinearLayoutLocale.class, R.id.linear_layout_locale); + addItem(result, "Grid LTR", BiDiTestGridLayoutLtr.class, R.id.grid_layout_ltr); + addItem(result, "Grid RTL", BiDiTestGridLayoutRtl.class, R.id.grid_layout_rtl); + addItem(result, "Grid LOC", BiDiTestGridLayoutLocale.class, R.id.grid_layout_locale); + addItem(result, "Frame LTR", BiDiTestFrameLayoutLtr.class, R.id.frame_layout_ltr); addItem(result, "Frame RTL", BiDiTestFrameLayoutRtl.class, R.id.frame_layout_rtl); addItem(result, "Frame LOC", BiDiTestFrameLayoutLocale.class, R.id.frame_layout_locale); diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestGridLayoutLocale.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestGridLayoutLocale.java new file mode 100644 index 000000000000..16e61ad0f6b5 --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestGridLayoutLocale.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bidi; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class BiDiTestGridLayoutLocale extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.grid_layout_locale, container, false); + } +} diff --git a/libs/rs/rsFifo.cpp b/tests/BiDiTests/src/com/android/bidi/BiDiTestGridLayoutLtr.java index 3d5d8c45b3c1..df6c9fe00c82 100644 --- a/libs/rs/rsFifo.cpp +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestGridLayoutLtr.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2012 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. @@ -14,18 +14,19 @@ * limitations under the License. */ -#include "rsFifoSocket.h" -#include "utils/Timers.h" -#include "utils/StopWatch.h" +package com.android.bidi; -using namespace android; -using namespace android::renderscript; +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; -Fifo::Fifo() { +public class BiDiTestGridLayoutLtr extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.grid_layout_ltr, container, false); + } } - -Fifo::~Fifo() { - -} - diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestGridLayoutRtl.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestGridLayoutRtl.java new file mode 100644 index 000000000000..8bed113b9a01 --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestGridLayoutRtl.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bidi; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class BiDiTestGridLayoutRtl extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.grid_layout_rtl, container, false); + } +} diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 5bbcce30fdba..3904c214b54d 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -30,6 +30,8 @@ android:label="HwUi" android:hardwareAccelerated="true"> + <meta-data android:name="android.graphics.renderThread" android:value="true" /> + <activity android:name="PaintDrawFilterActivity" android:label="_DrawFilter"> @@ -38,6 +40,15 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + + <activity + android:name="ClipRegionActivity" + android:label="_ClipRegion"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> <activity android:name="DisplayListLayersActivity" diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegionActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegionActivity.java new file mode 100644 index 000000000000..b2a508b0cc16 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegionActivity.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Region; +import android.os.Bundle; +import android.view.View; + +@SuppressWarnings({"UnusedDeclaration"}) +public class ClipRegionActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final RegionView view = new RegionView(this); + setContentView(view); + } + + public static class RegionView extends View { + public RegionView(Context c) { + super(c); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.save(); + canvas.clipRect(100.0f, 100.0f, getWidth() - 100.0f, getHeight() - 100.0f, + Region.Op.DIFFERENCE); + canvas.drawARGB(255, 255, 0, 0); + canvas.restore(); + } + } +} diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingTest.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingTest.java index f96e68bcdb90..912d863c6d05 100644 --- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingTest.java +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingTest.java @@ -84,7 +84,7 @@ public class ImageProcessingTest extends ActivityInstrumentationTestCase2<ImageP Log.v(TAG, "RenderScript framew time core: " + t + " ms"); } long avgValue = sum/ITERATION; - rsWriter.write("Averge frame time: " + avgValue + " ms\n"); + rsWriter.write("Average frame time: " + avgValue + " ms\n"); Log.v(TAG, "Average frame time: " + avgValue + " ms"); rsWriter.close(); } catch (IOException e) { diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java index 5ab2c58a4959..22936786c9ac 100644 --- a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java @@ -65,6 +65,7 @@ public class RSTestCore { unitTests = new ArrayList<UnitTest>(); unitTests.add(new UT_primitives(this, mRes, mCtx)); + unitTests.add(new UT_constant(this, mRes, mCtx)); unitTests.add(new UT_vector(this, mRes, mCtx)); unitTests.add(new UT_rsdebug(this, mRes, mCtx)); unitTests.add(new UT_rstime(this, mRes, mCtx)); @@ -73,6 +74,7 @@ public class RSTestCore { unitTests.add(new UT_refcount(this, mRes, mCtx)); unitTests.add(new UT_foreach(this, mRes, mCtx)); unitTests.add(new UT_atomic(this, mRes, mCtx)); + unitTests.add(new UT_struct(this, mRes, mCtx)); unitTests.add(new UT_math(this, mRes, mCtx)); unitTests.add(new UT_fp_mad(this, mRes, mCtx)); /* @@ -92,7 +94,7 @@ public class RSTestCore { for (int i = 0; i < uta.length; i++) { ScriptField_ListAllocs_s.Item listElem = new ScriptField_ListAllocs_s.Item(); listElem.text = Allocation.createFromString(mRS, uta[i].name, Allocation.USAGE_SCRIPT); - listElem.result = uta[i].result; + listElem.result = uta[i].getResult(); mListAllocs.set(listElem, i, false); uta[i].setItem(listElem); } diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_constant.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_constant.java new file mode 100644 index 000000000000..adda5a3cbb81 --- /dev/null +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_constant.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.rs.test; + +import android.content.Context; +import android.content.res.Resources; +import android.renderscript.*; + +public class UT_constant extends UnitTest { + private Resources mRes; + + protected UT_constant(RSTestCore rstc, Resources res, Context ctx) { + super(rstc, "Const", ctx); + mRes = res; + } + + private void Assert(boolean b) { + if (!b) { + failTest(); + } + } + + public void run() { + Assert(ScriptC_constant.const_floatTest == 1.99f); + Assert(ScriptC_constant.const_doubleTest == 2.05); + Assert(ScriptC_constant.const_charTest == -8); + Assert(ScriptC_constant.const_shortTest == -16); + Assert(ScriptC_constant.const_intTest == -32); + Assert(ScriptC_constant.const_longTest == 17179869184l); + Assert(ScriptC_constant.const_longlongTest == 68719476736l); + + Assert(ScriptC_constant.const_ucharTest == 8); + Assert(ScriptC_constant.const_ushortTest == 16); + Assert(ScriptC_constant.const_uintTest == 32); + Assert(ScriptC_constant.const_ulongTest == 4611686018427387904L); + Assert(ScriptC_constant.const_int64_tTest == -17179869184l); + Assert(ScriptC_constant.const_uint64_tTest == 117179869184l); + + Assert(ScriptC_constant.const_boolTest == true); + + passTest(); + } +} diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_primitives.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_primitives.java index b7a65a57951e..18829c202e43 100644 --- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_primitives.java +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_primitives.java @@ -92,8 +92,7 @@ public class UT_primitives extends UnitTest { ScriptC_primitives s = new ScriptC_primitives(pRS, mRes, R.raw.primitives); pRS.setMessageHandler(mRsMessage); if (!initializeGlobals(s)) { - // initializeGlobals failed - result = -1; + failTest(); } else { s.invoke_primitives_test(0, 0); pRS.finish(); diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_struct.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_struct.java new file mode 100644 index 000000000000..2a55686b3d68 --- /dev/null +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_struct.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.rs.test; + +import android.content.Context; +import android.content.res.Resources; +import android.renderscript.*; + +public class UT_struct extends UnitTest { + private Resources mRes; + + protected UT_struct(RSTestCore rstc, Resources res, Context ctx) { + super(rstc, "Struct", ctx); + mRes = res; + } + + public void run() { + RenderScript pRS = RenderScript.create(mCtx); + ScriptC_struct s = new ScriptC_struct(pRS, mRes, R.raw.struct); + pRS.setMessageHandler(mRsMessage); + + ScriptField_Point2 p = new ScriptField_Point2(pRS, 1); + ScriptField_Point2.Item i = new ScriptField_Point2.Item(); + int val = 100; + i.x = val; + i.y = val; + p.set(i, 0, true); + s.bind_point2(p); + s.invoke_struct_test(val); + pRS.finish(); + waitForMessage(); + + val = 200; + p.set_x(0, val, true); + p.set_y(0, val, true); + s.invoke_struct_test(val); + pRS.finish(); + waitForMessage(); + pRS.destroy(); + } +} diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java index 748701dd4e0a..0ac09ca5650c 100644 --- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java @@ -307,7 +307,7 @@ public class UT_vector extends UnitTest { ScriptC_vector s = new ScriptC_vector(pRS, mRes, R.raw.vector); pRS.setMessageHandler(mRsMessage); if (!initializeGlobals(s)) { - result = -1; + failTest(); } else { s.invoke_vector_test(); pRS.finish(); diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java index a97ffa7d47ab..edff83f2756b 100644 --- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java @@ -21,7 +21,7 @@ import android.renderscript.RenderScript.RSMessageHandler; public class UnitTest extends Thread { public String name; - public int result; + private int result; private ScriptField_ListAllocs_s.Item mItem; private RSTestCore mRSTC; private boolean msgHandled; @@ -63,7 +63,7 @@ public class UnitTest extends Thread { } } - protected void updateUI() { + private void updateUI() { if (mItem != null) { mItem.result = result; msgHandled = true; @@ -104,6 +104,22 @@ public class UnitTest extends Thread { } } + public int getResult() { + return result; + } + + public void failTest() { + result = -1; + updateUI(); + } + + public void passTest() { + if (result != -1) { + result = 1; + } + updateUI(); + } + public void setItem(ScriptField_ListAllocs_s.Item item) { mItem = item; } diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/constant.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/constant.rs new file mode 100644 index 000000000000..732eaefa6bb8 --- /dev/null +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/constant.rs @@ -0,0 +1,19 @@ +#include "shared.rsh" + +const float floatTest = 1.99f; +const double doubleTest = 2.05; +const char charTest = -8; +const short shortTest = -16; +const int intTest = -32; +const long longTest = 17179869184l; // 1 << 34 +const long long longlongTest = 68719476736l; // 1 << 36 + +const uchar ucharTest = 8; +const ushort ushortTest = 16; +const uint uintTest = 32; +const ulong ulongTest = 4611686018427387904L; +const int64_t int64_tTest = -17179869184l; // - 1 << 34 +const uint64_t uint64_tTest = 117179869184l; + +const bool boolTest = true; + diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/struct.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/struct.rs new file mode 100644 index 000000000000..1cd728e92748 --- /dev/null +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/struct.rs @@ -0,0 +1,37 @@ +#include "shared.rsh" + +typedef struct Point2 { + int x; + int y; +} Point_2; +Point_2 *point2; + +static bool test_Point_2(int expected) { + bool failed = false; + + rsDebug("Point: ", point2[0].x, point2[0].y); + _RS_ASSERT(point2[0].x == expected); + _RS_ASSERT(point2[0].y == expected); + + if (failed) { + rsDebug("test_Point_2 FAILED", 0); + } + else { + rsDebug("test_Point_2 PASSED", 0); + } + + return failed; +} + +void struct_test(int expected) { + bool failed = false; + failed |= test_Point_2(expected); + + if (failed) { + rsSendToClientBlocking(RS_MSG_TEST_FAILED); + } + else { + rsSendToClientBlocking(RS_MSG_TEST_PASSED); + } +} + diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java index a706f781cfb1..a38ac25153cf 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java @@ -40,8 +40,11 @@ public class ProfiledWebView extends WebView { private long mContentInvalMillis; private static final int LOAD_STALL_MILLIS = 2000; // nr of millis after load, // before test is forced - private double mLoadTime; - private double mAnimationTime; + + // ignore anim end events until this many millis after load + private static final long ANIM_SAFETY_THRESHOLD = 200; + private long mLoadTime; + private long mAnimationTime; public ProfiledWebView(Context context) { super(context); @@ -131,7 +134,6 @@ public class ProfiledWebView extends WebView { // invalidate all content, and kick off redraw Log.d("ProfiledWebView", "kicking off test with callback registration, and tile discard..."); - registerPageSwapCallback(); discardAllTextures(); invalidate(); mIsScrolling = true; @@ -150,10 +152,11 @@ public class ProfiledWebView extends WebView { */ @Override protected void pageSwapCallback(boolean startAnim) { + super.pageSwapCallback(startAnim); + if (!mIsTesting && mIsScrolling) { // kick off testing mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis; - super.pageSwapCallback(startAnim); Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis + "millis"); mIsTesting = true; invalidate(); // ensure a redraw so that auto-scrolling can occur @@ -166,14 +169,14 @@ public class ProfiledWebView extends WebView { String updatesString = settings.getProperty("tree_updates"); int updates = (updatesString == null) ? -1 : Integer.parseInt(updatesString); - double animationTime; - if (mAnimationTime == 0) { + long animationTime; + if (mAnimationTime == 0 || mAnimationTime - mLoadTime < ANIM_SAFETY_THRESHOLD) { animationTime = System.currentTimeMillis() - mLoadTime; } else { animationTime = mAnimationTime - mLoadTime; } - return updates * 1000 / animationTime; + return updates * 1000.0 / animationTime; } public void setDoubleBuffering(boolean useDoubleBuffering) { diff --git a/tests/touchlag/Android.mk b/tests/touchlag/Android.mk new file mode 100644 index 000000000000..4f8aa1e60ca8 --- /dev/null +++ b/tests/touchlag/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + touchlag.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils libutils \ + +LOCAL_MODULE:= test-touchlag + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/tests/touchlag/touchlag.cpp b/tests/touchlag/touchlag.cpp new file mode 100644 index 000000000000..df4befb95fb9 --- /dev/null +++ b/tests/touchlag/touchlag.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2012 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 <stdint.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/fb.h> +#include <linux/input.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <cutils/memory.h> +#include <asm-generic/mman.h> +#include <sys/mman.h> +#include <utils/threads.h> +#include <unistd.h> +#include <math.h> + +using namespace android; + +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +#endif + +struct Buffer { + size_t w; + size_t h; + size_t s; + union { + void* addr; + uint32_t* pixels; + }; +}; + +void clearBuffer(Buffer* buf, uint32_t pixel) { + android_memset32(buf->pixels, pixel, buf->s * buf->h * 4); +} + +void drawTwoPixels(Buffer* buf, uint32_t pixel, ssize_t x, ssize_t y, size_t w) { + if (y>0 && y<ssize_t(buf->h)) { + uint32_t* bits = buf->pixels + y * buf->s; + if (x>=0 && x<buf->w) { + bits[x] = pixel; + } + ssize_t W(w); + if ((x+W)>=0 && (x+W)<buf->w) { + bits[x+W] = pixel; + } + } +} + +void drawHLine(Buffer* buf, uint32_t pixel, ssize_t x, ssize_t y, size_t w) { + if (y>0 && y<ssize_t(buf->h)) { + ssize_t W(w); + if (x<0) { + W += x; + x = 0; + } + if (x+w > buf->w) { + W = buf->w - x; + } + if (W>0) { + uint32_t* bits = buf->pixels + y * buf->s + x; + android_memset32(bits, pixel, W*4); + } + } +} + +void drawRect(Buffer* buf, uint32_t pixel, ssize_t x, ssize_t y, size_t w, size_t h) { + ssize_t W(w), H(h); + if (x<0) { + w += x; + x = 0; + } + if (y<0) { + h += y; + y = 0; + } + if (x+w > buf->w) W = buf->w - x; + if (y+h > buf->h) H = buf->h - y; + if (W>0 && H>0) { + uint32_t* bits = buf->pixels + y * buf->s + x; + for (ssize_t i=0 ; i<H ; i++) { + android_memset32(bits, pixel, W*4); + bits += buf->s; + } + } +} + +void drawCircle(Buffer* buf, uint32_t pixel, + size_t x0, size_t y0, size_t radius, bool filled = false) { + ssize_t f = 1 - radius; + ssize_t ddF_x = 1; + ssize_t ddF_y = -2 * radius; + ssize_t x = 0; + ssize_t y = radius; + if (filled) { + drawHLine(buf, pixel, x0-radius, y0, 2*radius); + } else { + drawTwoPixels(buf, pixel, x0-radius, y0, 2*radius); + } + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + if (filled) { + drawHLine(buf, pixel, x0-x, y0+y, 2*x); + drawHLine(buf, pixel, x0-x, y0-y, 2*x); + drawHLine(buf, pixel, x0-y, y0+x, 2*y); + drawHLine(buf, pixel, x0-y, y0-x, 2*y); + } else { + drawTwoPixels(buf, pixel, x0-x, y0+y, 2*x); + drawTwoPixels(buf, pixel, x0-x, y0-y, 2*x); + drawTwoPixels(buf, pixel, x0-y, y0+x, 2*y); + drawTwoPixels(buf, pixel, x0-y, y0-x, 2*y); + } + } +} + +class TouchEvents { + class EventThread : public Thread { + int fd; + + virtual bool threadLoop() { + input_event event; + int first_down = 0; + do { + read(fd, &event, sizeof(event)); + if (event.type == EV_ABS) { + if (event.code == ABS_MT_TRACKING_ID) { + down = event.value == -1 ? 0 : 1; + first_down = down; + } + if (event.code == ABS_MT_POSITION_X) { + x = event.value; + } + if (event.code == ABS_MT_POSITION_Y) { + y = event.value; + } + } + } while (event.type == EV_SYN); + return true; + } + + public: + int x, y, down; + EventThread() : Thread(false), + x(0), y(0), down(0) + { + fd = open("/dev/input/event1", O_RDONLY); + } +}; + sp<EventThread> thread; + +public: + TouchEvents() { + thread = new EventThread(); + thread->run("EventThread", PRIORITY_URGENT_DISPLAY); + } + + int getMostRecentPosition(int* x, int* y) { + *x = thread->x; + *y = thread->y; + return thread->down; + } +}; + + +struct Queue { + struct position { + int x, y; + }; + int index; + position q[16]; + Queue() : index(0) { } + void push(int x, int y) { + index++; + index &= 0xF; + q[index].x = x; + q[index].y = y; + } + void get(int lag, int* x, int* y) { + const int i = (index - lag) & 0xF; + *x = q[i].x; + *y = q[i].y; + } +}; + +extern char *optarg; +extern int optind; +extern int optopt; +extern int opterr; +extern int optreset; + +void usage(const char* name) { + printf("\nusage: %s [-h] [-l lag]\n", name); +} + +int main(int argc, char** argv) { + fb_var_screeninfo vi; + fb_fix_screeninfo fi; + + int lag = 0; + int fd = open("/dev/graphics/fb0", O_RDWR); + ioctl(fd, FBIOGET_VSCREENINFO, &vi); + ioctl(fd, FBIOGET_FSCREENINFO, &fi); + void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + Buffer framebuffer; + framebuffer.w = vi.xres; + framebuffer.h = vi.yres; + framebuffer.s = fi.line_length / (vi.bits_per_pixel >> 3); + framebuffer.addr = bits; + + int ch; + while ((ch = getopt(argc, argv, "hl:")) != -1) { + switch (ch) { + case 'l': + lag = atoi(optarg); + break; + case 'h': + default: + usage(argv[0]); + exit(0); + } + } + argc -= optind; + argv += optind; + + + TouchEvents touch; + Queue queue; + + + int x=0, y=0, down=0; + int lag_x=0, lag_y=0; + + clearBuffer(&framebuffer, 0); + while (true) { + uint32_t crt = 0; + int err = ioctl(fd, FBIO_WAITFORVSYNC, &crt); + + // draw beam marker + drawRect(&framebuffer, 0x400000, framebuffer.w-2, 0, 2, framebuffer.h); + // erase screen + if (lag) { + drawCircle(&framebuffer, 0, lag_x, lag_y, 100); + drawHLine(&framebuffer, 0, 0, lag_y, 32); + } + drawCircle(&framebuffer, 0, x, y, 100, true); + drawHLine(&framebuffer, 0, 0, y, 32); + + // draw a line at y=1000 + drawHLine(&framebuffer, 0x808080, 0, 1000, framebuffer.w); + + // get touch events + touch.getMostRecentPosition(&x, &y); + queue.push(x, y); + queue.get(lag, &lag_x, &lag_y); + + if (lag) { + drawCircle(&framebuffer, 0x00FF00, lag_x, lag_y, 100); + drawHLine(&framebuffer, 0x00FF00, 0, lag_y, 32); + } + + drawCircle(&framebuffer, 0xFFFFFF, x, y, 100, true); + drawHLine(&framebuffer, 0xFFFFFF, 0, y, 32); + + // draw end of frame beam marker + drawRect(&framebuffer, 0x004000, framebuffer.w-2, 0, 2, framebuffer.h); + } + + close(fd); + return 0; +} diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index 6ce665b467f1..ec614039c8ae 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1837,6 +1837,49 @@ String8 AaptDir::getPrintableSource() const // ========================================================================= // ========================================================================= +status_t AaptSymbols::applyJavaSymbols(const sp<AaptSymbols>& javaSymbols) +{ + status_t err = NO_ERROR; + size_t N = javaSymbols->mSymbols.size(); + for (size_t i=0; i<N; i++) { + const String8& name = javaSymbols->mSymbols.keyAt(i); + const AaptSymbolEntry& entry = javaSymbols->mSymbols.valueAt(i); + ssize_t pos = mSymbols.indexOfKey(name); + if (pos < 0) { + entry.sourcePos.error("Symbol '%s' declared with <java-symbol> not defined\n", name.string()); + err = UNKNOWN_ERROR; + continue; + } + //printf("**** setting symbol #%d/%d %s to isJavaSymbol=%d\n", + // i, N, name.string(), entry.isJavaSymbol ? 1 : 0); + mSymbols.editValueAt(pos).isJavaSymbol = entry.isJavaSymbol; + } + + N = javaSymbols->mNestedSymbols.size(); + for (size_t i=0; i<N; i++) { + const String8& name = javaSymbols->mNestedSymbols.keyAt(i); + const sp<AaptSymbols>& symbols = javaSymbols->mNestedSymbols.valueAt(i); + ssize_t pos = mNestedSymbols.indexOfKey(name); + if (pos < 0) { + SourcePos pos; + pos.error("Java symbol dir %s not defined\n", name.string()); + err = UNKNOWN_ERROR; + continue; + } + //printf("**** applying java symbols in dir %s\n", name.string()); + status_t myerr = mNestedSymbols.valueAt(pos)->applyJavaSymbols(symbols); + if (myerr != NO_ERROR) { + err = myerr; + } + } + + return err; +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + AaptAssets::AaptAssets() : AaptDir(String8(), String8()), mChanged(false), mHaveIncludedAssets(false), mRes(NULL) @@ -2404,6 +2447,48 @@ sp<AaptSymbols> AaptAssets::getSymbolsFor(const String8& name) return sym; } +sp<AaptSymbols> AaptAssets::getJavaSymbolsFor(const String8& name) +{ + sp<AaptSymbols> sym = mJavaSymbols.valueFor(name); + if (sym == NULL) { + sym = new AaptSymbols(); + mJavaSymbols.add(name, sym); + } + return sym; +} + +status_t AaptAssets::applyJavaSymbols() +{ + size_t N = mJavaSymbols.size(); + for (size_t i=0; i<N; i++) { + const String8& name = mJavaSymbols.keyAt(i); + const sp<AaptSymbols>& symbols = mJavaSymbols.valueAt(i); + ssize_t pos = mSymbols.indexOfKey(name); + if (pos < 0) { + SourcePos pos; + pos.error("Java symbol dir %s not defined\n", name.string()); + return UNKNOWN_ERROR; + } + //printf("**** applying java symbols in dir %s\n", name.string()); + status_t err = mSymbols.valueAt(pos)->applyJavaSymbols(symbols); + if (err != NO_ERROR) { + return err; + } + } + + return NO_ERROR; +} + +bool AaptAssets::isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const { + //printf("isJavaSymbol %s: public=%d, includePrivate=%d, isJavaSymbol=%d\n", + // sym.name.string(), sym.isPublic ? 1 : 0, includePrivate ? 1 : 0, + // sym.isJavaSymbol ? 1 : 0); + if (!mHavePrivateSymbols) return true; + if (sym.isPublic) return true; + if (includePrivate && sym.isJavaSymbol) return true; + return false; +} + status_t AaptAssets::buildIncludedResources(Bundle* bundle) { if (!mHaveIncludedAssets) { diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index d5345b277caf..1c653e1fb77a 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -53,17 +53,6 @@ enum { AXIS_END = AXIS_VERSION, }; -enum { - SDK_CUPCAKE = 3, - SDK_DONUT = 4, - SDK_ECLAIR = 5, - SDK_ECLAIR_0_1 = 6, - SDK_MR1 = 7, - SDK_FROYO = 8, - SDK_HONEYCOMB_MR2 = 13, - SDK_ICE_CREAM_SANDWICH = 14, -}; - /** * This structure contains a specific variation of a single file out * of all the variations it can have that we can have. @@ -326,16 +315,16 @@ class AaptSymbolEntry { public: AaptSymbolEntry() - : isPublic(false), typeCode(TYPE_UNKNOWN) + : isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN) { } AaptSymbolEntry(const String8& _name) - : name(_name), isPublic(false), typeCode(TYPE_UNKNOWN) + : name(_name), isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN) { } AaptSymbolEntry(const AaptSymbolEntry& o) : name(o.name), sourcePos(o.sourcePos), isPublic(o.isPublic) - , comment(o.comment), typeComment(o.typeComment) + , isJavaSymbol(o.isJavaSymbol), comment(o.comment), typeComment(o.typeComment) , typeCode(o.typeCode), int32Val(o.int32Val), stringVal(o.stringVal) { } @@ -343,6 +332,7 @@ public: { sourcePos = o.sourcePos; isPublic = o.isPublic; + isJavaSymbol = o.isJavaSymbol; comment = o.comment; typeComment = o.typeComment; typeCode = o.typeCode; @@ -355,6 +345,7 @@ public: SourcePos sourcePos; bool isPublic; + bool isJavaSymbol; String16 comment; String16 typeComment; @@ -412,6 +403,15 @@ public: return NO_ERROR; } + status_t makeSymbolJavaSymbol(const String8& name, const SourcePos& pos) { + if (!check_valid_symbol_name(name, pos, "symbol")) { + return BAD_VALUE; + } + AaptSymbolEntry& sym = edit_symbol(name, &pos); + sym.isJavaSymbol = true; + return NO_ERROR; + } + void appendComment(const String8& name, const String16& comment, const SourcePos& pos) { if (comment.size() <= 0) { return; @@ -452,6 +452,8 @@ public: return sym; } + status_t applyJavaSymbols(const sp<AaptSymbols>& javaSymbols); + const KeyedVector<String8, AaptSymbolEntry>& getSymbols() const { return mSymbols; } const DefaultKeyedVector<String8, sp<AaptSymbols> >& getNestedSymbols() const @@ -520,7 +522,11 @@ public: virtual ~AaptAssets() { delete mRes; } const String8& getPackage() const { return mPackage; } - void setPackage(const String8& package) { mPackage = package; mSymbolsPrivatePackage = package; } + void setPackage(const String8& package) { + mPackage = package; + mSymbolsPrivatePackage = package; + mHavePrivateSymbols = false; + } const SortedVector<AaptGroupEntry>& getGroupEntries() const; @@ -543,11 +549,22 @@ public: sp<AaptSymbols> getSymbolsFor(const String8& name); + sp<AaptSymbols> getJavaSymbolsFor(const String8& name); + + status_t applyJavaSymbols(); + const DefaultKeyedVector<String8, sp<AaptSymbols> >& getSymbols() const { return mSymbols; } String8 getSymbolsPrivatePackage() const { return mSymbolsPrivatePackage; } - void setSymbolsPrivatePackage(const String8& pkg) { mSymbolsPrivatePackage = pkg; } - + void setSymbolsPrivatePackage(const String8& pkg) { + mSymbolsPrivatePackage = pkg; + mHavePrivateSymbols = mSymbolsPrivatePackage != mPackage; + } + + bool havePrivateSymbols() const { return mHavePrivateSymbols; } + + bool isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const; + status_t buildIncludedResources(Bundle* bundle); status_t addIncludedResources(const sp<AaptFile>& file); const ResTable& getIncludedResources() const; @@ -587,7 +604,9 @@ private: String8 mPackage; SortedVector<AaptGroupEntry> mGroupEntries; DefaultKeyedVector<String8, sp<AaptSymbols> > mSymbols; + DefaultKeyedVector<String8, sp<AaptSymbols> > mJavaSymbols; String8 mSymbolsPrivatePackage; + bool mHavePrivateSymbols; Vector<sp<AaptDir> > mResDirs; diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index 2d1060ba23a0..8e3a1c9d11af 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -14,6 +14,18 @@ #include <utils/String8.h> #include <utils/Vector.h> +enum { + SDK_CUPCAKE = 3, + SDK_DONUT = 4, + SDK_ECLAIR = 5, + SDK_ECLAIR_0_1 = 6, + SDK_MR1 = 7, + SDK_FROYO = 8, + SDK_HONEYCOMB_MR2 = 13, + SDK_ICE_CREAM_SANDWICH = 14, + SDK_ICE_CREAM_SANDWICH_MR1 = 15, +}; + /* * Things we can do. */ @@ -82,7 +94,6 @@ public: void setRequireLocalization(bool val) { mRequireLocalization = val; } bool getPseudolocalize(void) const { return mPseudolocalize; } void setPseudolocalize(bool val) { mPseudolocalize = val; } - bool getWantUTF16(void) const { return mWantUTF16; } void setWantUTF16(bool val) { mWantUTF16 = val; } bool getValues(void) const { return mValues; } void setValues(bool val) { mValues = val; } @@ -103,6 +114,10 @@ public: bool getGenDependencies() { return mGenDependencies; } void setGenDependencies(bool val) { mGenDependencies = val; } + bool getUTF16StringsOption() { + return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO); + } + /* * Input options. */ diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 89942de57eaa..c79e243828c4 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -479,6 +479,11 @@ int doDump(Bundle* bundle) #ifndef HAVE_ANDROID_OS res.print(bundle->getValues()); #endif + + } else if (strcmp("strings", option) == 0) { + const ResStringPool* pool = res.getTableStringBlock(0); + printStringPool(pool); + } else if (strcmp("xmltree", option) == 0) { if (bundle->getFileSpecCount() < 3) { fprintf(stderr, "ERROR: no dump xmltree resource file specified\n"); @@ -1382,7 +1387,7 @@ int doDump(Bundle* bundle) delete dir; } } else if (strcmp("badger", option) == 0) { - printf(CONSOLE_DATA); + printf("%s", CONSOLE_DATA); } else if (strcmp("configurations", option) == 0) { Vector<ResTable_config> configs; res.getConfigurations(&configs); @@ -1612,6 +1617,12 @@ int doPackage(Bundle* bundle) goto bail; } + // Update symbols with information about which ones are needed as Java symbols. + assets->applyJavaSymbols(); + if (SourcePos::hasErrors()) { + goto bail; + } + // If we've been asked to generate a dependency file, do that here if (bundle->getGenDependencies()) { // If this is the packaging step, generate the dependency file next to @@ -1633,7 +1644,7 @@ int doPackage(Bundle* bundle) } // Write out R.java constants - if (assets->getPackage() == assets->getSymbolsPrivatePackage()) { + if (!assets->havePrivateSymbols()) { if (bundle->getCustomPackage() == NULL) { // Write the R.java file into the appropriate class directory // e.g. gen/com/foo/app/R.java diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 1ecf7daef123..7eaf5280f5d4 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -847,8 +847,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) * request UTF-16 encoding and the parameters of this package * allow UTF-8 to be used. */ - if (!bundle->getWantUTF16() - && bundle->isMinSdkAtLeast(SDK_FROYO)) { + if (!bundle->getUTF16StringsOption()) { xmlFlags |= XML_COMPILE_UTF8; } @@ -1809,7 +1808,7 @@ static status_t writeSymbolClass( if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) { continue; } - if (!includePrivate && !sym.isPublic) { + if (!assets->isJavaSymbol(sym, includePrivate)) { continue; } String16 name(sym.name); @@ -1865,7 +1864,7 @@ static status_t writeSymbolClass( if (sym.typeCode != AaptSymbolEntry::TYPE_STRING) { continue; } - if (!includePrivate && !sym.isPublic) { + if (!assets->isJavaSymbol(sym, includePrivate)) { continue; } String16 name(sym.name); @@ -1977,7 +1976,8 @@ status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, "\n" "package %s;\n\n", package.string()); - status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, className, 0, bundle->getNonConstantId()); + status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, + className, 0, bundle->getNonConstantId()); if (err != NO_ERROR) { return err; } diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index fdb39ca08e9f..7a0499c476d7 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -753,6 +753,7 @@ status_t compileResourceFile(Bundle* bundle, const String16 public16("public"); const String16 public_padding16("public-padding"); const String16 private_symbols16("private-symbols"); + const String16 java_symbol16("java-symbol"); const String16 add_resource16("add-resource"); const String16 skip16("skip"); const String16 eat_comment16("eat-comment"); @@ -1058,6 +1059,49 @@ status_t compileResourceFile(Bundle* bundle, } continue; + } else if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) { + SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); + + String16 type; + ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); + if (typeIdx < 0) { + srcPos.error("A 'type' attribute is required for <public>\n"); + hasErrors = localHasErrors = true; + } + type = String16(block.getAttributeStringValue(typeIdx, &len)); + + String16 name; + ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); + if (nameIdx < 0) { + srcPos.error("A 'name' attribute is required for <public>\n"); + hasErrors = localHasErrors = true; + } + name = String16(block.getAttributeStringValue(nameIdx, &len)); + + sp<AaptSymbols> symbols = assets->getJavaSymbolsFor(String8("R")); + if (symbols != NULL) { + symbols = symbols->addNestedSymbol(String8(type), srcPos); + } + if (symbols != NULL) { + symbols->makeSymbolJavaSymbol(String8(name), srcPos); + String16 comment( + block.getComment(&len) ? block.getComment(&len) : nulStr); + symbols->appendComment(String8(name), comment, srcPos); + } else { + srcPos.error("Unable to create symbols!\n"); + hasErrors = localHasErrors = true; + } + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) { + break; + } + } + } + continue; + + } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) { SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); @@ -2047,7 +2091,8 @@ bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool, uint32_t attrID, const Vector<StringPool::entry_style_span>* style, String16* outStr, void* accessorCookie, - uint32_t attrType) + uint32_t attrType, const String8* configTypeName, + const ConfigDescription* config) { String16 finalStr; @@ -2075,10 +2120,19 @@ bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool, if (outValue->dataType == outValue->TYPE_STRING) { // Should do better merging styles. if (pool) { + String8 configStr; + if (config != NULL) { + configStr = config->toString(); + } else { + configStr = "(null)"; + } + NOISY(printf("Adding to pool string style #%d config %s: %s\n", + style != NULL ? style->size() : 0, + configStr.string(), String8(finalStr).string())); if (style != NULL && style->size() > 0) { - outValue->data = pool->add(finalStr, *style); + outValue->data = pool->add(finalStr, *style, configTypeName, config); } else { - outValue->data = pool->add(finalStr, true); + outValue->data = pool->add(finalStr, true, configTypeName, config); } } else { // Caller will fill this in later. @@ -2537,16 +2591,19 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) return err; } + const ConfigDescription nullConfig; + const size_t N = mOrderedPackages.size(); size_t pi; const static String16 mipmap16("mipmap"); - bool useUTF8 = !bundle->getWantUTF16() && bundle->isMinSdkAtLeast(SDK_FROYO); + bool useUTF8 = !bundle->getUTF16StringsOption(); // Iterate through all data, collecting all values (strings, // references, etc). StringPool valueStrings = StringPool(false, useUTF8); + Vector<sp<Entry> > allEntries; for (pi=0; pi<N; pi++) { sp<Package> p = mOrderedPackages.itemAt(pi); if (p->getTypes().size() == 0) { @@ -2567,6 +2624,19 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) const String16 typeName(t->getName()); typeStrings.add(typeName, false); + // This is a hack to tweak the sorting order of the final strings, + // to put stuff that is generally not language-specific first. + String8 configTypeName(typeName); + if (configTypeName == "drawable" || configTypeName == "layout" + || configTypeName == "color" || configTypeName == "anim" + || configTypeName == "interpolator" || configTypeName == "animator" + || configTypeName == "xml" || configTypeName == "menu" + || configTypeName == "mipmap" || configTypeName == "raw") { + configTypeName = "1complex"; + } else { + configTypeName = "2value"; + } + const bool filterable = (typeName != mipmap16); const size_t N = t->getOrderedConfigs().size(); @@ -2586,10 +2656,21 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) continue; } e->setNameIndex(keyStrings.add(e->getName(), true)); - status_t err = e->prepareFlatten(&valueStrings, this); + + // If this entry has no values for other configs, + // and is the default config, then it is special. Otherwise + // we want to add it with the config info. + ConfigDescription* valueConfig = NULL; + if (N != 1 || config == nullConfig) { + valueConfig = &config; + } + + status_t err = e->prepareFlatten(&valueStrings, this, + &configTypeName, &config); if (err != NO_ERROR) { return err; } + allEntries.add(e); } } } @@ -2598,6 +2679,17 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) p->setKeyStrings(keyStrings.createStringBlock()); } + if (bundle->getOutputAPKFile() != NULL) { + // Now we want to sort the value strings for better locality. This will + // cause the positions of the strings to change, so we need to go back + // through out resource entries and update them accordingly. Only need + // to do this if actually writing the output file. + valueStrings.sortByConfig(); + for (pi=0; pi<allEntries.size(); pi++) { + allEntries[pi]->remapStringValue(&valueStrings); + } + } + ssize_t strAmt = 0; // Now build the array of package chunks. @@ -3137,14 +3229,16 @@ status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table, return hasErrors ? UNKNOWN_ERROR : NO_ERROR; } -status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table) +status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table, + const String8* configTypeName, const ConfigDescription* config) { if (mType == TYPE_ITEM) { Item& it = mItem; AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value)); if (!table->stringToValue(&it.parsedValue, strings, it.value, false, true, 0, - &it.style, NULL, &ac, mItemFormat)) { + &it.style, NULL, &ac, mItemFormat, + configTypeName, config)) { return UNKNOWN_ERROR; } } else if (mType == TYPE_BAG) { @@ -3155,7 +3249,8 @@ status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable AccessorCookie ac(it.sourcePos, String8(key), String8(it.value)); if (!table->stringToValue(&it.parsedValue, strings, it.value, false, true, it.bagKeyId, - &it.style, NULL, &ac, it.format)) { + &it.style, NULL, &ac, it.format, + configTypeName, config)) { return UNKNOWN_ERROR; } } @@ -3167,6 +3262,29 @@ status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable return NO_ERROR; } +status_t ResourceTable::Entry::remapStringValue(StringPool* strings) +{ + if (mType == TYPE_ITEM) { + Item& it = mItem; + if (it.parsedValue.dataType == Res_value::TYPE_STRING) { + it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data); + } + } else if (mType == TYPE_BAG) { + const size_t N = mBag.size(); + for (size_t i=0; i<N; i++) { + Item& it = mBag.editValueAt(i); + if (it.parsedValue.dataType == Res_value::TYPE_STRING) { + it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data); + } + } + } else { + mPos.error("Error: entry %s is not a single item or a bag.\n", + String8(mName).string()); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + ssize_t ResourceTable::Entry::flatten(Bundle* bundle, const sp<AaptFile>& data, bool isPublic) { size_t amt = 0; diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index 8123bb3d4226..a3e066690fba 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -76,6 +76,37 @@ public: class Type; class Entry; + struct ConfigDescription : public ResTable_config { + ConfigDescription() { + memset(this, 0, sizeof(*this)); + size = sizeof(ResTable_config); + } + ConfigDescription(const ResTable_config&o) { + *static_cast<ResTable_config*>(this) = o; + size = sizeof(ResTable_config); + } + ConfigDescription(const ConfigDescription&o) { + *static_cast<ResTable_config*>(this) = o; + } + + ConfigDescription& operator=(const ResTable_config& o) { + *static_cast<ResTable_config*>(this) = o; + size = sizeof(ResTable_config); + return *this; + } + ConfigDescription& operator=(const ConfigDescription& o) { + *static_cast<ResTable_config*>(this) = o; + return *this; + } + + inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } + inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } + inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } + inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } + inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } + inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } + }; + ResourceTable(Bundle* bundle, const String16& assetsPackage); status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets); @@ -183,7 +214,9 @@ public: uint32_t attrID, const Vector<StringPool::entry_style_span>* style = NULL, String16* outStr = NULL, void* accessorCookie = NULL, - uint32_t attrType = ResTable_map::TYPE_ANY); + uint32_t attrType = ResTable_map::TYPE_ANY, + const String8* configTypeName = NULL, + const ConfigDescription* config = NULL); status_t assignResourceIds(); status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL); @@ -305,7 +338,10 @@ public: status_t assignResourceIds(ResourceTable* table, const String16& package); - status_t prepareFlatten(StringPool* strings, ResourceTable* table); + status_t prepareFlatten(StringPool* strings, ResourceTable* table, + const String8* configTypeName, const ConfigDescription* config); + + status_t remapStringValue(StringPool* strings); ssize_t flatten(Bundle*, const sp<AaptFile>& data, bool isPublic); @@ -322,37 +358,6 @@ public: uint32_t mParentId; SourcePos mPos; }; - - struct ConfigDescription : public ResTable_config { - ConfigDescription() { - memset(this, 0, sizeof(*this)); - size = sizeof(ResTable_config); - } - ConfigDescription(const ResTable_config&o) { - *static_cast<ResTable_config*>(this) = o; - size = sizeof(ResTable_config); - } - ConfigDescription(const ConfigDescription&o) { - *static_cast<ResTable_config*>(this) = o; - } - - ConfigDescription& operator=(const ResTable_config& o) { - *static_cast<ResTable_config*>(this) = o; - size = sizeof(ResTable_config); - return *this; - } - ConfigDescription& operator=(const ConfigDescription& o) { - *static_cast<ResTable_config*>(this) = o; - return *this; - } - - inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } - inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } - inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } - inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } - inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } - inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } - }; class ConfigList : public RefBase { public: diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp index 9a0a1c46251b..fe88e37c159f 100644 --- a/tools/aapt/StringPool.cpp +++ b/tools/aapt/StringPool.cpp @@ -5,8 +5,10 @@ // #include "StringPool.h" +#include "ResourceTable.h" #include <utils/ByteOrder.h> +#include <utils/SortedVector.h> #if HAVE_PRINTF_ZD # define ZD "%zd" @@ -30,16 +32,70 @@ void strcpy16_htod(uint16_t* dst, const uint16_t* src) void printStringPool(const ResStringPool* pool) { + SortedVector<const void*> uniqueStrings; + const size_t N = pool->size(); + for (size_t i=0; i<N; i++) { + size_t len; + if (pool->isUTF8()) { + uniqueStrings.add(pool->string8At(i, &len)); + } else { + uniqueStrings.add(pool->stringAt(i, &len)); + } + } + + printf("String pool of " ZD " unique %s %s strings, " ZD " entries and " + ZD " styles using " ZD " bytes:\n", + (ZD_TYPE)uniqueStrings.size(), pool->isUTF8() ? "UTF-8" : "UTF-16", + pool->isSorted() ? "sorted" : "non-sorted", + (ZD_TYPE)N, (ZD_TYPE)pool->styleCount(), (ZD_TYPE)pool->bytes()); + const size_t NS = pool->size(); for (size_t s=0; s<NS; s++) { - size_t len; - const char *str = (const char*)pool->string8At(s, &len); - if (str == NULL) { - str = String8(pool->stringAt(s, &len)).string(); + String8 str = pool->string8ObjectAt(s); + printf("String #" ZD ": %s\n", (ZD_TYPE) s, str.string()); + } +} + +String8 StringPool::entry::makeConfigsString() const { + String8 configStr(configTypeName); + if (configStr.size() > 0) configStr.append(" "); + if (configs.size() > 0) { + for (size_t j=0; j<configs.size(); j++) { + if (j > 0) configStr.append(", "); + configStr.append(configs[j].toString()); } + } else { + configStr = "(none)"; + } + return configStr; +} - printf("String #" ZD ": %s\n", (ZD_TYPE) s, str); +int StringPool::entry::compare(const entry& o) const { + // Strings with styles go first, to reduce the size of the + // styles array. + if (hasStyles) { + return o.hasStyles ? 0 : -1; + } + if (o.hasStyles) { + return 1; + } + int comp = configTypeName.compare(o.configTypeName); + if (comp != 0) { + return comp; } + const size_t LHN = configs.size(); + const size_t RHN = o.configs.size(); + size_t i=0; + while (i < LHN && i < RHN) { + comp = configs[i].compareLogical(o.configs[i]); + if (comp != 0) { + return comp; + } + i++; + } + if (LHN < RHN) return -1; + else if (LHN > RHN) return 1; + return 0; } StringPool::StringPool(bool sorted, bool utf8) @@ -47,14 +103,16 @@ StringPool::StringPool(bool sorted, bool utf8) { } -ssize_t StringPool::add(const String16& value, bool mergeDuplicates) +ssize_t StringPool::add(const String16& value, bool mergeDuplicates, + const String8* configTypeName, const ResTable_config* config) { - return add(String16(), value, mergeDuplicates); + return add(String16(), value, mergeDuplicates, configTypeName, config); } -ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans) +ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans, + const String8* configTypeName, const ResTable_config* config) { - ssize_t res = add(String16(), value, false); + ssize_t res = add(String16(), value, false, configTypeName, config); if (res >= 0) { addStyleSpans(res, spans); } @@ -62,7 +120,7 @@ ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& s } ssize_t StringPool::add(const String16& ident, const String16& value, - bool mergeDuplicates) + bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config) { if (ident.size() > 0) { ssize_t idx = mIdents.valueFor(ident); @@ -84,20 +142,43 @@ ssize_t StringPool::add(const String16& ident, const String16& value, } } + if (configTypeName != NULL) { + entry& ent = mEntries.editItemAt(eidx); + NOISY(printf("*** adding config type name %s, was %s\n", + configTypeName->string(), ent.configTypeName.string())); + if (ent.configTypeName.size() <= 0) { + ent.configTypeName = *configTypeName; + } else if (ent.configTypeName != *configTypeName) { + ent.configTypeName = " "; + } + } + + if (config != NULL) { + // Add this to the set of configs associated with the string. + entry& ent = mEntries.editItemAt(eidx); + size_t addPos; + for (addPos=0; addPos<ent.configs.size(); addPos++) { + int cmp = ent.configs.itemAt(addPos).compareLogical(*config); + if (cmp >= 0) { + if (cmp > 0) { + NOISY(printf("*** inserting config: %s\n", config->toString().string())); + ent.configs.insertAt(*config, addPos); + } + break; + } + } + if (addPos >= ent.configs.size()) { + NOISY(printf("*** adding config: %s\n", config->toString().string())); + ent.configs.add(*config); + } + } + const bool first = vidx < 0; if (first || !mergeDuplicates) { pos = mEntryArray.add(eidx); if (first) { vidx = mValues.add(value, pos); - const size_t N = mEntryArrayToValues.size(); - for (size_t i=0; i<N; i++) { - size_t& e = mEntryArrayToValues.editItemAt(i); - if ((ssize_t)e >= vidx) { - e++; - } - } } - mEntryArrayToValues.add(vidx); if (!mSorted) { entry& ent = mEntries.editItemAt(eidx); ent.indices.add(pos); @@ -147,6 +228,7 @@ status_t StringPool::addStyleSpan(size_t idx, const entry_style_span& span) entry_style& style = mEntryStyleArray.editItemAt(idx); style.spans.add(span); + mEntries.editItemAt(mEntryArray[idx]).hasStyles = true; return NO_ERROR; } @@ -169,6 +251,132 @@ size_t StringPool::countIdentifiers() const return mIdents.size(); } +int StringPool::config_sort(const size_t* lhs, const size_t* rhs, void* state) +{ + StringPool* pool = (StringPool*)state; + const entry& lhe = pool->mEntries[pool->mEntryArray[*lhs]]; + const entry& rhe = pool->mEntries[pool->mEntryArray[*rhs]]; + return lhe.compare(rhe); +} + +void StringPool::sortByConfig() +{ + LOG_ALWAYS_FATAL_IF(mSorted, "Can't sort string pool containing identifiers."); + LOG_ALWAYS_FATAL_IF(mIdents.size() > 0, "Can't sort string pool containing identifiers."); + LOG_ALWAYS_FATAL_IF(mOriginalPosToNewPos.size() > 0, "Can't sort string pool after already sorted."); + + const size_t N = mEntryArray.size(); + + // This is a vector that starts out with a 1:1 mapping to entries + // in the array, which we will sort to come up with the desired order. + // At that point it maps from the new position in the array to the + // original position the entry appeared. + Vector<size_t> newPosToOriginalPos; + for (size_t i=0; i<mEntryArray.size(); i++) { + newPosToOriginalPos.add(i); + } + + // Sort the array. + NOISY(printf("SORTING STRINGS BY CONFIGURATION...\n")); + newPosToOriginalPos.sort(config_sort, this); + NOISY(printf("DONE SORTING STRINGS BY CONFIGURATION.\n")); + + // Create the reverse mapping from the original position in the array + // to the new position where it appears in the sorted array. This is + // so that clients can re-map any positions they had previously stored. + mOriginalPosToNewPos = newPosToOriginalPos; + for (size_t i=0; i<N; i++) { + mOriginalPosToNewPos.editItemAt(newPosToOriginalPos[i]) = i; + } + +#if 0 + SortedVector<entry> entries; + + for (size_t i=0; i<N; i++) { + printf("#%d was %d: %s\n", i, newPosToOriginalPos[i], + mEntries[mEntryArray[newPosToOriginalPos[i]]].makeConfigsString().string()); + entries.add(mEntries[mEntryArray[i]]); + } + + for (size_t i=0; i<entries.size(); i++) { + printf("Sorted config #%d: %s\n", i, + entries[i].makeConfigsString().string()); + } +#endif + + // Now we rebuild the arrays. + Vector<entry> newEntries; + Vector<size_t> newEntryArray; + Vector<entry_style> newEntryStyleArray; + DefaultKeyedVector<size_t, size_t> origOffsetToNewOffset; + + for (size_t i=0; i<N; i++) { + // We are filling in new offset 'i'; oldI is where we can find it + // in the original data structure. + size_t oldI = newPosToOriginalPos[i]; + // This is the actual entry associated with the old offset. + const entry& oldEnt = mEntries[mEntryArray[oldI]]; + // This is the same entry the last time we added it to the + // new entry array, if any. + ssize_t newIndexOfOffset = origOffsetToNewOffset.indexOfKey(oldI); + size_t newOffset; + if (newIndexOfOffset < 0) { + // This is the first time we have seen the entry, so add + // it. + newOffset = newEntries.add(oldEnt); + newEntries.editItemAt(newOffset).indices.clear(); + } else { + // We have seen this entry before, use the existing one + // instead of adding it again. + newOffset = origOffsetToNewOffset.valueAt(newIndexOfOffset); + } + // Update the indices to include this new position. + newEntries.editItemAt(newOffset).indices.add(i); + // And add the offset of the entry to the new entry array. + newEntryArray.add(newOffset); + // Add any old style to the new style array. + if (mEntryStyleArray.size() > 0) { + if (oldI < mEntryStyleArray.size()) { + newEntryStyleArray.add(mEntryStyleArray[oldI]); + } else { + newEntryStyleArray.add(entry_style()); + } + } + } + + // Now trim any entries at the end of the new style array that are + // not needed. + for (ssize_t i=newEntryStyleArray.size()-1; i>=0; i--) { + const entry_style& style = newEntryStyleArray[i]; + if (style.spans.size() > 0) { + // That's it. + break; + } + // This one is not needed; remove. + newEntryStyleArray.removeAt(i); + } + + // All done, install the new data structures and upate mValues with + // the new positions. + mEntries = newEntries; + mEntryArray = newEntryArray; + mEntryStyleArray = newEntryStyleArray; + mValues.clear(); + for (size_t i=0; i<mEntries.size(); i++) { + const entry& ent = mEntries[i]; + mValues.add(ent.value, ent.indices[0]); + } + +#if 0 + printf("FINAL SORTED STRING CONFIGS:\n"); + for (size_t i=0; i<mEntries.size(); i++) { + const entry& ent = mEntries[i]; + printf("#" ZD " %s: %s\n", (ZD_TYPE)i, ent.makeConfigsString().string(), + String8(ent.value).string()); + } +#endif +} + sp<AaptFile> StringPool::createStringBlock() { sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(), diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h index 727525976cd9..255bdbfc0a7f 100644 --- a/tools/aapt/StringPool.h +++ b/tools/aapt/StringPool.h @@ -40,12 +40,28 @@ class StringPool public: struct entry { entry() : offset(0) { } - entry(const String16& _value) : value(_value), offset(0) { } - entry(const entry& o) : value(o.value), offset(o.offset), indices(o.indices) { } + entry(const String16& _value) : value(_value), offset(0), hasStyles(false) { } + entry(const entry& o) : value(o.value), offset(o.offset), + hasStyles(o.hasStyles), indices(o.indices), + configTypeName(o.configTypeName), configs(o.configs) { } String16 value; size_t offset; + bool hasStyles; Vector<size_t> indices; + String8 configTypeName; + Vector<ResTable_config> configs; + + String8 makeConfigsString() const; + + int compare(const entry& o) const; + + inline bool operator<(const entry& o) const { return compare(o) < 0; } + inline bool operator<=(const entry& o) const { return compare(o) <= 0; } + inline bool operator==(const entry& o) const { return compare(o) == 0; } + inline bool operator!=(const entry& o) const { return compare(o) != 0; } + inline bool operator>=(const entry& o) const { return compare(o) >= 0; } + inline bool operator>(const entry& o) const { return compare(o) > 0; } }; struct entry_style_span { @@ -84,12 +100,15 @@ public: * if this string pool is sorted, the returned index will not be valid * when the pool is finally written. */ - ssize_t add(const String16& value, bool mergeDuplicates = false); + ssize_t add(const String16& value, bool mergeDuplicates = false, + const String8* configTypeName = NULL, const ResTable_config* config = NULL); - ssize_t add(const String16& value, const Vector<entry_style_span>& spans); + ssize_t add(const String16& value, const Vector<entry_style_span>& spans, + const String8* configTypeName = NULL, const ResTable_config* config = NULL); ssize_t add(const String16& ident, const String16& value, - bool mergeDuplicates = false); + bool mergeDuplicates = false, + const String8* configTypeName = NULL, const ResTable_config* config = NULL); status_t addStyleSpan(size_t idx, const String16& name, uint32_t start, uint32_t end); @@ -102,6 +121,18 @@ public: size_t countIdentifiers() const; + // Sort the contents of the string block by the configuration associated + // with each item. After doing this you can use mapOriginalPosToNewPos() + // to find out the new position given the position originall returned by + // add(). + void sortByConfig(); + + // For use after sortByConfig() to map from the original position of + // a string to its new sorted position. + size_t mapOriginalPosToNewPos(size_t originalPos) const { + return mOriginalPosToNewPos.itemAt(originalPos); + } + sp<AaptFile> createStringBlock(); status_t writeStringBlock(const sp<AaptFile>& pool); @@ -125,27 +156,41 @@ public: const Vector<size_t>* offsetsForString(const String16& val) const; private: + static int config_sort(const size_t* lhs, const size_t* rhs, void* state); + const bool mSorted; const bool mUTF8; - // Raw array of unique strings, in some arbitrary order. + + // The following data structures represent the actual structures + // that will be generated for the final string pool. + + // Raw array of unique strings, in some arbitrary order. This is the + // actual strings that appear in the final string pool, in the order + // that they will be written. Vector<entry> mEntries; // Array of indices into mEntries, in the order they were // added to the pool. This can be different than mEntries // if the same string was added multiple times (it will appear // once in mEntries, with multiple occurrences in this array). + // This is the lookup array that will be written for finding + // the string for each offset/position in the string pool. Vector<size_t> mEntryArray; // Optional style span information associated with each index of // mEntryArray. Vector<entry_style> mEntryStyleArray; - // Mapping from indices in mEntryArray to indices in mValues. - Vector<size_t> mEntryArrayToValues; + + // The following data structures are used for book-keeping as the + // string pool is constructed. + // Unique set of all the strings added to the pool, mapped to // the first index of mEntryArray where the value was added. DefaultKeyedVector<String16, ssize_t> mValues; // Unique set of all (optional) identifiers of strings in the // pool, mapping to indices in mEntries. DefaultKeyedVector<String16, ssize_t> mIdents; - + // This array maps from the original position a string was placed at + // in mEntryArray to its new position after being sorted with sortByConfig(). + Vector<size_t> mOriginalPosToNewPos; }; #endif diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index c0d74275a8a6..95a68d174580 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -45,6 +45,7 @@ bool isWhitespace(const char16_t* str) static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE); static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE); +static const String16 RESOURCES_TOOLS_NAMESPACE("http://schemas.android.com/tools"); String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic) { @@ -761,13 +762,16 @@ status_t XMLNode::addAttribute(const String16& ns, const String16& name, SourcePos(mFilename, getStartLineNumber()).error("Child to CDATA node."); return UNKNOWN_ERROR; } - attribute_entry e; - e.index = mNextAttributeIndex++; - e.ns = ns; - e.name = name; - e.string = value; - mAttributes.add(e); - mAttributeOrder.add(e.index, mAttributes.size()-1); + + if (ns != RESOURCES_TOOLS_NAMESPACE) { + attribute_entry e; + e.index = mNextAttributeIndex++; + e.ns = ns; + e.name = name; + e.string = value; + mAttributes.add(e); + mAttributeOrder.add(e.index, mAttributes.size()-1); + } return NO_ERROR; } @@ -1215,11 +1219,13 @@ status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds, collect_attr_strings(dest, outResIds, true); int i; - if (mNamespacePrefix.size() > 0) { - dest->add(mNamespacePrefix, true); - } - if (mNamespaceUri.size() > 0) { - dest->add(mNamespaceUri, true); + if (RESOURCES_TOOLS_NAMESPACE != mNamespaceUri) { + if (mNamespacePrefix.size() > 0) { + dest->add(mNamespacePrefix, true); + } + if (mNamespaceUri.size() > 0) { + dest->add(mNamespaceUri, true); + } } if (mElementName.size() > 0) { dest->add(mElementName, true); @@ -1338,6 +1344,7 @@ status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& de const void* extData = NULL; size_t extSize = 0; ResXMLTree_attribute attr; + bool writeCurrentNode = true; const size_t NA = mAttributes.size(); const size_t NC = mChildren.size(); @@ -1350,7 +1357,7 @@ status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& de const String16 style16("style"); const type type = getType(); - + memset(&node, 0, sizeof(node)); memset(&attr, 0, sizeof(attr)); node.header.headerSize = htods(sizeof(node)); @@ -1395,17 +1402,21 @@ status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& de } } } else if (type == TYPE_NAMESPACE) { - node.header.type = htods(RES_XML_START_NAMESPACE_TYPE); - extData = &namespaceExt; - extSize = sizeof(namespaceExt); - memset(&namespaceExt, 0, sizeof(namespaceExt)); - if (mNamespacePrefix.size() > 0) { - namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix)); + if (mNamespaceUri == RESOURCES_TOOLS_NAMESPACE) { + writeCurrentNode = false; } else { - namespaceExt.prefix.index = htodl((uint32_t)-1); + node.header.type = htods(RES_XML_START_NAMESPACE_TYPE); + extData = &namespaceExt; + extSize = sizeof(namespaceExt); + memset(&namespaceExt, 0, sizeof(namespaceExt)); + if (mNamespacePrefix.size() > 0) { + namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix)); + } else { + namespaceExt.prefix.index = htodl((uint32_t)-1); + } + namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix)); + namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri)); } - namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix)); - namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri)); LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!"); } else if (type == TYPE_CDATA) { node.header.type = htods(RES_XML_CDATA_TYPE); @@ -1422,9 +1433,11 @@ status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& de node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA)); - dest->writeData(&node, sizeof(node)); - if (extSize > 0) { - dest->writeData(extData, extSize); + if (writeCurrentNode) { + dest->writeData(&node, sizeof(node)); + if (extSize > 0) { + dest->writeData(extData, extSize); + } } for (i=0; i<NA; i++) { @@ -1476,12 +1489,14 @@ status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& de dest->writeData(&node, sizeof(node)); dest->writeData(&endElementExt, sizeof(endElementExt)); } else if (type == TYPE_NAMESPACE) { - node.header.type = htods(RES_XML_END_NAMESPACE_TYPE); - node.lineNumber = htodl(getEndLineNumber()); - node.comment.index = htodl((uint32_t)-1); - node.header.size = htodl(sizeof(node)+extSize); - dest->writeData(&node, sizeof(node)); - dest->writeData(extData, extSize); + if (writeCurrentNode) { + node.header.type = htods(RES_XML_END_NAMESPACE_TYPE); + node.lineNumber = htodl(getEndLineNumber()); + node.comment.index = htodl((uint32_t)-1); + node.header.size = htodl(sizeof(node)+extSize); + dest->writeData(&node, sizeof(node)); + dest->writeData(extData, extSize); + } } return NO_ERROR; diff --git a/tools/layoutlib/bridge/.settings/README.txt b/tools/layoutlib/bridge/.settings/README.txt new file mode 100644 index 000000000000..9120b20710a3 --- /dev/null +++ b/tools/layoutlib/bridge/.settings/README.txt @@ -0,0 +1,2 @@ +Copy this in eclipse project as a .settings folder at the root. +This ensure proper compilation compliance and warning/error levels.
\ No newline at end of file diff --git a/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs b/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000000..5381a0e16c7d --- /dev/null +++ b/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,93 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled +org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/tools/layoutlib/bridge/resources/bars/action_bar.xml b/tools/layoutlib/bridge/resources/bars/action_bar.xml index 51983f2d3ece..7adc5af44b75 100644 --- a/tools/layoutlib/bridge/resources/bars/action_bar.xml +++ b/tools/layoutlib/bridge/resources/bars/action_bar.xml @@ -1,9 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <ImageView - android:layout_height="wrap_content" - android:layout_width="wrap_content"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> + <include layout="@android:layout/action_bar_home" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> </merge> diff --git a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java index af83c61b2241..b46134ad4aa1 100644 --- a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java +++ b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java @@ -23,11 +23,10 @@ import com.android.ide.common.rendering.api.Result.Status; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.RenderSessionImpl; -import android.animation.ValueAnimator; import android.os.Handler; import android.os.Handler_Delegate; -import android.os.Message; import android.os.Handler_Delegate.IHandlerCallback; +import android.os.Message; import java.util.PriorityQueue; import java.util.Queue; @@ -57,6 +56,7 @@ public abstract class AnimationThread extends Thread { mUptimeMillis = uptimeMillis; } + @Override public int compareTo(MessageBundle bundle) { if (mUptimeMillis < bundle.mUptimeMillis) { return -1; @@ -85,6 +85,7 @@ public abstract class AnimationThread extends Thread { Bridge.prepareThread(); try { Handler_Delegate.setCallback(new IHandlerCallback() { + @Override public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { if (msg.what == ValueAnimator.ANIMATION_START /*|| FIXME: The ANIMATION_FRAME message no longer exists. Instead, diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java index 9a8cf0462131..65a75b0ab25d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java @@ -105,6 +105,7 @@ public class BitmapShader_Delegate extends Shader_Delegate { mTileModeY = tileModeY; } + @Override public java.awt.PaintContext createContext( java.awt.image.ColorModel colorModel, java.awt.Rectangle deviceBounds, @@ -148,13 +149,16 @@ public class BitmapShader_Delegate extends Shader_Delegate { mColorModel = colorModel; } + @Override public void dispose() { } + @Override public java.awt.image.ColorModel getColorModel() { return mColorModel; } + @Override public java.awt.image.Raster getRaster(int x, int y, int w, int h) { java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, java.awt.image.BufferedImage.TYPE_INT_ARGB); @@ -240,6 +244,7 @@ public class BitmapShader_Delegate extends Shader_Delegate { } + @Override public int getTransparency() { return java.awt.Paint.TRANSLUCENT; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index f7978366967d..16f15757f072 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -291,6 +291,7 @@ public final class Canvas_Delegate { Paint paint) { draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { for (int i = 0 ; i < count ; i += 4) { graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], @@ -619,6 +620,7 @@ public final class Canvas_Delegate { final int h = canvasDelegate.mBitmap.getImage().getHeight(); draw(nativeCanvas, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paint) { // reset its transform just in case graphics.setTransform(new AffineTransform()); @@ -651,6 +653,7 @@ public final class Canvas_Delegate { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); } @@ -669,6 +672,7 @@ public final class Canvas_Delegate { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { int style = paintDelegate.getStyle(); @@ -693,6 +697,7 @@ public final class Canvas_Delegate { if (oval.right > oval.left && oval.bottom > oval.top) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { int style = paintDelegate.getStyle(); @@ -728,6 +733,7 @@ public final class Canvas_Delegate { if (oval.right > oval.left && oval.bottom > oval.top) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { int style = paintDelegate.getStyle(); @@ -757,6 +763,7 @@ public final class Canvas_Delegate { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { int style = paintDelegate.getStyle(); @@ -789,6 +796,7 @@ public final class Canvas_Delegate { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { Shape shape = pathDelegate.getJavaShape(); int style = paintDelegate.getStyle(); @@ -892,6 +900,7 @@ public final class Canvas_Delegate { draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paint) { if (paint != null && paint.isFilterBitmap()) { graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, @@ -931,6 +940,7 @@ public final class Canvas_Delegate { final AffineTransform mtx = matrixDelegate.getAffineTransform(); canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paint) { if (paint != null && paint.isFilterBitmap()) { graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, @@ -970,6 +980,7 @@ public final class Canvas_Delegate { final float startX, final float startY, int flags, int paint) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { // WARNING: the logic in this method is similar to Paint_Delegate.measureText. // Any change to this method should be reflected in Paint.measureText @@ -1279,6 +1290,7 @@ public final class Canvas_Delegate { draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paint) { if (paint != null && paint.isFilterBitmap()) { graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, diff --git a/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java index 38c092d8572b..7475c22bf58c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java @@ -87,6 +87,7 @@ public abstract class Gradient_Delegate extends Shader_Delegate { mTileMode = tileMode; } + @Override public int getTransparency() { return java.awt.Paint.TRANSLUCENT; } diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java index a2ba758a7e0c..f117fca52cc1 100644 --- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java @@ -132,6 +132,7 @@ public final class LinearGradient_Delegate extends Gradient_Delegate { mDSize2 = mDx * mDx + mDy * mDy; } + @Override public java.awt.PaintContext createContext( java.awt.image.ColorModel colorModel, java.awt.Rectangle deviceBounds, @@ -176,13 +177,16 @@ public final class LinearGradient_Delegate extends Gradient_Delegate { mColorModel = colorModel; } + @Override public void dispose() { } + @Override public java.awt.image.ColorModel getColorModel() { return mColorModel; } + @Override public java.awt.image.Raster getRaster(int x, int y, int w, int h) { java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, java.awt.image.BufferedImage.TYPE_INT_ARGB); diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java index 451edd2f430d..5df2a21567cf 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -474,7 +474,7 @@ public final class Matrix_Delegate { } Matrix_Delegate other = sManager.getDelegate(other_matrix); - if (d == null) { + if (other == null) { return false; } @@ -570,7 +570,7 @@ public final class Matrix_Delegate { } Matrix_Delegate other = sManager.getDelegate(other_matrix); - if (d == null) { + if (other == null) { return false; } diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java index 5e882ce44390..be27b54bb828 100644 --- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -215,6 +215,7 @@ public final class NinePatch_Delegate { Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null); canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() { + @Override public void draw(Graphics2D graphics, Paint_Delegate paint) { chunkObject.draw(bitmap_delegate.getImage(), graphics, left, top, right - left, bottom - top, destDensity, srcDensity); diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java index 9bf78b4c7249..3fe45fae2a3d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java @@ -118,6 +118,7 @@ public class RadialGradient_Delegate extends Gradient_Delegate { mRadius = radius; } + @Override public java.awt.PaintContext createContext( java.awt.image.ColorModel colorModel, java.awt.Rectangle deviceBounds, @@ -162,13 +163,16 @@ public class RadialGradient_Delegate extends Gradient_Delegate { mColorModel = colorModel; } + @Override public void dispose() { } + @Override public java.awt.image.ColorModel getColorModel() { return mColorModel; } + @Override public java.awt.image.Raster getRaster(int x, int y, int w, int h) { java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, java.awt.image.BufferedImage.TYPE_INT_ARGB); diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java index 966e06e4a8b2..13ae12e5a9fa 100644 --- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java @@ -110,6 +110,7 @@ public class SweepGradient_Delegate extends Gradient_Delegate { mCy = cy; } + @Override public java.awt.PaintContext createContext( java.awt.image.ColorModel colorModel, java.awt.Rectangle deviceBounds, @@ -154,13 +155,16 @@ public class SweepGradient_Delegate extends Gradient_Delegate { mColorModel = colorModel; } + @Override public void dispose() { } + @Override public java.awt.image.ColorModel getColorModel() { return mColorModel; } + @Override public java.awt.image.Raster getRaster(int x, int y, int w, int h) { java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, java.awt.image.BufferedImage.TYPE_INT_ARGB); diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java index ce32da94badc..6aa4b3b2eb26 100644 --- a/tools/layoutlib/bridge/src/android/view/SurfaceView.java +++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java @@ -27,7 +27,7 @@ import android.util.AttributeSet; * Mock version of the SurfaceView. * Only non override public methods from the real SurfaceView have been added in there. * Methods that take an unknown class as parameter or as return object, have been removed for now. - * + * * TODO: generate automatically. * */ @@ -36,7 +36,7 @@ public class SurfaceView extends MockView { public SurfaceView(Context context) { this(context, null); } - + public SurfaceView(Context context, AttributeSet attrs) { this(context, attrs , 0); } @@ -44,53 +44,66 @@ public class SurfaceView extends MockView { public SurfaceView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } - + public SurfaceHolder getHolder() { return mSurfaceHolder; } private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { - + + @Override public boolean isCreating() { return false; } + @Override public void addCallback(Callback callback) { } + @Override public void removeCallback(Callback callback) { } - + + @Override public void setFixedSize(int width, int height) { } + @Override public void setSizeFromLayout() { } + @Override public void setFormat(int format) { } + @Override public void setType(int type) { } + @Override public void setKeepScreenOn(boolean screenOn) { } - + + @Override public Canvas lockCanvas() { return null; } + @Override public Canvas lockCanvas(Rect dirty) { return null; } + @Override public void unlockCanvasAndPost(Canvas canvas) { } + @Override public Surface getSurface() { return null; } + @Override public Rect getSurfaceFrame() { return null; } diff --git a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java index 9efdcaffe70a..3017292d8337 100644 --- a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java +++ b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java @@ -43,28 +43,33 @@ public class ITextServicesManager_Stub_Delegate { private static class FakeTextServicesManager implements ITextServicesManager { + @Override public void finishSpellCheckerService(ISpellCheckerSessionListener arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public void getSpellCheckerService(String arg0, String arg1, ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4) throws RemoteException { @@ -72,26 +77,31 @@ public class ITextServicesManager_Stub_Delegate { } + @Override public boolean isSpellCheckerEnabled() throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setSpellCheckerEnabled(boolean arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public IBinder asBinder() { // TODO Auto-generated method stub return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java index c64ab657ac05..e28866e58891 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -19,7 +19,7 @@ package com.android.layoutlib.bridge.android; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentValues; -import android.content.ICancelationSignal; +import android.content.ICancellationSignal; import android.content.IContentProvider; import android.content.OperationApplicationException; import android.content.res.AssetFileDescriptor; @@ -92,7 +92,7 @@ public final class BridgeContentProvider implements IContentProvider { @Override public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4, - ICancelationSignal arg5) throws RemoteException { + ICancellationSignal arg5) throws RemoteException { // TODO Auto-generated method stub return null; } @@ -124,7 +124,7 @@ public final class BridgeContentProvider implements IContentProvider { } @Override - public ICancelationSignal createCancelationSignal() throws RemoteException { + public ICancellationSignal createCancellationSignal() throws RemoteException { // TODO Auto-generated method stub return null; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java index 2a52888d2d29..db0694c8707d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java @@ -37,151 +37,179 @@ import java.util.List; */ public class BridgeIInputMethodManager implements IInputMethodManager { + @Override public void addClient(IInputMethodClient arg0, IInputContext arg1, int arg2, int arg3) throws RemoteException { // TODO Auto-generated method stub } + @Override public void finishInput(IInputMethodClient arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public InputMethodSubtype getCurrentInputMethodSubtype() throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public List<InputMethodInfo> getEnabledInputMethodList() throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public List<InputMethodInfo> getInputMethodList() throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public InputMethodSubtype getLastInputMethodSubtype() throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public List getShortcutInputMethodsAndSubtypes() throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public void hideMySoftInput(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public boolean hideSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean notifySuggestionPicked(SuggestionSpan arg0, String arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public void registerSuggestionSpansForNotification(SuggestionSpan[] arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void removeClient(IInputMethodClient arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setAdditionalInputMethodSubtypes(String arg0, InputMethodSubtype[] arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public boolean setCurrentInputMethodSubtype(InputMethodSubtype arg0) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public void setImeWindowStatus(IBinder arg0, int arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setInputMethod(IBinder arg0, String arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setInputMethodAndSubtype(IBinder arg0, String arg1, InputMethodSubtype arg2) throws RemoteException { // TODO Auto-generated method stub } + @Override public boolean setInputMethodEnabled(String arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public void showInputMethodAndSubtypeEnablerFromClient(IInputMethodClient arg0, String arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void showInputMethodPickerFromClient(IInputMethodClient arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void showMySoftInput(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public boolean showSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public InputBindResult startInput(IInputMethodClient arg0, IInputContext arg1, EditorInfo arg2, boolean arg3, boolean arg4) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public boolean switchToLastInputMethod(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public void updateStatusIcon(IBinder arg0, String arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub } + @Override public void windowGainedFocus(IInputMethodClient arg0, IBinder arg1, boolean arg2, boolean arg3, int arg4, boolean arg5, int arg6) throws RemoteException { // TODO Auto-generated method stub } + @Override public IBinder asBinder() { // TODO Auto-generated method stub return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java index d2084083981a..f5912e7bdb24 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java @@ -37,6 +37,7 @@ public class BridgeLayoutParamsMapAttributes implements AttributeSet { mAttributes = attributes; } + @Override public String getAttributeValue(String namespace, String name) { if (BridgeConstants.NS_RESOURCES.equals(namespace)) { return mAttributes.get(name); @@ -49,93 +50,114 @@ public class BridgeLayoutParamsMapAttributes implements AttributeSet { // BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int) // Should they ever be called, we'll just implement them on a need basis. + @Override public int getAttributeCount() { throw new UnsupportedOperationException(); } + @Override public String getAttributeName(int index) { throw new UnsupportedOperationException(); } + @Override public String getAttributeValue(int index) { throw new UnsupportedOperationException(); } + @Override public String getPositionDescription() { throw new UnsupportedOperationException(); } + @Override public int getAttributeNameResource(int index) { throw new UnsupportedOperationException(); } + @Override public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) { throw new UnsupportedOperationException(); } + @Override public boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue) { throw new UnsupportedOperationException(); } + @Override public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) { throw new UnsupportedOperationException(); } + @Override public int getAttributeIntValue(String namespace, String attribute, int defaultValue) { throw new UnsupportedOperationException(); } + @Override public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) { throw new UnsupportedOperationException(); } + @Override public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) { throw new UnsupportedOperationException(); } + @Override public int getAttributeListValue(int index, String[] options, int defaultValue) { throw new UnsupportedOperationException(); } + @Override public boolean getAttributeBooleanValue(int index, boolean defaultValue) { throw new UnsupportedOperationException(); } + @Override public int getAttributeResourceValue(int index, int defaultValue) { throw new UnsupportedOperationException(); } + @Override public int getAttributeIntValue(int index, int defaultValue) { throw new UnsupportedOperationException(); } + @Override public int getAttributeUnsignedIntValue(int index, int defaultValue) { throw new UnsupportedOperationException(); } + @Override public float getAttributeFloatValue(int index, float defaultValue) { throw new UnsupportedOperationException(); } + @Override public String getIdAttribute() { throw new UnsupportedOperationException(); } + @Override public String getClassAttribute() { throw new UnsupportedOperationException(); } + @Override public int getIdAttributeResourceValue(int defaultValue) { throw new UnsupportedOperationException(); } + @Override public int getStyleAttribute() { throw new UnsupportedOperationException(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java index e13380e7e2dd..79606a48f134 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java @@ -24,72 +24,67 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.view.DragEvent; import android.view.IWindow; -import android.view.KeyEvent; -import android.view.MotionEvent; /** * Implementation of {@link IWindow} to pass to the AttachInfo. */ public final class BridgeWindow implements IWindow { + @Override public void dispatchAppVisibility(boolean arg0) throws RemoteException { // pass for now. } + @Override public void dispatchGetNewSurface() throws RemoteException { // pass for now. } - - public void dispatchKey(KeyEvent arg0) throws RemoteException { - // pass for now. - } - - public void dispatchPointer(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { - // pass for now. - } - - public void dispatchTrackball(MotionEvent arg0, long arg1, boolean arg2) - throws RemoteException { - // pass for now. - } - + @Override public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2) throws RemoteException { // pass for now. } + @Override public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4, Configuration arg5) throws RemoteException { // pass for now. } + @Override public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException { // pass for now. } + @Override public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, boolean sync) { // pass for now. } + @Override public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, boolean sync) { // pass for now. } + @Override public void closeSystemDialogs(String reason) { // pass for now. } + @Override public void dispatchDragEvent(DragEvent event) { // pass for now. } + @Override public void dispatchSystemUiVisibilityChanged(int seq, int globalUi, int localValue, int localChanges) { // pass for now. } + @Override public IBinder asBinder() { // pass for now. return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java index 516725e02174..bef2c95b4d6a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java @@ -67,288 +67,345 @@ public class BridgeWindowManager implements IWindowManager { // ---- implementation of IWindowManager that we care about ---- + @Override public int getRotation() throws RemoteException { return mRotation; } + @Override public int getMaximumSizeDimension() throws RemoteException { return 0; } + @Override public void getDisplaySize(Point arg0) throws RemoteException { } + @Override public void getRealDisplaySize(Point arg0) throws RemoteException { } // ---- unused implementation of IWindowManager ---- + @Override public boolean canStatusBarHide() throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, boolean arg4) throws RemoteException { // TODO Auto-generated method stub } + @Override public void addWindowToken(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void clearForcedDisplaySize() throws RemoteException { // TODO Auto-generated method stub } + @Override public void closeSystemDialogs(String arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void disableKeyguard(IBinder arg0, String arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void executeAppTransition() throws RemoteException { // TODO Auto-generated method stub } + @Override public void exitKeyguardSecurely(IOnKeyguardExitResult arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void freezeRotation(int arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public float getAnimationScale(int arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public float[] getAnimationScales() throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public int getAppOrientation(IApplicationToken arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getDPadKeycodeState(int arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getDPadScancodeState(int arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public InputDevice getInputDevice(int arg0) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public int[] getInputDeviceIds() throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public int getKeycodeState(int arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getKeycodeStateForDevice(int arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getPendingAppTransition() throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getScancodeState(int arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getScancodeStateForDevice(int arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getSwitchState(int arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getSwitchStateForDevice(int arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getTrackballKeycodeState(int arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public int getTrackballScancodeState(int arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public boolean hasKeys(int[] arg0, boolean[] arg1) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean inKeyguardRestrictedInputMode() throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean injectInputEventNoWait(InputEvent arg0) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean injectKeyEvent(KeyEvent arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean injectPointerEvent(MotionEvent arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean injectTrackballEvent(MotionEvent arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean inputMethodClientHasFocus(IInputMethodClient arg0) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean isKeyguardLocked() throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean isKeyguardSecure() throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public boolean isViewServerRunning() throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public InputChannel monitorInput(String arg0) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public void moveAppToken(int arg0, IBinder arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void moveAppTokensToBottom(List<IBinder> arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void moveAppTokensToTop(List<IBinder> arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public IWindowSession openSession(IInputMethodClient arg0, IInputContext arg1) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public void overridePendingAppTransition(String arg0, int arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub } + @Override public void pauseKeyDispatching(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void prepareAppTransition(int arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void reenableKeyguard(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void removeAppToken(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void removeWindowToken(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void resumeKeyDispatching(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public Bitmap screenshotApplications(IBinder arg0, int arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public void setAnimationScale(int arg0, float arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setAnimationScales(float[] arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setAppGroupId(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setAppOrientation(IApplicationToken arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setAppStartingWindow(IBinder arg0, String arg1, int arg2, CompatibilityInfo arg3, CharSequence arg4, int arg5, int arg6, int arg7, IBinder arg8, boolean arg9) throws RemoteException { @@ -356,122 +413,147 @@ public class BridgeWindowManager implements IWindowManager { } + @Override public void setAppVisibility(IBinder arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setAppWillBeHidden(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setEventDispatching(boolean arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setFocusedApp(IBinder arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setForcedDisplaySize(int arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setInTouchMode(boolean arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setNewConfiguration(Configuration arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setPointerSpeed(int arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void updateRotation(boolean arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void setStrictModeVisualIndicatorPreference(String arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void showStrictModeViolation(boolean arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void startAppFreezingScreen(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public boolean startViewServer(int arg0) throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public void statusBarVisibilityChanged(int arg0) throws RemoteException { // TODO Auto-generated method stub } + @Override public void stopAppFreezingScreen(IBinder arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub } + @Override public boolean stopViewServer() throws RemoteException { // TODO Auto-generated method stub return false; } + @Override public void thawRotation() throws RemoteException { // TODO Auto-generated method stub } + @Override public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1) throws RemoteException { // TODO Auto-generated method stub return null; } + @Override public int watchRotation(IRotationWatcher arg0) throws RemoteException { // TODO Auto-generated method stub return 0; } + @Override public void waitForWindowDrawn(IBinder token, IRemoteCallback callback) { // TODO Auto-generated method stub } - + + @Override public IBinder asBinder() { // TODO Auto-generated method stub return null; } + @Override public int getPreferredOptionsPanelGravity() throws RemoteException { return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; } + @Override public void dismissKeyguard() { } + @Override public boolean hasNavigationBar() { return false; // should this return something else? } + @Override public void lockNow() { // TODO Auto-generated method stub } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index a640a9139be8..d3721ed10f70 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -26,7 +26,6 @@ import android.os.RemoteException; import android.view.IWindow; import android.view.IWindowSession; import android.view.InputChannel; -import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceView; import android.view.WindowManager.LayoutParams; @@ -37,6 +36,7 @@ import android.view.WindowManager.LayoutParams; */ public final class BridgeWindowSession implements IWindowSession { + @Override public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3, InputChannel outInputchannel) throws RemoteException { @@ -44,40 +44,30 @@ public final class BridgeWindowSession implements IWindowSession { return 0; } + @Override public int addWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3) throws RemoteException { // pass for now. return 0; } + @Override public void finishDrawing(IWindow arg0) throws RemoteException { // pass for now. } - public void finishKey(IWindow arg0) throws RemoteException { - // pass for now. - } - + @Override public boolean getInTouchMode() throws RemoteException { // pass for now. return false; } + @Override public boolean performHapticFeedback(IWindow window, int effectId, boolean always) { // pass for now. return false; } - - public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException { - // pass for now. - return null; - } - - public MotionEvent getPendingTrackballMove(IWindow arg0) throws RemoteException { - // pass for now. - return null; - } - + @Override public int relayout(IWindow arg0, int seq, LayoutParams arg1, int arg2, int arg3, int arg4, int arg4_5, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b, Surface arg8) throws RemoteException { @@ -85,35 +75,43 @@ public final class BridgeWindowSession implements IWindowSession { return 0; } + @Override public void performDeferredDestroy(IWindow window) { // pass for now. } + @Override public boolean outOfMemory(IWindow window) throws RemoteException { return false; } + @Override public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { // pass for now. } + @Override public void remove(IWindow arg0) throws RemoteException { // pass for now. } + @Override public void setInTouchMode(boolean arg0) throws RemoteException { // pass for now. } + @Override public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException { // pass for now. } + @Override public void setInsets(IWindow window, int touchable, Rect contentInsets, Rect visibleInsets, Region touchableRegion) { // pass for now. } + @Override public IBinder prepareDrag(IWindow window, int flags, int thumbnailWidth, int thumbnailHeight, Surface outSurface) throws RemoteException { @@ -121,6 +119,7 @@ public final class BridgeWindowSession implements IWindowSession { return null; } + @Override public boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) @@ -129,49 +128,47 @@ public final class BridgeWindowSession implements IWindowSession { return false; } + @Override public void reportDropResult(IWindow window, boolean consumed) throws RemoteException { // pass for now } + @Override public void dragRecipientEntered(IWindow window) throws RemoteException { // pass for now } + @Override public void dragRecipientExited(IWindow window) throws RemoteException { // pass for now } + @Override public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) { // pass for now. } + @Override public void wallpaperOffsetsComplete(IBinder window) { // pass for now. } + @Override public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, int z, Bundle extras, boolean sync) { // pass for now. return null; } + @Override public void wallpaperCommandComplete(IBinder window, Bundle result) { // pass for now. } - public void closeSystemDialogs(String reason) { - // pass for now. - } - + @Override public IBinder asBinder() { // pass for now. return null; } - - public IBinder prepareDrag(IWindow arg0, boolean arg1, int arg2, int arg3, Surface arg4) - throws RemoteException { - // TODO Auto-generated method stub - return null; - } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java index f8ed4f70a9f3..ac8712eaa025 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java @@ -95,6 +95,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { // ------- XmlResourceParser implementation + @Override public void setFeature(String name, boolean state) throws XmlPullParserException { if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { @@ -106,6 +107,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { throw new XmlPullParserException("Unsupported feature: " + name); } + @Override public boolean getFeature(String name) { if (FEATURE_PROCESS_NAMESPACES.equals(name)) { return true; @@ -116,82 +118,101 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return false; } + @Override public void setProperty(String name, Object value) throws XmlPullParserException { throw new XmlPullParserException("setProperty() not supported"); } + @Override public Object getProperty(String name) { return null; } + @Override public void setInput(Reader in) throws XmlPullParserException { mParser.setInput(in); } + @Override public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException { mParser.setInput(inputStream, inputEncoding); } + @Override public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException { throw new XmlPullParserException( "defineEntityReplacementText() not supported"); } + @Override public String getNamespacePrefix(int pos) throws XmlPullParserException { throw new XmlPullParserException("getNamespacePrefix() not supported"); } + @Override public String getInputEncoding() { return null; } + @Override public String getNamespace(String prefix) { throw new RuntimeException("getNamespace() not supported"); } + @Override public int getNamespaceCount(int depth) throws XmlPullParserException { throw new XmlPullParserException("getNamespaceCount() not supported"); } + @Override public String getPositionDescription() { return "Binary XML file line #" + getLineNumber(); } + @Override public String getNamespaceUri(int pos) throws XmlPullParserException { throw new XmlPullParserException("getNamespaceUri() not supported"); } + @Override public int getColumnNumber() { return -1; } + @Override public int getDepth() { return mParser.getDepth(); } + @Override public String getText() { return mParser.getText(); } + @Override public int getLineNumber() { return mParser.getLineNumber(); } + @Override public int getEventType() { return mEventType; } + @Override public boolean isWhitespace() throws XmlPullParserException { // Original comment: whitespace was stripped by aapt. return mParser.isWhitespace(); } + @Override public String getPrefix() { throw new RuntimeException("getPrefix not supported"); } + @Override public char[] getTextCharacters(int[] holderForStartAndLength) { String txt = getText(); char[] chars = null; @@ -204,55 +225,68 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return chars; } + @Override public String getNamespace() { return mParser.getNamespace(); } + @Override public String getName() { return mParser.getName(); } + @Override public String getAttributeNamespace(int index) { return mParser.getAttributeNamespace(index); } + @Override public String getAttributeName(int index) { return mParser.getAttributeName(index); } + @Override public String getAttributePrefix(int index) { throw new RuntimeException("getAttributePrefix not supported"); } + @Override public boolean isEmptyElementTag() { // XXX Need to detect this. return false; } + @Override public int getAttributeCount() { return mParser.getAttributeCount(); } + @Override public String getAttributeValue(int index) { return mParser.getAttributeValue(index); } + @Override public String getAttributeType(int index) { return "CDATA"; } + @Override public boolean isAttributeDefault(int index) { return false; } + @Override public int nextToken() throws XmlPullParserException, IOException { return next(); } + @Override public String getAttributeValue(String namespace, String name) { return mParser.getAttributeValue(namespace, name); } + @Override public int next() throws XmlPullParserException, IOException { if (!mStarted) { mStarted = true; @@ -313,6 +347,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return "????"; } + @Override public void require(int type, String namespace, String name) throws XmlPullParserException { if (type != getEventType() @@ -322,6 +357,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { + getPositionDescription()); } + @Override public String nextText() throws XmlPullParserException, IOException { if (getEventType() != START_TAG) { throw new XmlPullParserException(getPositionDescription() @@ -348,6 +384,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { } } + @Override public int nextTag() throws XmlPullParserException, IOException { int eventType = next(); if (eventType == TEXT && isWhitespace()) { // skip whitespace @@ -363,76 +400,94 @@ public class BridgeXmlBlockParser implements XmlResourceParser { // AttributeSet implementation + @Override public void close() { // pass } + @Override public boolean getAttributeBooleanValue(int index, boolean defaultValue) { return mAttrib.getAttributeBooleanValue(index, defaultValue); } + @Override public boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue) { return mAttrib.getAttributeBooleanValue(namespace, attribute, defaultValue); } + @Override public float getAttributeFloatValue(int index, float defaultValue) { return mAttrib.getAttributeFloatValue(index, defaultValue); } + @Override public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) { return mAttrib.getAttributeFloatValue(namespace, attribute, defaultValue); } + @Override public int getAttributeIntValue(int index, int defaultValue) { return mAttrib.getAttributeIntValue(index, defaultValue); } + @Override public int getAttributeIntValue(String namespace, String attribute, int defaultValue) { return mAttrib.getAttributeIntValue(namespace, attribute, defaultValue); } + @Override public int getAttributeListValue(int index, String[] options, int defaultValue) { return mAttrib.getAttributeListValue(index, options, defaultValue); } + @Override public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) { return mAttrib.getAttributeListValue(namespace, attribute, options, defaultValue); } + @Override public int getAttributeNameResource(int index) { return mAttrib.getAttributeNameResource(index); } + @Override public int getAttributeResourceValue(int index, int defaultValue) { return mAttrib.getAttributeResourceValue(index, defaultValue); } + @Override public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) { return mAttrib.getAttributeResourceValue(namespace, attribute, defaultValue); } + @Override public int getAttributeUnsignedIntValue(int index, int defaultValue) { return mAttrib.getAttributeUnsignedIntValue(index, defaultValue); } + @Override public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) { return mAttrib.getAttributeUnsignedIntValue(namespace, attribute, defaultValue); } + @Override public String getClassAttribute() { return mAttrib.getClassAttribute(); } + @Override public String getIdAttribute() { return mAttrib.getIdAttribute(); } + @Override public int getIdAttributeResourceValue(int defaultValue) { return mAttrib.getIdAttributeResourceValue(defaultValue); } + @Override public int getStyleAttribute() { return mAttrib.getStyleAttribute(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index 72ed3513f4b9..1817ab5e0833 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -132,7 +132,7 @@ abstract class CustomBar extends LinearLayout { if (bitmap != null) { BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap); - imageView.setBackgroundDrawable(drawable); + imageView.setImageDrawable(drawable); } } } @@ -145,6 +145,14 @@ abstract class CustomBar extends LinearLayout { } } + protected void loadIconById(int id, String iconReference) { + ResourceValue value = getResourceValue(iconReference); + if (value != null) { + loadIconById(id, value); + } + } + + protected Drawable loadIcon(int index, ResourceType type, String name) { BridgeContext bridgeContext = (BridgeContext) mContext; RenderResources res = bridgeContext.getRenderResources(); @@ -162,34 +170,64 @@ abstract class CustomBar extends LinearLayout { if (child instanceof ImageView) { ImageView imageView = (ImageView) child; - Drawable drawable = ResourceHelper.getDrawable( - value, (BridgeContext) mContext); - if (drawable != null) { - imageView.setBackgroundDrawable(drawable); - } + return loadIcon(imageView, value); + } - return drawable; + return null; + } + + private Drawable loadIconById(int id, ResourceValue value) { + View child = findViewById(id); + if (child instanceof ImageView) { + ImageView imageView = (ImageView) child; + + return loadIcon(imageView, value); } return null; } + + private Drawable loadIcon(ImageView imageView, ResourceValue value) { + Drawable drawable = ResourceHelper.getDrawable(value, (BridgeContext) mContext); + if (drawable != null) { + imageView.setImageDrawable(drawable); + } + + return drawable; + } + protected TextView setText(int index, String stringReference) { View child = getChildAt(index); if (child instanceof TextView) { TextView textView = (TextView) child; - ResourceValue value = getResourceValue(stringReference); - if (value != null) { - textView.setText(value.getValue()); - } else { - textView.setText(stringReference); - } + setText(textView, stringReference); + return textView; + } + + return null; + } + + protected TextView setTextById(int id, String stringReference) { + View child = findViewById(id); + if (child instanceof TextView) { + TextView textView = (TextView) child; + setText(textView, stringReference); return textView; } return null; } + private void setText(TextView textView, String stringReference) { + ResourceValue value = getResourceValue(stringReference); + if (value != null) { + textView.setText(value.getValue()); + } else { + textView.setText(stringReference); + } + } + protected void setStyle(String themeEntryName) { BridgeContext bridgeContext = (BridgeContext) mContext; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java index f6edea411ead..68f5aba1fc12 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java @@ -34,7 +34,7 @@ public class FakeActionBar extends CustomBar { // Cannot access the inside items through id because no R.id values have been // created for them. // We do know the order though. - loadIcon(0, icon); + loadIconById(android.R.id.home, icon); mTextView = setText(1, label); setStyle("actionBarStyle"); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index d5400d7f3b65..6840f468bc5c 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -33,10 +33,10 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.Result; -import com.android.ide.common.rendering.api.SessionParams; -import com.android.ide.common.rendering.api.ViewInfo; import com.android.ide.common.rendering.api.Result.Status; +import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; +import com.android.ide.common.rendering.api.ViewInfo; import com.android.internal.util.XmlUtils; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; @@ -69,8 +69,8 @@ import android.util.TypedValue; import android.view.AttachInfo_Accessor; import android.view.BridgeInflater; import android.view.View; -import android.view.ViewGroup; import android.view.View.MeasureSpec; +import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.AbsListView; @@ -82,8 +82,8 @@ import android.widget.LinearLayout; import android.widget.ListView; import android.widget.QuickContactBadge; import android.widget.TabHost; -import android.widget.TabWidget; import android.widget.TabHost.TabSpec; +import android.widget.TabWidget; import java.awt.AlphaComposite; import java.awt.Color; @@ -835,6 +835,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { previousTransition.addTransitionListener(new TransitionListener() { private int mChangeDisappearingCount = 0; + @Override public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { @@ -842,6 +843,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } + @Override public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { @@ -1227,6 +1229,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label", tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details)) .setContent(new TabHost.TabContentFactory() { + @Override public View createTabContent(String tag) { return new LinearLayout(getContext()); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java index c9bb424e909d..22570b9cf865 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java @@ -74,38 +74,46 @@ public class FakeAdapter extends BaseAdapter implements ListAdapter, SpinnerAdap } } + @Override public boolean isEnabled(int position) { return true; } + @Override public int getCount() { return mItems.size(); } + @Override public Object getItem(int position) { return mItems.get(position); } + @Override public long getItemId(int position) { return position; } + @Override public int getItemViewType(int position) { return mItems.get(position).getType(); } + @Override public View getView(int position, View convertView, ViewGroup parent) { // we don't care about recycling here because we never scroll. AdapterItem item = mItems.get(position); return getView(item, null /*parentGroup*/, convertView, parent); } + @Override public int getViewTypeCount() { return mTypes.size(); } // ---- SpinnerAdapter + @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { // pass return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java index 2c492e3efb16..199e0404a16b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java @@ -99,23 +99,28 @@ public class FakeExpandableAdapter extends BaseAdapter implements ExpandableList // ---- ExpandableListAdapter + @Override public int getGroupCount() { return mItems.size(); } + @Override public int getChildrenCount(int groupPosition) { AdapterItem item = mItems.get(groupPosition); return item.getChildren().size(); } + @Override public Object getGroup(int groupPosition) { return mItems.get(groupPosition); } + @Override public Object getChild(int groupPosition, int childPosition) { return getChildItem(groupPosition, childPosition); } + @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { // we don't care about recycling here because we never scroll. @@ -123,6 +128,7 @@ public class FakeExpandableAdapter extends BaseAdapter implements ExpandableList return getView(item, null /*parentItem*/, convertView, parent); } + @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { // we don't care about recycling here because we never scroll. @@ -131,48 +137,59 @@ public class FakeExpandableAdapter extends BaseAdapter implements ExpandableList return getView(item, parentItem, convertView, parent); } + @Override public long getGroupId(int groupPosition) { return groupPosition; } + @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } + @Override public long getCombinedGroupId(long groupId) { return groupId << 16 | 0x0000FFFF; } + @Override public long getCombinedChildId(long groupId, long childId) { return groupId << 16 | childId; } + @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } + @Override public void onGroupCollapsed(int groupPosition) { // pass } + @Override public void onGroupExpanded(int groupPosition) { // pass } // ---- HeterogeneousExpandableList + @Override public int getChildType(int groupPosition, int childPosition) { return getChildItem(groupPosition, childPosition).getType(); } + @Override public int getChildTypeCount() { return mChildrenTypes.size(); } + @Override public int getGroupType(int groupPosition) { return mItems.get(groupPosition).getType(); } + @Override public int getGroupTypeCount() { return mGroupTypes.size(); } diff --git a/tools/layoutlib/create/.settings/README.txt b/tools/layoutlib/create/.settings/README.txt new file mode 100644 index 000000000000..9120b20710a3 --- /dev/null +++ b/tools/layoutlib/create/.settings/README.txt @@ -0,0 +1,2 @@ +Copy this in eclipse project as a .settings folder at the root. +This ensure proper compilation compliance and warning/error levels.
\ No newline at end of file diff --git a/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs b/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000000..5381a0e16c7d --- /dev/null +++ b/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,93 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled +org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 70c8a009b291..4b33474c65a8 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -27,6 +27,7 @@ public final class CreateInfo implements ICreateInfo { * Returns the list of class from layoutlib_create to inject in layoutlib. * The list can be empty but must not be null. */ + @Override public Class<?>[] getInjectedClasses() { return INJECTED_CLASSES; } @@ -35,6 +36,7 @@ public final class CreateInfo implements ICreateInfo { * Returns the list of methods to rewrite as delegates. * The list can be empty but must not be null. */ + @Override public String[] getDelegateMethods() { return DELEGATE_METHODS; } @@ -43,6 +45,7 @@ public final class CreateInfo implements ICreateInfo { * Returns the list of classes on which to delegate all native methods. * The list can be empty but must not be null. */ + @Override public String[] getDelegateClassNatives() { return DELEGATE_CLASS_NATIVES; } @@ -54,6 +57,7 @@ public final class CreateInfo implements ICreateInfo { * <p/> * This usage is deprecated. Please use method 'delegates' instead. */ + @Override public String[] getOverriddenMethods() { return OVERRIDDEN_METHODS; } @@ -63,6 +67,7 @@ public final class CreateInfo implements ICreateInfo { * of class to replace followed by the new FQCN. * The list can be empty but must not be null. */ + @Override public String[] getRenamedClasses() { return RENAMED_CLASSES; } @@ -74,6 +79,7 @@ public final class CreateInfo implements ICreateInfo { * the methods to delete. * The list can be empty but must not be null. */ + @Override public String[] getDeleteReturns() { return DELETE_RETURNS; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java index 627ea17260fc..7d1e4cf49635 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java @@ -28,13 +28,14 @@ public class MethodAdapter implements MethodListener { * A stub method is being invoked. * <p/> * Known limitation: caller arguments are not available. - * + * * @param signature The signature of the method being invoked, composed of the * binary class name followed by the method descriptor (aka argument * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V". * @param isNative True if the method was a native method. * @param caller The calling object. Null for static methods, "this" for instance methods. */ + @Override public void onInvokeV(String signature, boolean isNative, Object caller) { } @@ -43,6 +44,7 @@ public class MethodAdapter implements MethodListener { * @see #onInvokeV(String, boolean, Object) * @return an integer, or a boolean, or a short or a byte. */ + @Override public int onInvokeI(String signature, boolean isNative, Object caller) { onInvokeV(signature, isNative, caller); return 0; @@ -53,6 +55,7 @@ public class MethodAdapter implements MethodListener { * @see #onInvokeV(String, boolean, Object) * @return a long. */ + @Override public long onInvokeL(String signature, boolean isNative, Object caller) { onInvokeV(signature, isNative, caller); return 0; @@ -63,6 +66,7 @@ public class MethodAdapter implements MethodListener { * @see #onInvokeV(String, boolean, Object) * @return a float. */ + @Override public float onInvokeF(String signature, boolean isNative, Object caller) { onInvokeV(signature, isNative, caller); return 0; @@ -73,6 +77,7 @@ public class MethodAdapter implements MethodListener { * @see #onInvokeV(String, boolean, Object) * @return a double. */ + @Override public double onInvokeD(String signature, boolean isNative, Object caller) { onInvokeV(signature, isNative, caller); return 0; @@ -83,6 +88,7 @@ public class MethodAdapter implements MethodListener { * @see #onInvokeV(String, boolean, Object) * @return an object. */ + @Override public Object onInvokeA(String signature, boolean isNative, Object caller) { onInvokeV(signature, isNative, caller); return null; diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index f4ff389d41b2..7b76a5b2f914 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -65,24 +65,29 @@ public class AsmGeneratorTest { public void testClassRenaming() throws IOException, LogAbortException { ICreateInfo ci = new ICreateInfo() { + @Override public Class<?>[] getInjectedClasses() { // classes to inject in the final JAR return new Class<?>[0]; } + @Override public String[] getDelegateMethods() { return new String[0]; } + @Override public String[] getDelegateClassNatives() { return new String[0]; } + @Override public String[] getOverriddenMethods() { // methods to force override return new String[0]; } + @Override public String[] getRenamedClasses() { // classes to rename (so that we can replace them) return new String[] { @@ -91,6 +96,7 @@ public class AsmGeneratorTest { }; } + @Override public String[] getDeleteReturns() { // methods deleted from their return type. return new String[0]; |