diff options
73 files changed, 1977 insertions, 1029 deletions
diff --git a/api/current.txt b/api/current.txt index 2c69beed6266..6a32efedfd63 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3870,6 +3870,8 @@ package android.app { method public android.content.IntentSender getIntentSender(); method public static android.app.PendingIntent getService(android.content.Context, int, android.content.Intent, int); method public java.lang.String getTargetPackage(); + method public int getTargetUid(); + method public int getTargetUserHandle(); method public static android.app.PendingIntent readPendingIntentOrNullFromParcel(android.os.Parcel); method public void send() throws android.app.PendingIntent.CanceledException; method public void send(int) throws android.app.PendingIntent.CanceledException; @@ -5262,6 +5264,7 @@ package android.content { method public abstract int checkUriPermission(android.net.Uri, int, int, int); method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public abstract deprecated void clearWallpaper() throws java.io.IOException; + method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration); method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract java.lang.String[] databaseList(); method public abstract boolean deleteDatabase(java.lang.String); @@ -5406,6 +5409,7 @@ package android.content { method public int checkUriPermission(android.net.Uri, int, int, int); method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public void clearWallpaper() throws java.io.IOException; + method public android.content.Context createConfigurationContext(android.content.res.Configuration); method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.lang.String[] databaseList(); method public boolean deleteDatabase(java.lang.String); @@ -5984,6 +5988,8 @@ package android.content { public class IntentSender implements android.os.Parcelable { method public int describeContents(); method public java.lang.String getTargetPackage(); + method public int getTargetUid(); + method public int getTargetUserHandle(); method public static android.content.IntentSender readIntentSenderOrNullFromParcel(android.os.Parcel); method public void sendIntent(android.content.Context, int, android.content.Intent, android.content.IntentSender.OnFinished, android.os.Handler) throws android.content.IntentSender.SendIntentException; method public void sendIntent(android.content.Context, int, android.content.Intent, android.content.IntentSender.OnFinished, android.os.Handler, java.lang.String) throws android.content.IntentSender.SendIntentException; @@ -13210,7 +13216,6 @@ package android.net.wifi { field public java.lang.String capabilities; field public int frequency; field public int level; - field public long timestamp; } public final class SupplicantState extends java.lang.Enum implements android.os.Parcelable { @@ -17234,9 +17239,11 @@ package android.provider { field public static final java.lang.String DURATION = "duration"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; + field public static final java.lang.String LIMIT_PARAM_KEY = "limit"; field public static final int MISSED_TYPE = 3; // 0x3 field public static final java.lang.String NEW = "new"; field public static final java.lang.String NUMBER = "number"; + field public static final java.lang.String OFFSET_PARAM_KEY = "offset"; field public static final int OUTGOING_TYPE = 2; // 0x2 field public static final java.lang.String TYPE = "type"; } @@ -21147,6 +21154,7 @@ package android.test.mock { method public int checkUriPermission(android.net.Uri, int, int, int); method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public void clearWallpaper(); + method public android.content.Context createConfigurationContext(android.content.res.Configuration); method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.lang.String[] databaseList(); method public boolean deleteDatabase(java.lang.String); @@ -23351,6 +23359,7 @@ package android.view { public class ContextThemeWrapper extends android.content.ContextWrapper { ctor public ContextThemeWrapper(); ctor public ContextThemeWrapper(android.content.Context, int); + method public void applyOverrideConfiguration(android.content.res.Configuration); method protected void onApplyThemeResource(android.content.res.Resources.Theme, int, boolean); } diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java index 088d20da8463..f9aa00ec2501 100644 --- a/core/java/android/animation/TimeAnimator.java +++ b/core/java/android/animation/TimeAnimator.java @@ -13,6 +13,12 @@ public class TimeAnimator extends ValueAnimator { private long mPreviousTime = -1; @Override + public void start() { + mPreviousTime = -1; + super.start(); + } + + @Override boolean animationFrame(long currentTime) { if (mListener != null) { long totalTime = currentTime - mStartTime; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bb35ddd33d51..0789c60ffa3d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1471,13 +1471,25 @@ public final class ActivityThread { private static class ResourcesKey { final private String mResDir; + final private Configuration mOverrideConfiguration; final private float mScale; final private int mHash; - ResourcesKey(String resDir, float scale) { + ResourcesKey(String resDir, Configuration overrideConfiguration, float scale) { mResDir = resDir; + if (overrideConfiguration != null) { + if (Configuration.EMPTY.equals(overrideConfiguration)) { + overrideConfiguration = null; + } + } + mOverrideConfiguration = overrideConfiguration; mScale = scale; - mHash = mResDir.hashCode() << 2 + (int) (mScale * 2); + int hash = 17; + hash = 31 * hash + mResDir.hashCode(); + hash = 31 * hash + (mOverrideConfiguration != null + ? mOverrideConfiguration.hashCode() : 0); + hash = 31 * hash + Float.floatToIntBits(mScale); + mHash = hash; } @Override @@ -1491,7 +1503,21 @@ public final class ActivityThread { return false; } ResourcesKey peer = (ResourcesKey) obj; - return mResDir.equals(peer.mResDir) && mScale == peer.mScale; + if (!mResDir.equals(peer.mResDir)) { + return false; + } + if (mOverrideConfiguration != peer.mOverrideConfiguration) { + if (mOverrideConfiguration == null || peer.mOverrideConfiguration == null) { + return false; + } + if (!mOverrideConfiguration.equals(peer.mOverrideConfiguration)) { + return false; + } + } + if (mScale != peer.mScale) { + return false; + } + return true; } } @@ -1562,8 +1588,10 @@ public final class ActivityThread { * @param compInfo the compability info. It will use the default compatibility info when it's * null. */ - Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { - ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); + Resources getTopLevelResources(String resDir, Configuration overrideConfiguration, + CompatibilityInfo compInfo) { + ResourcesKey key = new ResourcesKey(resDir, overrideConfiguration, + compInfo.applicationScale); Resources r; synchronized (mPackages) { // Resources is app scale dependent. @@ -1595,13 +1623,20 @@ public final class ActivityThread { //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics metrics = getDisplayMetricsLocked(null, false); - r = new Resources(assets, metrics, getConfiguration(), compInfo); + Configuration config; + if (key.mOverrideConfiguration != null) { + config = new Configuration(getConfiguration()); + config.updateFrom(key.mOverrideConfiguration); + } else { + config = getConfiguration(); + } + r = new Resources(assets, metrics, config, compInfo); if (false) { Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale); } - + synchronized (mPackages) { WeakReference<Resources> wr = mActiveResources.get(key); Resources existing = wr != null ? wr.get() : null; @@ -1621,8 +1656,10 @@ public final class ActivityThread { /** * Creates the top level resources for the given package. */ - Resources getTopLevelResources(String resDir, LoadedApk pkgInfo) { - return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo.get()); + Resources getTopLevelResources(String resDir, Configuration overrideConfiguration, + LoadedApk pkgInfo) { + return getTopLevelResources(resDir, overrideConfiguration, + pkgInfo.mCompatibilityInfo.get()); } final Handler getHandler() { @@ -3675,18 +3712,28 @@ public final class ActivityThread { ApplicationPackageManager.configurationChanged(); //Slog.i(TAG, "Configuration changed in " + currentPackageName()); - - Iterator<WeakReference<Resources>> it = - mActiveResources.values().iterator(); - //Iterator<Map.Entry<String, WeakReference<Resources>>> it = - // mActiveResources.entrySet().iterator(); + + Configuration tmpConfig = null; + + Iterator<Map.Entry<ResourcesKey, WeakReference<Resources>>> it = + mActiveResources.entrySet().iterator(); while (it.hasNext()) { - WeakReference<Resources> v = it.next(); - Resources r = v.get(); + Map.Entry<ResourcesKey, WeakReference<Resources>> entry = it.next(); + Resources r = entry.getValue().get(); if (r != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + r + " config to: " + config); - r.updateConfiguration(config, dm, compat); + Configuration override = entry.getKey().mOverrideConfiguration; + if (override != null) { + if (tmpConfig == null) { + tmpConfig = new Configuration(); + } + tmpConfig.setTo(config); + tmpConfig.updateFrom(override); + r.updateConfiguration(tmpConfig, dm, compat); + } else { + r.updateConfiguration(config, dm, compat); + } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 2face4c22c2c..9b59e2ce1017 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -713,7 +713,7 @@ final class ApplicationPackageManager extends PackageManager { } Resources r = mContext.mMainThread.getTopLevelResources( app.uid == Process.myUid() ? app.sourceDir - : app.publicSourceDir, mContext.mPackageInfo); + : app.publicSourceDir, null, mContext.mPackageInfo); if (r != null) { return r; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4496ce81484a..1ef14ebaec2b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -37,6 +37,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -525,7 +526,7 @@ class ContextImpl extends Context { @Override public AssetManager getAssets() { - return mResources.getAssets(); + return getResources().getAssets(); } @Override @@ -1591,6 +1592,16 @@ class ContextImpl extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + ContextImpl c = new ContextImpl(); + c.init(mPackageInfo, null, mMainThread); + c.mResources = mMainThread.getTopLevelResources( + mPackageInfo.getResDir(), overrideConfiguration, + mResources.getCompatibilityInfo()); + return c; + } + + @Override public boolean isRestricted() { return mRestricted; } @@ -1659,12 +1670,11 @@ class ContextImpl extends Context { " compatiblity info:" + container.getDisplayMetrics()); } mResources = mainThread.getTopLevelResources( - mPackageInfo.getResDir(), container.getCompatibilityInfo()); + mPackageInfo.getResDir(), null, container.getCompatibilityInfo()); } mMainThread = mainThread; mContentResolver = new ApplicationContentResolver(this, mainThread); - - setActivityToken(activityToken); + mActivityToken = activityToken; } final void init(Resources resources, ActivityThread mainThread) { @@ -1691,10 +1701,6 @@ class ContextImpl extends Context { return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext()); } - final void setActivityToken(IBinder token) { - mActivityToken = token; - } - final void setOuterContext(Context context) { mOuterContext = context; } diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index 688cdfd66eb5..074d34398379 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -30,4 +30,5 @@ interface ISearchManager { List<ResolveInfo> getGlobalSearchActivities(); ComponentName getGlobalSearchActivity(); ComponentName getWebSearchActivity(); + ComponentName getAssistIntent(int userHandle); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index be4b2844c0b1..f4195d68794a 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -471,7 +471,7 @@ public final class LoadedApk { public Resources getResources(ActivityThread mainThread) { if (mResources == null) { - mResources = mainThread.getTopLevelResources(mResDir, this); + mResources = mainThread.getTopLevelResources(mResDir, null, this); } return mResources; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b2e69de2278b..cb83dc2cdcb5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -897,12 +897,16 @@ public class Notification implements Parcelable * Builder class for {@link Notification} objects. * * Provides a convenient way to set the various fields of a {@link Notification} and generate - * content views using the platform's notification layout template. + * content views using the platform's notification layout template. If your app supports + * versions of Android as old as API level 4, you can instead use + * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, + * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support + * library</a>. * - * Example: + * <p>Example: * * <pre class="prettyprint"> - * Notification noti = new Notification.Builder() + * Notification noti = new Notification.Builder(mContext) * .setContentTitle("New mail from " + sender.toString()) * .setContentText(subject) * .setSmallIcon(R.drawable.new_mail) diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 8adc8a2625af..c320ee399606 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserId; import android.util.AndroidException; /** @@ -617,6 +618,47 @@ public final class PendingIntent implements Parcelable { } /** + * Return the uid of the application that created this + * PendingIntent, that is the identity under which you will actually be + * sending the Intent. The returned integer is supplied by the system, so + * that an application can not spoof its uid. + * + * @return The uid of the PendingIntent, or -1 if there is + * none associated with it. + */ + public int getTargetUid() { + try { + return ActivityManagerNative.getDefault() + .getUidForIntentSender(mTarget); + } catch (RemoteException e) { + // Should never happen. + return -1; + } + } + + /** + * Return the user handle of the application that created this + * PendingIntent, that is the user under which you will actually be + * sending the Intent. The returned integer is supplied by the system, so + * that an application can not spoof its user. See + * {@link android.os.Process#myUserHandle() Process.myUserHandle()} for + * more explanation of user handles. + * + * @return The user handle of the PendingIntent, or -1 if there is + * none associated with it. + */ + public int getTargetUserHandle() { + try { + int uid = ActivityManagerNative.getDefault() + .getUidForIntentSender(mTarget); + return uid > 0 ? UserId.getUserId(uid) : -1; + } catch (RemoteException e) { + // Should never happen. + return -1; + } + } + + /** * @hide * Check to verify that this PendingIntent targets a specific package. */ diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index d1d51310d6f8..b8c99373e407 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -31,6 +31,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserId; import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -845,14 +846,28 @@ public class SearchManager * * @hide */ - public static final Intent getAssistIntent(Context context) { - PackageManager pm = context.getPackageManager(); - Intent intent = new Intent(Intent.ACTION_ASSIST); - ComponentName component = intent.resolveActivity(pm); - if (component != null) { - intent.setComponent(component); + public Intent getAssistIntent(Context context) { + return getAssistIntent(context, UserId.myUserId()); + } + + /** + * Gets an intent for launching installed assistant activity, or null if not available. + * @return The assist intent. + * + * @hide + */ + public Intent getAssistIntent(Context context, int userHandle) { + try { + ComponentName comp = mService.getAssistIntent(userHandle); + if (comp == null) { + return null; + } + Intent intent = new Intent(Intent.ACTION_ASSIST); + intent.setComponent(comp); return intent; + } catch (RemoteException re) { + Log.e(TAG, "getAssistIntent() failed: " + re); + return null; } - return null; } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index bf60a96ea071..a90142ae2a48 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -19,6 +19,7 @@ package android.content; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.DatabaseErrorHandler; @@ -2445,6 +2446,23 @@ public abstract class Context { int flags) throws PackageManager.NameNotFoundException; /** + * Return a new Context object for the current Context but whose resources + * are adjusted to match the given Configuration. Each call to this method + * returns a new instance of a Contex object; Context objects are not + * shared, however common state (ClassLoader, other Resources for the + * same configuration) may be so the Context itself can be fairly lightweight. + * + * @param overrideConfiguration A {@link Configuration} specifying what + * values to modify in the base Configuration of the original Context's + * resources. If the base configuration changes (such as due to an + * orientation change), the resources of this context will also change except + * for those that have been explicitly overridden with a value here. + * + * @return A Context for the application. + */ + public abstract Context createConfigurationContext(Configuration overrideConfiguration); + + /** * Indicates whether this Context is restricted. * * @return True if this Context is restricted, false otherwise. diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index ff4c9a100123..fdf60ab0517f 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -19,6 +19,7 @@ package android.content; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -533,6 +534,11 @@ public class ContextWrapper extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + return mBase.createConfigurationContext(overrideConfiguration); + } + + @Override public boolean isRestricted() { return mBase.isRestricted(); } diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index 4db4bdca3657..961864564be5 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserId; import android.util.AndroidException; @@ -223,6 +224,47 @@ public class IntentSender implements Parcelable { } /** + * Return the uid of the application that created this + * PendingIntent, that is the identity under which you will actually be + * sending the Intent. The returned integer is supplied by the system, so + * that an application can not spoof its uid. + * + * @return The uid of the PendingIntent, or -1 if there is + * none associated with it. + */ + public int getTargetUid() { + try { + return ActivityManagerNative.getDefault() + .getUidForIntentSender(mTarget); + } catch (RemoteException e) { + // Should never happen. + return -1; + } + } + + /** + * Return the user handle of the application that created this + * PendingIntent, that is the user under which you will actually be + * sending the Intent. The returned integer is supplied by the system, so + * that an application can not spoof its user. See + * {@link android.os.Process#myUserHandle() Process.myUserHandle()} for + * more explanation of user handles. + * + * @return The user handle of the PendingIntent, or -1 if there is + * none associated with it. + */ + public int getTargetUserHandle() { + try { + int uid = ActivityManagerNative.getDefault() + .getUidForIntentSender(mTarget); + return uid > 0 ? UserId.getUserId(uid) : -1; + } catch (RemoteException e) { + // Should never happen. + return -1; + } + } + + /** * Comparison operator on two IntentSender objects, such that true * is returned then they both represent the same operation from the * same package. diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index ea13a2a141ab..52b6498f80e5 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -35,6 +35,9 @@ import java.util.Locale; * <pre>Configuration config = getResources().getConfiguration();</pre> */ public final class Configuration implements Parcelable, Comparable<Configuration> { + /** @hide */ + public static final Configuration EMPTY = new Configuration(); + /** * Current user preference for the scaling factor for fonts, relative * to the base density scaling. diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index d2af3e90f33b..7559f1e70630 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1121,8 +1121,8 @@ public class Resources { * Return a StyledAttributes holding the values defined by * <var>Theme</var> which are listed in <var>attrs</var>. * - * <p>Be sure to call StyledAttributes.recycle() when you are done with - * the array. + * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done + * with the array. * * @param attrs The desired attributes. * @@ -1149,8 +1149,8 @@ public class Resources { * Return a StyledAttributes holding the values defined by the style * resource <var>resid</var> which are listed in <var>attrs</var>. * - * <p>Be sure to call StyledAttributes.recycle() when you are done with - * the array. + * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done + * with the array. * * @param resid The desired style resource. * @param attrs The desired attributes in the style. @@ -1209,8 +1209,8 @@ public class Resources { * AttributeSet specifies a style class (through the "style" attribute), * that style will be applied on top of the base attributes it defines. * - * <p>Be sure to call StyledAttributes.recycle() when you are done with - * the array. + * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done + * with the array. * * <p>When determining the final value of a particular attribute, there * are four inputs that come into play:</p> @@ -1435,9 +1435,12 @@ public class Resources { int configChanges = 0xfffffff; if (config != null) { mTmpConfig.setTo(config); + int density = config.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = mMetrics.noncompatDensityDpi; + } if (mCompatibilityInfo != null) { - mCompatibilityInfo.applyToConfiguration(mMetrics.noncompatDensityDpi, - mTmpConfig); + mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); } if (mTmpConfig.locale == null) { mTmpConfig.locale = Locale.getDefault(); @@ -1448,6 +1451,10 @@ public class Resources { if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); } + if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { + mMetrics.densityDpi = mConfiguration.densityDpi; + mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + } mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; String locale = null; diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 281b8436a99d..7f3b6b93619c 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -691,7 +691,7 @@ public class TypedArray { } /** - * Give back a previously retrieved StyledAttributes, for later re-use. + * Give back a previously retrieved array, for later re-use. */ public void recycle() { synchronized (mResources.mTmpValue) { diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index b892c8128cdd..77853683b2e5 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -123,6 +123,7 @@ public class Handler { } mQueue = mLooper.mQueue; mCallback = null; + mAsynchronous = false; } /** @@ -147,6 +148,7 @@ public class Handler { } mQueue = mLooper.mQueue; mCallback = callback; + mAsynchronous = false; } /** @@ -156,6 +158,7 @@ public class Handler { mLooper = looper; mQueue = looper.mQueue; mCallback = null; + mAsynchronous = false; } /** @@ -166,6 +169,31 @@ public class Handler { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; + mAsynchronous = false; + } + + /** + * Use the provided queue instead of the default one and take a callback + * interface in which to handle messages. Also set whether the handler + * should be asynchronous. + * + * Handlers are synchronous by default unless this constructor is used to make + * one that is strictly asynchronous. + * + * Asynchronous messages represent interrupts or events that do not require global ordering + * with represent to synchronous messages. Asynchronous messages are not subject to + * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. + * + * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for + * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. + * + * @hide + */ + public Handler(Looper looper, Callback callback, boolean async) { + mLooper = looper; + mQueue = looper.mQueue; + mCallback = callback; + mAsynchronous = async; } /** @@ -464,20 +492,15 @@ public class Handler { * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ - public boolean sendMessageAtTime(Message msg, long uptimeMillis) - { - boolean sent = false; + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; - if (queue != null) { - msg.target = this; - sent = queue.enqueueMessage(msg, uptimeMillis); - } - else { + if (queue == null) { RuntimeException e = new RuntimeException( - this + " sendMessageAtTime() called with no mQueue"); + this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); + return false; } - return sent; + return enqueueMessage(queue, msg, uptimeMillis); } /** @@ -492,20 +515,23 @@ public class Handler { * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ - public final boolean sendMessageAtFrontOfQueue(Message msg) - { - boolean sent = false; + public final boolean sendMessageAtFrontOfQueue(Message msg) { MessageQueue queue = mQueue; - if (queue != null) { - msg.target = this; - sent = queue.enqueueMessage(msg, 0); - } - else { + if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); + return false; + } + return enqueueMessage(queue, msg, 0); + } + + private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { + msg.target = this; + if (mAsynchronous) { + msg.setAsynchronous(true); } - return sent; + return queue.enqueueMessage(msg, uptimeMillis); } /** @@ -618,5 +644,6 @@ public class Handler { final MessageQueue mQueue; final Looper mLooper; final Callback mCallback; + final boolean mAsynchronous; IMessenger mMessenger; } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 22b68bc0f9a2..5dca67ff6f98 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -17,9 +17,6 @@ package android.provider; -import com.android.internal.telephony.CallerInfo; -import com.android.internal.telephony.PhoneConstants; - import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -30,6 +27,9 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.DataUsageFeedback; import android.text.TextUtils; +import com.android.internal.telephony.CallerInfo; +import com.android.internal.telephony.PhoneConstants; + /** * The CallLog provider contains information about placed and received calls. */ @@ -59,6 +59,20 @@ public class CallLog { Uri.parse("content://call_log/calls/filter"); /** + * Query parameter used to limit the number of call logs returned. + * <p> + * TYPE: integer + */ + public static final String LIMIT_PARAM_KEY = "limit"; + + /** + * Query parameter used to specify the starting record to return. + * <p> + * TYPE: integer + */ + public static final String OFFSET_PARAM_KEY = "offset"; + + /** * An optional URI parameter which instructs the provider to allow the operation to be * applied to voicemail records as well. * <p> diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index a1f6735db34e..b4f5e121d208 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -18,6 +18,9 @@ package android.server.search; import com.android.internal.content.PackageMonitor; +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.AppGlobals; import android.app.ISearchManager; import android.app.SearchManager; import android.app.SearchableInfo; @@ -27,14 +30,18 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.os.Binder; import android.os.Process; +import android.os.RemoteException; import android.os.UserId; import android.os.UserManager; import android.provider.Settings; import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import java.util.List; @@ -207,4 +214,48 @@ public class SearchManagerService extends ISearchManager.Stub { return getSearchables(UserId.getCallingUserId()).getWebSearchActivity(); } + @Override + public ComponentName getAssistIntent(int userHandle) { + try { + if (userHandle != UserId.getCallingUserId()) { + // Requesting a different user, make sure that they have the permission + if (ActivityManager.checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Binder.getCallingUid(), -1, true) + == PackageManager.PERMISSION_GRANTED) { + // Translate to the current user id, if caller wasn't aware + if (userHandle == UserId.USER_CURRENT) { + long identity = Binder.clearCallingIdentity(); + userHandle = ActivityManagerNative.getDefault().getCurrentUser().id; + Binder.restoreCallingIdentity(identity); + } + } else { + String msg = "Permission Denial: " + + "Request to getAssistIntent for " + userHandle + + " but is calling from user " + UserId.getCallingUserId() + + "; this requires " + + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + return null; + } + } + IPackageManager pm = AppGlobals.getPackageManager(); + Intent assistIntent = new Intent(Intent.ACTION_ASSIST); + ResolveInfo info = + pm.resolveIntent(assistIntent, + assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.MATCH_DEFAULT_ONLY, userHandle); + if (info != null) { + return new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + } + } catch (RemoteException re) { + // Local call + Log.e(TAG, "RemoteException in getAssistIntent: " + re); + } catch (Exception e) { + Log.e(TAG, "Exception in getAssistIntent: " + e); + } + return null; + } } diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java index fcadad7dfbe8..47e2129af0e1 100644 --- a/core/java/android/speech/tts/BlockingAudioTrack.java +++ b/core/java/android/speech/tts/BlockingAudioTrack.java @@ -67,12 +67,10 @@ class BlockingAudioTrack { private int mAudioBufferSize; private int mBytesWritten = 0; - private AudioTrack mAudioTrack; + // Need to be seen by stop() which can be called from another thread. mAudioTrack will be + // set to null only after waitAndRelease(). + private volatile AudioTrack mAudioTrack; private volatile boolean mStopped; - // Locks the initialization / uninitialization of the audio track. - // This is required because stop() will throw an illegal state exception - // if called before init() or after mAudioTrack.release(). - private final Object mAudioTrackLock = new Object(); BlockingAudioTrack(int streamType, int sampleRate, int audioFormat, int channelCount, @@ -93,19 +91,21 @@ class BlockingAudioTrack { mStopped = false; } - public void init() { + public boolean init() { AudioTrack track = createStreamingAudioTrack(); + mAudioTrack = track; - synchronized (mAudioTrackLock) { - mAudioTrack = track; + if (track == null) { + return false; + } else { + return true; } } public void stop() { - synchronized (mAudioTrackLock) { - if (mAudioTrack != null) { - mAudioTrack.stop(); - } + AudioTrack track = mAudioTrack; + if (track != null) { + track.stop(); } mStopped = true; } @@ -120,6 +120,12 @@ class BlockingAudioTrack { } public void waitAndRelease() { + AudioTrack track = mAudioTrack; + if (track == null) { + if (DBG) Log.d(TAG, "Audio track null [duplicate call to waitAndRelease ?]"); + return; + } + // For "small" audio tracks, we have to stop() them to make them mixable, // else the audio subsystem will wait indefinitely for us to fill the buffer // before rendering the track mixable. @@ -129,11 +135,11 @@ class BlockingAudioTrack { if (mBytesWritten < mAudioBufferSize && !mStopped) { if (DBG) { Log.d(TAG, "Stopping audio track to flush audio, state was : " + - mAudioTrack.getPlayState() + ",stopped= " + mStopped); + track.getPlayState() + ",stopped= " + mStopped); } mIsShortUtterance = true; - mAudioTrack.stop(); + track.stop(); } // Block until the audio track is done only if we haven't stopped yet. @@ -145,11 +151,9 @@ class BlockingAudioTrack { // The last call to AudioTrack.write( ) will return only after // all data from the audioTrack has been sent to the mixer, so // it's safe to release at this point. - if (DBG) Log.d(TAG, "Releasing audio track [" + mAudioTrack.hashCode() + "]"); - synchronized (mAudioTrackLock) { - mAudioTrack.release(); - mAudioTrack = null; - } + if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]"); + track.release(); + mAudioTrack = null; } diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java index 04c33775d5c1..3e33e8e4b527 100644 --- a/core/java/android/speech/tts/FileSynthesisCallback.java +++ b/core/java/android/speech/tts/FileSynthesisCallback.java @@ -95,6 +95,22 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { } } + /** + * Checks whether a given file exists, and deletes it if it does. + */ + private boolean maybeCleanupExistingFile(File file) { + if (file.exists()) { + Log.v(TAG, "File " + file + " exists, deleting."); + if (!file.delete()) { + Log.e(TAG, "Failed to delete " + file); + return false; + } + } + + return true; + } + + @Override public int getMaxBufferSize() { return MAX_AUDIO_BUFFER_SIZE; @@ -120,6 +136,11 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { cleanUp(); throw new IllegalArgumentException("FileSynthesisRequest.start() called twice"); } + + if (!maybeCleanupExistingFile(mFileName)) { + return TextToSpeech.ERROR; + } + mSampleRateInHz = sampleRateInHz; mAudioFormat = audioFormat; mChannelCount = channelCount; @@ -166,6 +187,12 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { public int done() { if (DBG) Log.d(TAG, "FileSynthesisRequest.done()"); synchronized (mStateLock) { + if (mDone) { + if (DBG) Log.d(TAG, "Duplicate call to done()"); + // This preserves existing behaviour. Earlier, if done was called twice + // we'd return ERROR because mFile == null and we'd add to logspam. + return TextToSpeech.ERROR; + } if (mStopped) { if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java index d299d70421f5..e853c9e36379 100644 --- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java +++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java @@ -87,7 +87,10 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { dispatcher.dispatchOnStart(); - mAudioTrack.init(); + if (!mAudioTrack.init()) { + dispatcher.dispatchOnError(); + return; + } try { byte[] buffer = null; @@ -242,4 +245,3 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { } } } - diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 4c1a0afcdf1d..d124e68a253e 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -549,7 +549,7 @@ public abstract class TextToSpeechService extends Service { @Override public boolean isValid() { if (mText == null) { - Log.wtf(TAG, "Got null text"); + Log.e(TAG, "null synthesis text"); return false; } if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) { @@ -641,14 +641,6 @@ public abstract class TextToSpeechService extends Service { } @Override - public boolean isValid() { - if (!super.isValid()) { - return false; - } - return checkFile(mFile); - } - - @Override protected AbstractSynthesisCallback createSynthesisCallback() { return new FileSynthesisCallback(mFile); } @@ -664,33 +656,6 @@ public abstract class TextToSpeechService extends Service { } return status; } - - /** - * Checks that the given file can be used for synthesis output. - */ - private boolean checkFile(File file) { - try { - if (file.exists()) { - Log.v(TAG, "File " + file + " exists, deleting."); - if (!file.delete()) { - Log.e(TAG, "Failed to delete " + file); - return false; - } - } - if (!file.createNewFile()) { - Log.e(TAG, "Can't create file " + file); - return false; - } - if (!file.delete()) { - Log.e(TAG, "Failed to delete " + file); - return false; - } - return true; - } catch (IOException e) { - Log.e(TAG, "Can't use " + file + " due to exception " + e); - return false; - } - } } private class AudioSpeechItem extends SpeechItem { diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 270624cbfb17..83a70f3fce0d 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1042,9 +1042,14 @@ public class TextUtils { float avail, TruncateAt where, boolean preserveLength, EllipsizeCallback callback) { + + final String ellipsis = (where == TruncateAt.END_SMALL) ? + Resources.getSystem().getString(R.string.ellipsis_two_dots) : + Resources.getSystem().getString(R.string.ellipsis); + return ellipsize(text, paint, avail, where, preserveLength, callback, TextDirectionHeuristics.FIRSTSTRONG_LTR, - (where == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL); + ellipsis); } /** @@ -1700,9 +1705,4 @@ public class TextUtils { private static String[] EMPTY_STRING_ARRAY = new String[]{}; private static final char ZWNBS_CHAR = '\uFEFF'; - - private static final String ELLIPSIS_NORMAL = Resources.getSystem().getString( - R.string.ellipsis); - private static final String ELLIPSIS_TWO_DOTS = Resources.getSystem().getString( - R.string.ellipsis_two_dots); } diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index 626f385551f7..6c733f9e11a8 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -18,6 +18,7 @@ package android.view; import android.content.Context; import android.content.ContextWrapper; +import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; @@ -30,6 +31,8 @@ public class ContextThemeWrapper extends ContextWrapper { private int mThemeResource; private Resources.Theme mTheme; private LayoutInflater mInflater; + private Configuration mOverrideConfiguration; + private Resources mResources; public ContextThemeWrapper() { super(null); @@ -45,6 +48,41 @@ public class ContextThemeWrapper extends ContextWrapper { super.attachBaseContext(newBase); mBase = newBase; } + + /** + * Call to set an "override configuration" on this context -- this is + * a configuration that replies one or more values of the standard + * configuration that is applied to the context. See + * {@link Context#createConfigurationContext(Configuration)} for more + * information. + * + * <p>This method can only be called once, and must be called before any + * calls to {@link #getResources()} are made. + */ + public void applyOverrideConfiguration(Configuration overrideConfiguration) { + if (mResources != null) { + throw new IllegalStateException("getResources() has already been called"); + } + if (mOverrideConfiguration != null) { + throw new IllegalStateException("Override configuration has already been set"); + } + mOverrideConfiguration = new Configuration(overrideConfiguration); + } + + @Override + public Resources getResources() { + if (mResources != null) { + return mResources; + } + if (mOverrideConfiguration == null) { + mResources = super.getResources(); + return mResources; + } else { + Context resc = createConfigurationContext(mOverrideConfiguration); + mResources = resc.getResources(); + return mResources; + } + } @Override public void setTheme(int resid) { mThemeResource = resid; diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 7eab72d74e2b..e98000a20b50 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -155,7 +155,7 @@ <string name="global_action_lock" msgid="2844945191792119712">"Verrouillage de l\'écran"</string> <string name="global_action_power_off" msgid="4471879440839879722">"Éteindre"</string> <string name="global_action_bug_report" msgid="7934010578922304799">"Rapport de bug"</string> - <string name="bugreport_title" msgid="2667494803742548533">"Établir un rapport de bug"</string> + <string name="bugreport_title" msgid="2667494803742548533">"Créer un rapport de bug"</string> <string name="bugreport_message" msgid="398447048750350456">"Cela permet de recueillir des informations concernant l\'état actuel de votre appareil. Ces informations sont ensuite envoyées sous forme d\'e-mail. Merci de patienter pendant la préparation du rapport de bug. Cette opération peut prendre quelques instants."</string> <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Mode silencieux"</string> <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Le son est désactivé."</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 093c19d68247..cf8fa0169b57 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1075,7 +1075,7 @@ <string name="usb_storage_error_message" product="nosdcard" msgid="3017045217365540658">"USB विशाल संग्रहण के लिए आपके USB संग्रहण का उपयोग करने में समस्या है."</string> <string name="usb_storage_error_message" product="default" msgid="2876018512716970313">"USB विशाल संग्रहण के लिए आपके SD कार्ड का उपयोग करने में समस्या है."</string> <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB कनेक्ट किया गया"</string> - <string name="usb_storage_notification_message" msgid="939822783828183763">"आपके कंप्यूटर में/से फ़ाइल की प्रतिलिपि बनाने के लिए चयन करें."</string> + <string name="usb_storage_notification_message" msgid="939822783828183763">"आपके कंप्यूटर में/से फ़ाइल की प्रतिलिपि बनाने के लिए चुनें."</string> <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB संग्रहण बंद करें"</string> <string name="usb_storage_stop_notification_message" msgid="1656852098555623822">"USB संग्रहण बंद करने के लिए स्पर्श करें."</string> <string name="usb_storage_stop_title" msgid="660129851708775853">"USB संग्रहण उपयोग में है"</string> diff --git a/docs/html/design/downloads/index.jd b/docs/html/design/downloads/index.jd index be75800c8d34..4503098240c9 100644 --- a/docs/html/design/downloads/index.jd +++ b/docs/html/design/downloads/index.jd @@ -12,7 +12,7 @@ You can also download individual files listed below.</p> <div class="layout-content-col span-4"> <p> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Downloads_20120814.zip">Download All</a> + <a class="download-button" href="{@docRoot}downloads/design/Android_Design_Downloads_20120814.zip">Download All</a> </p> </div> @@ -37,10 +37,10 @@ available.</p> <div class="layout-content-col span-4"> <p> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Fireworks_Stencil_20120814.png">Adobe® Fireworks® PNG Stencil</a> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Illustrator_Vectors_20120814.ai">Adobe® Illustrator® Stencil</a> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_OmniGraffle_Stencil_20120814.graffle">Omni® OmniGraffle® Stencil</a> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Holo_Widgets_20120814.zip">Adobe® Photoshop® Sources</a> + <a class="download-button" href="{@docRoot}downloads/design/Android_Design_Fireworks_Stencil_20120814.png">Adobe® Fireworks® PNG Stencil</a> + <a class="download-button" href="{@docRoot}downloads/design/Android_Design_Illustrator_Vectors_20120814.ai">Adobe® Illustrator® Stencil</a> + <a class="download-button" href="{@docRoot}downloads/design/Android_Design_OmniGraffle_Stencil_20120814.graffle">Omni® OmniGraffle® Stencil</a> + <a class="download-button" href="{@docRoot}downloads/design/Android_Design_Holo_Widgets_20120814.zip">Adobe® Photoshop® Sources</a> </p> </div> @@ -66,7 +66,7 @@ modify to match your theme, plus source files.</p> <div class="layout-content-col span-4"> <p> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Icons_20120814.zip">Action Bar Icon Pack</a> + <a class="download-button" href="{@docRoot}downloads/design/Android_Design_Icons_20120814.zip">Action Bar Icon Pack</a> </p> </div> @@ -91,8 +91,8 @@ requirements of UI and high-resolution screens.</p> <div class="layout-content-col span-4"> <p> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Roboto_Hinted_20111129.zip">Roboto</a> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a> + <a class="download-button" href="{@docRoot}downloads/design/Roboto_Hinted_20111129.zip">Roboto</a> + <a class="download-button" href="{@docRoot}downloads/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a> </p> </div> @@ -115,7 +115,7 @@ shade that can be used as a complement when needed.</p> <div class="layout-content-col span-4"> <p> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Color_Swatches_20120229.zip">Color Swatches</a> + <a class="download-button" href="{@docRoot}downloads/design/Android_Design_Color_Swatches_20120229.zip">Color Swatches</a> </p> </div> diff --git a/docs/html/design/patterns/actionbar.jd b/docs/html/design/patterns/actionbar.jd index 9ceb499da739..ba5b4002a8c4 100644 --- a/docs/html/design/patterns/actionbar.jd +++ b/docs/html/design/patterns/actionbar.jd @@ -293,7 +293,7 @@ files for further customization. </p> <p> -<a href="https://dl-ssl.google.com/android/design/Android_Design_Icons_20120229.zip">Download the Action Bar Icon Pack</a> +<a href="{@docRoot}downloads/design/Android_Design_Icons_20120229.zip">Download the Action Bar Icon Pack</a> </p> diff --git a/docs/html/design/style/color.jd b/docs/html/design/style/color.jd index 9c7b6b6f0acc..5be34ac5f64d 100644 --- a/docs/html/design/style/color.jd +++ b/docs/html/design/style/color.jd @@ -115,7 +115,7 @@ between visual components. Note that red and green may be indistinguishable to c <p>Blue is the standard accent color in Android's color palette. Each color has a corresponding darker shade that can be used as a complement when needed.</p> -<p><a href="https://dl-ssl.google.com/android/design/Android_Design_Color_Swatches_20120229.zip">Download the swatches</a></p> +<p><a href="{@docRoot}downloads/design/Android_Design_Color_Swatches_20120229.zip">Download the swatches</a></p> <img src="{@docRoot}design/media/color_spectrum.png"> diff --git a/docs/html/design/style/iconography.jd b/docs/html/design/style/iconography.jd index 775e45d9fdbb..31274c51d5ee 100644 --- a/docs/html/design/style/iconography.jd +++ b/docs/html/design/style/iconography.jd @@ -110,7 +110,7 @@ files for further customization. </p> <p> -<a href="https://dl-ssl.google.com/android/design/Android_Design_Icons_20120229.zip">Download the Action Bar Icon Pack</a> +<a href="{@docRoot}downloads/design/Android_Design_Icons_20120229.zip">Download the Action Bar Icon Pack</a> </p> diff --git a/docs/html/design/style/typography.jd b/docs/html/design/style/typography.jd index db2fb5ff3301..a699bed88b32 100644 --- a/docs/html/design/style/typography.jd +++ b/docs/html/design/style/typography.jd @@ -18,8 +18,8 @@ italic weights by default.</p> <img src="{@docRoot}design/media/typography_alphas.png"> -<p><a href="https://dl-ssl.google.com/android/design/Roboto_Hinted_20111129.zip">Download Roboto</a></p> -<p><a href="https://dl-ssl.google.com/android/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a></p> +<p><a href="{@docRoot}downloads/design/Roboto_Hinted_20111129.zip">Download Roboto</a></p> +<p><a href="{@docRoot}downloads/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a></p> </div> </div> diff --git a/docs/html/guide/topics/media/camera.jd b/docs/html/guide/topics/media/camera.jd index a63270a77ead..3fe23f8ce73a 100644 --- a/docs/html/guide/topics/media/camera.jd +++ b/docs/html/guide/topics/media/camera.jd @@ -617,7 +617,7 @@ public class CameraActivity extends Activity { // Create our Preview view and set it as the content of our activity. mPreview = new CameraPreview(this, mCamera); - FrameLayout preview = (FrameLayout) findViewById(id.camera_preview); + FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview); preview.addView(mPreview); } } diff --git a/docs/html/guide/topics/ui/controls/radiobutton.jd b/docs/html/guide/topics/ui/controls/radiobutton.jd index f6f6d49f7294..c96e57649abc 100644 --- a/docs/html/guide/topics/ui/controls/radiobutton.jd +++ b/docs/html/guide/topics/ui/controls/radiobutton.jd @@ -72,7 +72,7 @@ click event for both radio buttons:</p> <pre> public void onRadioButtonClicked(View view) { // Is the button now checked? - boolean checked = (RadioButton) view).isChecked(); + boolean checked = ((RadioButton) view).isChecked(); // Check which radio button was clicked switch(view.getId()) { diff --git a/docs/html/guide/topics/ui/settings.jd b/docs/html/guide/topics/ui/settings.jd index fd3b684e2c8f..33e164b1f733 100644 --- a/docs/html/guide/topics/ui/settings.jd +++ b/docs/html/guide/topics/ui/settings.jd @@ -217,7 +217,7 @@ item in the list of settings.</p> android:dialogTitle="@string/pref_syncConnectionType" android:entries="@array/pref_syncConnectionTypes_entries" android:entryValues="@array/pref_syncConnectionTypes_values" - android:defaultValue="@string/pref_syncConnectionTypes_default" > + android:defaultValue="@string/pref_syncConnectionTypes_default" /> </PreferenceScreen> </pre> diff --git a/docs/html/training/basics/activity-lifecycle/starting.jd b/docs/html/training/basics/activity-lifecycle/starting.jd index 1a4bc2da5b4f..dd17304d8102 100644 --- a/docs/html/training/basics/activity-lifecycle/starting.jd +++ b/docs/html/training/basics/activity-lifecycle/starting.jd @@ -112,7 +112,7 @@ methods.</p> </table> --> -<p>As you'll learn in the following lessons, there are several situtations in which an activity +<p>As you'll learn in the following lessons, there are several situations in which an activity transitions between different states that are illustrated in figure 1. However, only three of these states can be static. That is, the activity can exist in one of only three states for an extended period of time:</p> diff --git a/docs/html/training/basics/firstapp/starting-activity.jd b/docs/html/training/basics/firstapp/starting-activity.jd index 4d0a84a6133b..3dafcfa3f96b 100644 --- a/docs/html/training/basics/firstapp/starting-activity.jd +++ b/docs/html/training/basics/firstapp/starting-activity.jd @@ -285,8 +285,8 @@ href="{@docRoot}tools/extras/support-library.html#SettingUp">setting up the Supp <p>The app is now runnable because the {@link android.content.Intent} in the first activity now resolves to the {@code DisplayMessageActivity} class. If you run the app now, -clicking the Send button starts the -second activity, but it doesn't show anything yet.</p> +clicking the Send button starts the second activity, but it's still using the default +"Hello world" layout.</p> <h2 id="ReceiveIntent">Receive the Intent</h2> diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index 2032f6756895..af8b0c29d2a0 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -561,6 +561,12 @@ public class RenderScript { return rsnScriptCCreate(mContext, resName, cacheDir, script, length); } + native int rsnScriptIntrinsicCreate(int con, int id, int eid); + synchronized int nScriptIntrinsicCreate(int id, int eid) { + validate(); + return rsnScriptIntrinsicCreate(mContext, id, eid); + } + native int rsnSamplerCreate(int con, int magFilter, int minFilter, int wrapS, int wrapT, int wrapR, float aniso); synchronized int nSamplerCreate(int magFilter, int minFilter, diff --git a/graphics/java/android/renderscript/ScriptIntrinsic.java b/graphics/java/android/renderscript/ScriptIntrinsic.java new file mode 100644 index 000000000000..6ad1527d05af --- /dev/null +++ b/graphics/java/android/renderscript/ScriptIntrinsic.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.renderscript; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; + + +/** + * @hide + **/ +public class ScriptIntrinsic extends Script { + ScriptIntrinsic(int id, RenderScript rs) { + super(id, rs); + } + + public void forEach(Allocation ain, Allocation aout) { + forEach(0, ain, aout, null); + } +} diff --git a/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java b/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java new file mode 100644 index 000000000000..0ae144980062 --- /dev/null +++ b/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java @@ -0,0 +1,65 @@ +/* + * 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.renderscript; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map.Entry; +import java.util.HashMap; + + +/** + * @hide + **/ +public class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { + private float[] mValues = new float[9]; + + ScriptIntrinsicConvolve3x3(int id, RenderScript rs) { + super(id, rs); + } + + /** + * Supported elements types are float, float4, uchar, uchar4 + * + * + * @param rs + * @param e + * + * @return ScriptIntrinsicConvolve3x3 + */ + public static ScriptIntrinsicConvolve3x3 create(RenderScript rs, Element e) { + int id = rs.nScriptIntrinsicCreate(1, e.getID(rs)); + return new ScriptIntrinsicConvolve3x3(id, rs); + + } + + + public void setValues(float v[]) { + FieldPacker fp = new FieldPacker(9*4); + for (int ct=0; ct < mValues.length; ct++) { + mValues[ct] = v[ct]; + fp.addF32(mValues[ct]); + } + setVar(0, fp); + } +} + diff --git a/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java b/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java new file mode 100644 index 000000000000..ee5f938ec436 --- /dev/null +++ b/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.renderscript; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map.Entry; +import java.util.HashMap; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * @hide + **/ +public class ScriptIntrinsicYuvToRGB extends ScriptIntrinsic { + ScriptIntrinsicYuvToRGB(int id, RenderScript rs) { + super(id, rs); + } + + + + public static class Builder { + RenderScript mRS; + + public Builder(RenderScript rs) { + mRS = rs; + } + + public void setInputFormat(int inputFormat) { + + } + + public void setOutputFormat(Element e) { + + } + + public ScriptIntrinsicYuvToRGB create() { + return null; + + } + + } + +} diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp index 09f69527cd41..a073c1aef8f7 100644 --- a/graphics/jni/android_renderscript_RenderScript.cpp +++ b/graphics/jni/android_renderscript_RenderScript.cpp @@ -1071,6 +1071,13 @@ exit: return ret; } +static jint +nScriptIntrinsicCreate(JNIEnv *_env, jobject _this, RsContext con, jint id, jint eid) +{ + LOG_API("nScriptIntrinsicCreate, con(%p) id(%i) element(%p)", con, id, (void *)eid); + return (jint)rsScriptIntrinsicCreate(con, id, (RsElement)eid); +} + // --------------------------------------------------------------------------- static jint @@ -1412,6 +1419,7 @@ static JNINativeMethod methods[] = { {"rsnScriptSetVarObj", "(IIII)V", (void*)nScriptSetVarObj }, {"rsnScriptCCreate", "(ILjava/lang/String;Ljava/lang/String;[BI)I", (void*)nScriptCCreate }, +{"rsnScriptIntrinsicCreate", "(III)I", (void*)nScriptIntrinsicCreate }, {"rsnProgramStoreCreate", "(IZZZZZZIII)I", (void*)nProgramStoreCreate }, diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 95fc2c5b797d..2de70d462bea 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -1699,7 +1699,9 @@ status_t DisplayListRenderer::drawTextOnPath(const char* text, int bytesCount, i addFloat(hOffset); addFloat(vOffset); paint->setAntiAlias(true); - addPaint(paint); + SkPaint* addedPaint = addPaint(paint); + FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint); + fontRenderer.precache(addedPaint, text, count); return DrawGlInfo::kStatusDone; } @@ -1711,7 +1713,9 @@ status_t DisplayListRenderer::drawPosText(const char* text, int bytesCount, int addInt(count); addFloats(positions, count * 2); paint->setAntiAlias(true); - addPaint(paint); + SkPaint* addedPaint = addPaint(paint); + FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint); + fontRenderer.precache(addedPaint, text, count); return DrawGlInfo::kStatusDone; } @@ -1742,7 +1746,11 @@ status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int cou addFloat(x); addFloat(y); addFloats(positions, count * 2); - addPaint(paint); + SkPaint* addedPaint = addPaint(paint); + if (!reject) { + FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint); + fontRenderer.precache(addedPaint, text, count); + } addFloat(length); addSkip(location); return DrawGlInfo::kStatusDone; diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 60a40c6b76be..c8b3e479a017 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -770,10 +770,10 @@ private: addInt((int) pathCopy); } - inline void addPaint(SkPaint* paint) { + inline SkPaint* addPaint(SkPaint* paint) { if (!paint) { addInt((int) NULL); - return; + return paint; } SkPaint* paintCopy = mPaintMap.valueFor(paint); @@ -785,6 +785,8 @@ private: } addInt((int) paintCopy); + + return paintCopy; } inline void addDisplayList(DisplayList* displayList) { diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index ccddd9155975..a596fa946467 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -37,11 +37,78 @@ namespace uirenderer { #define DEFAULT_TEXT_CACHE_WIDTH 1024 #define DEFAULT_TEXT_CACHE_HEIGHT 256 #define MAX_TEXT_CACHE_WIDTH 2048 -#define TEXTURE_BORDER_SIZE 1 +#define CACHE_BLOCK_ROUNDING_SIZE 4 #define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16) /////////////////////////////////////////////////////////////////////////////// +// CacheBlock +/////////////////////////////////////////////////////////////////////////////// + +/** + * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width + * order, except for the final block (the remainder space at the right, since we fill from the + * left). + */ +CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock *newBlock) { +#if DEBUG_FONT_RENDERER + ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", + newBlock, newBlock->mX, newBlock->mY, + newBlock->mWidth, newBlock->mHeight); +#endif + CacheBlock *currBlock = head; + CacheBlock *prevBlock = NULL; + while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) { + if (newBlock->mWidth < currBlock->mWidth) { + newBlock->mNext = currBlock; + newBlock->mPrev = prevBlock; + currBlock->mPrev = newBlock; + if (prevBlock) { + prevBlock->mNext = newBlock; + return head; + } else { + return newBlock; + } + } + prevBlock = currBlock; + currBlock = currBlock->mNext; + } + // new block larger than all others - insert at end (but before the remainder space, if there) + newBlock->mNext = currBlock; + newBlock->mPrev = prevBlock; + if (currBlock) { + currBlock->mPrev = newBlock; + } + if (prevBlock) { + prevBlock->mNext = newBlock; + return head; + } else { + return newBlock; + } +} + +CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock *blockToRemove) { +#if DEBUG_FONT_RENDERER + ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", + blockToRemove, blockToRemove->mX, blockToRemove->mY, + blockToRemove->mWidth, blockToRemove->mHeight); +#endif + CacheBlock* newHead = head; + CacheBlock* nextBlock = blockToRemove->mNext; + CacheBlock* prevBlock = blockToRemove->mPrev; + if (prevBlock) { + prevBlock->mNext = nextBlock; + } else { + newHead = nextBlock; + } + if (nextBlock) { + nextBlock->mPrev = prevBlock; + } + delete blockToRemove; + return newHead; +} + +/////////////////////////////////////////////////////////////////////////////// // CacheTextureLine /////////////////////////////////////////////////////////////////////////////// @@ -50,14 +117,73 @@ bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uin return false; } - if (mCurrentCol + glyph.fWidth + TEXTURE_BORDER_SIZE * 2 < mMaxWidth) { - *retOriginX = mCurrentCol + TEXTURE_BORDER_SIZE; - *retOriginY = mCurrentRow + TEXTURE_BORDER_SIZE; - mCurrentCol += glyph.fWidth + TEXTURE_BORDER_SIZE * 2; - mDirty = true; - return true; + uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE; + uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE; + // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE. + // This columns for glyphs that are close but not necessarily exactly the same size. It trades + // off the loss of a few pixels for some glyphs against the ability to store more glyphs + // of varying sizes in one block. + uint16_t roundedUpW = + (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE; + CacheBlock *cacheBlock = mCacheBlocks; + while (cacheBlock) { + // Store glyph in this block iff: it fits the block's remaining space and: + // it's the remainder space (mY == 0) or there's only enough height for this one glyph + // or it's within ROUNDING_SIZE of the block width + if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight && + (cacheBlock->mY == TEXTURE_BORDER_SIZE || + (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) { + if (cacheBlock->mHeight - glyphH < glyphH) { + // Only enough space for this glyph - don't bother rounding up the width + roundedUpW = glyphW; + } + *retOriginX = cacheBlock->mX; + *retOriginY = mCurrentRow + cacheBlock->mY; + // If this is the remainder space, create a new cache block for this column. Otherwise, + // adjust the info about this column. + if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { + uint16_t oldX = cacheBlock->mX; + // Adjust remainder space dimensions + cacheBlock->mWidth -= roundedUpW; + cacheBlock->mX += roundedUpW; + if (mMaxHeight - glyphH >= glyphH) { + // There's enough height left over to create a new CacheBlock + CacheBlock *newBlock = new CacheBlock(oldX, glyphH, roundedUpW, + mMaxHeight - glyphH); +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", + newBlock, newBlock->mX, newBlock->mY, + newBlock->mWidth, newBlock->mHeight); +#endif + mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock); + } + } else { + // Insert into current column and adjust column dimensions + cacheBlock->mY += glyphH; + cacheBlock->mHeight -= glyphH; +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d", + cacheBlock, cacheBlock->mX, cacheBlock->mY, + cacheBlock->mWidth, cacheBlock->mHeight); +#endif + } + if (cacheBlock->mHeight < fmin(glyphH, glyphW)) { + // If remaining space in this block is too small to be useful, remove it + mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock); + } + mDirty = true; +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: current block list:"); + mCacheBlocks->output(); +#endif + ++mNumGlyphs; + return true; + } + cacheBlock = cacheBlock->mNext; } - +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH); +#endif return false; } @@ -297,6 +423,27 @@ void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t le render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, positions); } +void Font::precache(SkPaint* paint, const char* text, int numGlyphs) { + + if (numGlyphs == 0 || text == NULL) { + return; + } + int glyphsCount = 0; + + while (glyphsCount < numGlyphs) { + glyph_t glyph = GET_GLYPH(text); + + // Reached the end of the string + if (IS_END_OF_STRING(glyph)) { + break; + } + + CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); + + glyphsCount++; + } +} + void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) { @@ -545,9 +692,33 @@ void FontRenderer::flushAllAndInvalidate() { mActiveFonts[i]->invalidateTextureCache(); } + uint16_t totalGlyphs = 0; for (uint32_t i = 0; i < mCacheLines.size(); i++) { - mCacheLines[i]->mCurrentCol = 0; + totalGlyphs += mCacheLines[i]->mNumGlyphs; + mCacheLines[i]->init(); + } + +#if DEBUG_FONT_RENDERER + ALOGD("FontRenderer: flushAllAndInvalidatel"); + // Erase caches, just as a debugging facility + if (mCacheTextureSmall && mCacheTextureSmall->mTexture) { + memset(mCacheTextureSmall->mTexture, 0, + mCacheTextureSmall->mWidth * mCacheTextureSmall->mHeight); + } + if (mCacheTexture128 && mCacheTexture128->mTexture) { + memset(mCacheTexture128->mTexture, 0, + mCacheTexture128->mWidth * mCacheTexture128->mHeight); } + if (mCacheTexture256 && mCacheTexture256->mTexture) { + memset(mCacheTexture256->mTexture, 0, + mCacheTexture256->mWidth * mCacheTexture256->mHeight); + } + if (mCacheTexture512 && mCacheTexture512->mTexture) { + memset(mCacheTexture512->mTexture, 0, + mCacheTexture512->mWidth * mCacheTexture512->mHeight); + } + ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs); +#endif } void FontRenderer::deallocateTextureMemory(CacheTexture *cacheTexture) { @@ -573,7 +744,16 @@ void FontRenderer::flushLargeCaches() { cacheLine->mCacheTexture == mCacheTexture256 || cacheLine->mCacheTexture == mCacheTexture512) && cacheLine->mCacheTexture->mTexture != NULL) { - cacheLine->mCurrentCol = 0; +#if DEBUG_FONT_RENDERER + if (cacheLine->mCacheTexture == mCacheTexture128) { + ALOGD("flushing cacheTexture128"); + } else if (cacheLine->mCacheTexture == mCacheTexture256) { + ALOGD("flushing cacheTexture256"); + } else { + ALOGD("flushing cacheTexture512"); + } +#endif + cacheLine->init(); for (uint32_t i = 0; i < mActiveFonts.size(); i++) { mActiveFonts[i]->invalidateTextureCache(cacheLine); } @@ -614,9 +794,12 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t* retOriginX, uint32_t* retOriginY) { cachedGlyph->mIsValid = false; // If the glyph is too tall, don't cache it - if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { - ALOGE("Font size to large to fit in cache. width, height = %i, %i", - (int) glyph.fWidth, (int) glyph.fHeight); + if (mCacheLines.size() == 0 || + glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { + if (mCacheLines.size() != 0) { + ALOGE("Font size too large to fit in cache. width, height = %i, %i", + (int) glyph.fWidth, (int) glyph.fHeight); + } return; } @@ -747,26 +930,26 @@ void FontRenderer::initTextTexture() { mUploadTexture = false; // Split up our default cache texture into lines of certain widths int nextLine = 0; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 18, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 18, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, mSmallCacheHeight - nextLine, - nextLine, 0, mCacheTextureSmall)); + nextLine, mCacheTextureSmall)); // The first cache is split into 2 lines of height 128, the rest have just one cache line. - mCacheLines.push(new CacheTextureLine(maxWidth, 128, 0, 0, mCacheTexture128)); - mCacheLines.push(new CacheTextureLine(maxWidth, 128, 128, 0, mCacheTexture128)); - mCacheLines.push(new CacheTextureLine(maxWidth, 256, 0, 0, mCacheTexture256)); - mCacheLines.push(new CacheTextureLine(maxWidth, 512, 0, 0, mCacheTexture512)); + mCacheLines.push(new CacheTextureLine(maxWidth, 128, 0, mCacheTexture128)); + mCacheLines.push(new CacheTextureLine(maxWidth, 128, 128, mCacheTexture128)); + mCacheLines.push(new CacheTextureLine(maxWidth, 256, 0, mCacheTexture256)); + mCacheLines.push(new CacheTextureLine(maxWidth, 512, 0, mCacheTexture512)); } // Avoid having to reallocate memory and render quad by quad @@ -837,6 +1020,10 @@ void FontRenderer::checkTextureUpdate() { glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId); lastTextureId = cacheTexture->mTextureId; } +#if DEBUG_FONT_RENDERER + ALOGD("glTextSubimage for cacheLine %d: xOff, yOff, width height = %d, %d, %d, %d", i, + xOffset, yOffset, width, height); +#endif glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, textureData); @@ -960,43 +1147,7 @@ void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, } } -uint32_t FontRenderer::getRemainingCacheCapacity() { - uint32_t remainingCapacity = 0; - float totalPixels = 0; - - //avoid divide by zero if the size is 0 - if (mCacheLines.size() == 0) { - return 0; - } - for(uint32_t i = 0; i < mCacheLines.size(); i ++) { - remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol); - totalPixels += mCacheLines[i]->mMaxWidth; - } - remainingCapacity = (remainingCapacity * 100) / totalPixels; - return remainingCapacity; -} - -void FontRenderer::precacheLatin(SkPaint* paint) { - // Remaining capacity is measured in % - uint32_t remainingCapacity = getRemainingCacheCapacity(); - uint32_t precacheIndex = 0; - - // We store a string with letters in a rough frequency of occurrence - String16 l("eisarntolcdugpmhbyfvkwzxjq EISARNTOLCDUGPMHBYFVKWZXJQ,.?!()-+@;:'0123456789"); - - size_t size = l.size(); - uint16_t latin[size]; - paint->utfToGlyphs(l.string(), SkPaint::kUTF16_TextEncoding, size * sizeof(char16_t), latin); - - while (remainingCapacity > 25 && precacheIndex < size) { - mCurrentFont->getCachedGlyph(paint, TO_GLYPH(latin[precacheIndex])); - remainingCapacity = getRemainingCacheCapacity(); - precacheIndex++; - } -} - void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { - uint32_t currentNumFonts = mActiveFonts.size(); int flags = 0; if (paint->isFakeBoldText()) { flags |= Font::kFakeBold; @@ -1012,12 +1163,6 @@ void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle, scaleX, style, strokeWidth); - const float maxPrecacheFontSize = 40.0f; - bool isNewFont = currentNumFonts != mActiveFonts.size(); - - if (isNewFont && fontSize <= maxPrecacheFontSize) { - precacheLatin(paint); - } } FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, @@ -1084,6 +1229,25 @@ void FontRenderer::finishRender() { } } +void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs) { + int flags = 0; + if (paint->isFakeBoldText()) { + flags |= Font::kFakeBold; + } + const float skewX = paint->getTextSkewX(); + uint32_t italicStyle = *(uint32_t*) &skewX; + const float scaleXFloat = paint->getTextScaleX(); + uint32_t scaleX = *(uint32_t*) &scaleXFloat; + SkPaint::Style style = paint->getStyle(); + const float strokeWidthFloat = paint->getStrokeWidth(); + uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat; + float fontSize = paint->getTextSize(); + Font* font = Font::create(this, SkTypeface::UniqueID(paint->getTypeface()), + fontSize, flags, italicStyle, scaleX, style, strokeWidth); + + font->precache(paint, text, numGlyphs); +} + bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) { if (!mCurrentFont) { diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 9ed69325c914..8b1d10c842e9 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -53,6 +53,8 @@ namespace uirenderer { #define IS_END_OF_STRING(glyph) glyph < 0 #endif +#define TEXTURE_BORDER_SIZE 1 + /////////////////////////////////////////////////////////////////////////////// // Declarations /////////////////////////////////////////////////////////////////////////////// @@ -80,16 +82,79 @@ public: bool mLinearFiltering; }; +/** + * CacheBlock is a noce in a linked list of current free space areas in a CacheTextureLine. + * Using CacheBlocks enables us to pack the cache line from top to bottom as well as left to right. + * When we add a glyph to the cache, we see if it fits within one of the existing columns that + * have already been started (this is the case if the glyph fits vertically as well as + * horizontally, and if its width is sufficiently close to the column width to avoid + * sub-optimal packing of small glyphs into wide columns). If there is no column in which the + * glyph fits, we check the final node, which is the remaining space in the cache line, creating + * a new column as appropriate. + * + * As columns fill up, we remove their CacheBlock from the list to avoid having to check + * small blocks in the future. + */ +struct CacheBlock { + uint16_t mX; + uint16_t mY; + uint16_t mWidth; + uint16_t mHeight; + CacheBlock* mNext; + CacheBlock* mPrev; + + CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height, bool empty = false): + mX(x), mY(y), mWidth(width), mHeight(height), mNext(NULL), mPrev(NULL) + { + } + + static CacheBlock* insertBlock(CacheBlock* head, CacheBlock *newBlock); + + static CacheBlock* removeBlock(CacheBlock* head, CacheBlock *blockToRemove); + + void output() { + CacheBlock *currBlock = this; + while (currBlock) { + ALOGD("Block: this, x, y, w, h = %p, %d, %d, %d, %d", + currBlock, currBlock->mX, currBlock->mY, currBlock->mWidth, currBlock->mHeight); + currBlock = currBlock->mNext; + } + } +}; + class CacheTextureLine { public: CacheTextureLine(uint16_t maxWidth, uint16_t maxHeight, uint32_t currentRow, - uint32_t currentCol, CacheTexture* cacheTexture): + CacheTexture* cacheTexture): mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), - mCurrentCol(currentCol), mDirty(false), + mNumGlyphs(0), mCacheTexture(cacheTexture) { + mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, + maxWidth - TEXTURE_BORDER_SIZE, maxHeight - TEXTURE_BORDER_SIZE, true); + } + + ~CacheTextureLine() { + reset(); + } + + void reset() { + // Delete existing cache blocks + while (mCacheBlocks != NULL) { + CacheBlock* tmpBlock = mCacheBlocks; + mCacheBlocks = mCacheBlocks->mNext; + delete tmpBlock; + } + mNumGlyphs = 0; + } + + void init() { + // reset, then create a new remainder space to start again + reset(); + mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, + mMaxWidth - TEXTURE_BORDER_SIZE, mMaxHeight - TEXTURE_BORDER_SIZE, true); } bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY); @@ -97,9 +162,10 @@ public: uint16_t mMaxHeight; uint16_t mMaxWidth; uint32_t mCurrentRow; - uint32_t mCurrentCol; bool mDirty; + uint16_t mNumGlyphs; CacheTexture* mCacheTexture; + CacheBlock* mCacheBlocks; }; struct CachedGlyphInfo { @@ -179,6 +245,8 @@ protected: MEASURE, }; + void precache(SkPaint* paint, const char* text, int numGlyphs); + void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions); @@ -244,6 +312,9 @@ public: } void setFont(SkPaint* paint, uint32_t fontId, float fontSize); + + void precache(SkPaint* paint, const char* text, int numGlyphs); + // bounds is an out parameter bool renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds); @@ -327,8 +398,6 @@ protected: void initRender(const Rect* clip, Rect* bounds); void finishRender(); - void precacheLatin(SkPaint* paint); - void issueDrawCommand(); void appendMeshQuadNoClip(float x1, float y1, float u1, float v1, float x2, float y2, float u2, float v2, @@ -347,7 +416,6 @@ protected: uint32_t mSmallCacheHeight; Vector<CacheTextureLine*> mCacheLines; - uint32_t getRemainingCacheCapacity(); Font* mCurrentFont; Vector<Font*> mActiveFonts; diff --git a/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml b/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml index c47833448d50..47c511c8661a 100644 --- a/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml +++ b/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml @@ -31,10 +31,16 @@ android:layout_height="@*android:dimen/status_bar_height" /> - <include layout="@layout/status_bar_expanded" - android:layout_width="@dimen/notification_panel_width" - android:layout_height="0dp" - android:layout_gravity="center_horizontal|top" - /> + <com.android.systemui.statusbar.phone.PanelHolder + android:id="@+id/panel_holder" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + <include layout="@layout/status_bar_expanded" + android:layout_width="@dimen/notification_panel_width" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|top" + /> + </com.android.systemui.statusbar.phone.PanelHolder> </com.android.systemui.statusbar.phone.StatusBarWindowView> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 5841978c2cd0..828dba46b5a3 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -18,12 +18,12 @@ */ --> -<FrameLayout +<com.android.systemui.statusbar.phone.NotificationPanelView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" android:id="@+id/notification_panel" - android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_width="0dp" + android:layout_height="0dp" android:background="@drawable/notification_panel_bg" android:paddingTop="@dimen/notification_panel_padding_top" android:layout_marginLeft="@dimen/notification_panel_margin_left" @@ -42,7 +42,7 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_marginBottom="@dimen/close_handle_underlap" android:orientation="vertical" > @@ -65,7 +65,7 @@ <ScrollView android:id="@+id/scroll" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:fadingEdge="none" android:overScrollMode="always" > @@ -78,7 +78,7 @@ </ScrollView> </LinearLayout> - <com.android.systemui.statusbar.phone.CloseDragHandle android:id="@+id/close" + <LinearLayout android:id="@+id/handle" android:layout_width="match_parent" android:layout_height="@dimen/close_handle_height" android:layout_gravity="bottom" @@ -91,6 +91,5 @@ android:scaleType="fitXY" android:src="@drawable/status_bar_close" /> - - </com.android.systemui.statusbar.phone.CloseDragHandle> -</FrameLayout><!-- end of sliding panel --> + </LinearLayout> +</com.android.systemui.statusbar.phone.NotificationPanelView><!-- end of sliding panel --> diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index 5bf1a58647f8..ad905bd12afc 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -32,9 +32,15 @@ android:layout_height="@*android:dimen/status_bar_height" /> - <include layout="@layout/status_bar_expanded" + <com.android.systemui.statusbar.phone.PanelHolder + android:id="@+id/panel_holder" android:layout_width="match_parent" - android:layout_height="0dp" - /> + android:layout_height="match_parent" + > + <include layout="@layout/status_bar_expanded" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + </com.android.systemui.statusbar.phone.PanelHolder> </com.android.systemui.statusbar.phone.StatusBarWindowView> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 9bbfc91f7e62..19b64ba61303 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -33,4 +33,5 @@ <drawable name="system_bar_background">#ff000000</drawable> <!-- the darkening filter applied to notifications --> <drawable name="notification_icon_area_smoke">#aa000000</drawable> + <color name="notification_panel_scrim_color">#B0000000</color> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java index 475fb6d20214..b36e71a00cf3 100644 --- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java @@ -24,6 +24,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.os.UserId; import android.os.Vibrator; import android.provider.Settings; import android.util.AttributeSet; @@ -73,14 +74,15 @@ public class SearchPanelView extends FrameLayout implements // Close Recent Apps if needed mBar.animateCollapse(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL); // Launch Assist - Intent intent = SearchManager.getAssistIntent(mContext); + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserId.USER_CURRENT); if (intent == null) return; try { ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, R.anim.search_launch_enter, R.anim.search_launch_exit, getHandler(), this); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(intent, opts.toBundle()); + mContext.startActivityAsUser(intent, opts.toBundle(), UserId.USER_CURRENT); } catch (ActivityNotFoundException e) { Slog.w(TAG, "Activity not found for " + intent.getAction()); onAnimationStarted(); @@ -140,7 +142,8 @@ public class SearchPanelView extends FrameLayout implements } private void maybeSwapSearchIcon() { - Intent intent = SearchManager.getAssistIntent(mContext); + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserId.USER_CURRENT); if (intent != null) { ComponentName component = intent.getComponent(); if (component == null || !mGlowPadView.replaceTargetDrawablesIfPresent(component, @@ -277,6 +280,7 @@ public class SearchPanelView extends FrameLayout implements } public boolean isAssistantAvailable() { - return SearchManager.getAssistIntent(mContext) != null; + return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserId.USER_CURRENT) != null; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java new file mode 100644 index 000000000000..ae7fb274bf12 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -0,0 +1,36 @@ +/* + * 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.systemui.statusbar.phone; + +import android.content.Context; +import android.util.AttributeSet; + +public class NotificationPanelView extends PanelView { + public NotificationPanelView(Context context, AttributeSet attrs) { + super(context, attrs); + android.util.Slog.v("NotificationPanelView", "ctor"); + } + + + @Override + public void fling(float vel, boolean always) { + ((PhoneStatusBarView) mBar).mBar.getGestureRecorder().tag( + "fling " + ((vel > 0) ? "open" : "closed"), + "v=" + vel); + super.fling(vel, always); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java new file mode 100644 index 000000000000..b2c72e867195 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -0,0 +1,115 @@ +package com.android.systemui.statusbar.phone; + +import java.util.ArrayList; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Slog; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +public class PanelBar extends FrameLayout { + public static final boolean DEBUG = true; + public static final String TAG = PanelView.class.getSimpleName(); + public static final void LOG(String fmt, Object... args) { + if (!DEBUG) return; + Slog.v(TAG, String.format(fmt, args)); + } + + private PanelHolder mPanelHolder; + private ArrayList<PanelView> mPanels = new ArrayList<PanelView>(); + private PanelView mTouchingPanel; + + public PanelBar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + } + + public void addPanel(PanelView pv) { + mPanels.add(pv); + pv.setBar(this); + } + + public void setPanelHolder(PanelHolder ph) { + if (ph == null) { + Slog.e(TAG, "setPanelHolder: null PanelHolder", new Throwable()); + return; + } + ph.setBar(this); + mPanelHolder = ph; + final int N = ph.getChildCount(); + for (int i=0; i<N; i++) { + final PanelView v = (PanelView) ph.getChildAt(i); + if (v != null) { + addPanel(v); + } + } + } + + public float getBarHeight() { + return getMeasuredHeight(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // figure out which panel needs to be talked to here + if (event.getAction() == MotionEvent.ACTION_DOWN) { + final int N = mPanels.size(); + final int i = (int)(N * event.getX() / getMeasuredWidth()); + mTouchingPanel = mPanels.get(i); + mPanelHolder.setSelectedPanel(mTouchingPanel); + LOG("PanelBar.onTouch: ACTION_DOWN: panel %d", i); + onPanelPeeked(); + } + final boolean result = mTouchingPanel.getHandle().dispatchTouchEvent(event); + return result; + } + + public void panelExpansionChanged(PanelView panel, float frac) { + boolean fullyClosed = true; + boolean fullyOpened = false; + for (PanelView pv : mPanels) { + if (pv.getExpandedHeight() > 0f) { + fullyClosed = false; + final float thisFrac = pv.getExpandedFraction(); + LOG("panel %s: f=%.1f", pv, thisFrac); + if (panel == pv) { + if (thisFrac == 1f) fullyOpened = true; + } else { + pv.setExpandedFraction(1f-frac); + } + } + } + if (fullyOpened) onPanelFullyOpened(); + if (fullyClosed) onAllPanelsCollapsed(); + else onPanelPeeked(); + + LOG("panelExpansionChanged: [%s%s ]", fullyOpened?" fullyOpened":"", fullyClosed?" fullyClosed":""); + } + + public void collapseAllPanels(boolean animate) { + for (PanelView pv : mPanels) { + if (animate && pv == mTouchingPanel) { + mTouchingPanel.collapse(); + } else { + pv.setExpandedFraction(0); // just in case + } + } + } + + public void onPanelPeeked() { + LOG("onPanelPeeked"); + } + + public void onAllPanelsCollapsed() { + LOG("onAllPanelsCollapsed"); + } + + public void onPanelFullyOpened() { + LOG("onPanelFullyOpened"); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java new file mode 100644 index 000000000000..abd82bdfb43f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java @@ -0,0 +1,65 @@ +package com.android.systemui.statusbar.phone; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +public class PanelHolder extends FrameLayout { + + private int mSelectedPanelIndex; + private PanelBar mBar; + + public PanelHolder(Context context, AttributeSet attrs) { + super(context, attrs); + setChildrenDrawingOrderEnabled(true); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + setChildrenDrawingOrderEnabled(true); + } + + public int getPanelIndex(PanelView pv) { + final int N = getChildCount(); + for (int i=0; i<N; i++) { + final PanelView v = (PanelView) getChildAt(i); + if (pv == v) return i; + } + return -1; + } + + public void setSelectedPanel(PanelView pv) { + mSelectedPanelIndex = getPanelIndex(pv); + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + if (mSelectedPanelIndex == -1) { + return i; + } else { + if (i == childCount - 1) { + return mSelectedPanelIndex; + } else if (i >= mSelectedPanelIndex) { + return i + 1; + } else { + return i; + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mBar.collapseAllPanels(true); + break; + } + return false; + } + + public void setBar(PanelBar panelBar) { + mBar = panelBar; + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java new file mode 100644 index 000000000000..7dc8f7c175bd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -0,0 +1,327 @@ +package com.android.systemui.statusbar.phone; + +import android.animation.TimeAnimator; +import android.animation.TimeAnimator.TimeListener; +import android.content.Context; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.widget.FrameLayout; + +import com.android.systemui.R; + +public class PanelView extends FrameLayout { + public static final boolean DEBUG = true; + public static final String TAG = PanelView.class.getSimpleName(); + public static final void LOG(String fmt, Object... args) { + if (!DEBUG) return; + Log.v(TAG, String.format(fmt, args)); + } + + public static final boolean BRAKES = false; + + private float mSelfExpandVelocityPx; // classic value: 2000px/s + private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up") + private float mFlingExpandMinVelocityPx; // classic value: 200px/s + private float mFlingCollapseMinVelocityPx; // classic value: 200px/s + private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1) + private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand) + private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s + + private float mExpandAccelPx; // classic value: 2000px/s/s + private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up") + + private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little + // faster than mSelfCollapseVelocityPx) + + private float mCollapseBrakingDistancePx = 200; // XXX Resource + private float mExpandBrakingDistancePx = 150; // XXX Resource + private float mBrakingSpeedPx = 150; // XXX Resource + + private View mHandleView; + private float mTouchOffset; + private float mExpandedFraction = 0; + private float mExpandedHeight = 0; + + private TimeAnimator mTimeAnimator; + private VelocityTracker mVelocityTracker; + + private int[] mAbsPos = new int[2]; + PanelBar mBar; + + private final TimeListener mAnimationCallback = new TimeListener() { + @Override + public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { + animationTick(deltaTime); + } + }; + + private float mVel, mAccel; + private int mFullHeight = 0; + + private void animationTick(long dtms) { + if (!mTimeAnimator.isStarted()) { + // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time + mTimeAnimator = new TimeAnimator(); + mTimeAnimator.setTimeListener(mAnimationCallback); + + mTimeAnimator.start(); + } else { + final float dt = dtms * 0.001f; // ms -> s + LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt); + LOG("tick: before: h=%d", (int) mExpandedHeight); + + final float fh = getFullHeight(); + final boolean closing = mExpandedHeight > 0 && mVel < 0; + boolean braking = false; + if (BRAKES) { + if (closing) { + braking = mExpandedHeight <= mCollapseBrakingDistancePx; + mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx; + } else { + braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx); + mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx; + } + } else { + mAccel = closing ? -mCollapseAccelPx : mExpandAccelPx; + } + + mVel += mAccel * dt; + + if (braking) { + if (closing && mVel > -mBrakingSpeedPx) { + mVel = -mBrakingSpeedPx; + } else if (!closing && mVel < mBrakingSpeedPx) { + mVel = mBrakingSpeedPx; + } + } else { + if (closing && mVel > -mFlingCollapseMinVelocityPx) { + mVel = -mFlingCollapseMinVelocityPx; + } else if (!closing && mVel > mFlingGestureMaxOutputVelocityPx) { + mVel = mFlingGestureMaxOutputVelocityPx; + } + } + + float h = mExpandedHeight + mVel * dt; + + LOG("tick: new h=%d closing=%s", (int) h, closing?"true":"false"); + + setExpandedHeightInternal(h); + + mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); + + if (mVel == 0 + || (closing && mExpandedHeight == 0) + || (!closing && mExpandedHeight == getFullHeight())) { + mTimeAnimator.end(); + } + } + } + + public PanelView(Context context, AttributeSet attrs) { + super(context, attrs); + + mTimeAnimator = new TimeAnimator(); + mTimeAnimator.setTimeListener(mAnimationCallback); + } + + private void loadDimens() { + final Resources res = getContext().getResources(); + + mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity); + mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity); + mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity); + mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity); + + mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1); + mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1); + + mExpandAccelPx = res.getDimension(R.dimen.expand_accel); + mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel); + + mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity); + + mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity); + } + + private void trackMovement(MotionEvent event) { + // Add movement to velocity tracker using raw screen X and Y coordinates instead + // of window coordinates because the window frame may be moving at the same time. + float deltaX = event.getRawX() - event.getX(); + float deltaY = event.getRawY() - event.getY(); + event.offsetLocation(deltaX, deltaY); + mVelocityTracker.addMovement(event); + event.offsetLocation(-deltaX, -deltaY); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + loadDimens(); + + mHandleView = findViewById(R.id.handle); + LOG("handle view: " + mHandleView); + if (mHandleView != null) { + mHandleView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + final float y = event.getY(); + final float rawY = event.getRawY(); + LOG("handle.onTouch: y=%.1f rawY=%.1f off=%.1f", y, rawY, mTouchOffset); + PanelView.this.getLocationOnScreen(mAbsPos); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mVelocityTracker = VelocityTracker.obtain(); + trackMovement(event); + mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight(); + break; + + case MotionEvent.ACTION_MOVE: + PanelView.this.setExpandedHeight(rawY - mAbsPos[1] - mTouchOffset); + + mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); + + trackMovement(event); + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + trackMovement(event); + mVelocityTracker.computeCurrentVelocity(1000); + + float yVel = mVelocityTracker.getYVelocity(); + boolean negative = yVel < 0; + + float xVel = mVelocityTracker.getXVelocity(); + if (xVel < 0) { + xVel = -xVel; + } + if (xVel > mFlingGestureMaxXVelocityPx) { + xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis + } + + float vel = (float)Math.hypot(yVel, xVel); + if (vel > mFlingGestureMaxOutputVelocityPx) { + vel = mFlingGestureMaxOutputVelocityPx; + } + if (negative) { + vel = -vel; + } + + LOG("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", + mVelocityTracker.getXVelocity(), + mVelocityTracker.getYVelocity(), + xVel, yVel, + vel); + + fling(vel, false); + + mVelocityTracker.recycle(); + mVelocityTracker = null; + + break; + } + return true; + }}); + } + } + + public void fling(float vel, boolean always) { + mVel = vel; + + if (mVel != 0) { + animationTick(0); // begin the animation + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + } + + @Override + protected void onViewAdded(View child) { + LOG("onViewAdded: " + child); + } + + public View getHandle() { + return mHandleView; + } + + // Rubberbands the panel to hold its contents. + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + LOG("onMeasure(%d, %d) -> (%d, %d)", + widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); + mFullHeight = getMeasuredHeight(); + heightMeasureSpec = MeasureSpec.makeMeasureSpec( + (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec)); + setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); + } + + + public void setExpandedHeight(float height) { + mTimeAnimator.end(); + setExpandedHeightInternal(height); + } + + public void setExpandedHeightInternal(float h) { + float fh = getFullHeight(); + if (fh == 0) { + // Hmm, full height hasn't been computed yet + } + + LOG("setExpansion: height=%.1f fh=%.1f", h, fh); + + if (h < 0) h = 0; + else if (h > fh) h = fh; + + mExpandedHeight = h; + + requestLayout(); +// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); +// lp.height = (int) mExpandedHeight; +// setLayoutParams(lp); + + mExpandedFraction = Math.min(1f, h / fh); + } + + private float getFullHeight() { + return mFullHeight; + } + + public void setExpandedFraction(float frac) { + setExpandedHeight(getFullHeight() * frac); + } + + public float getExpandedHeight() { + return mExpandedHeight; + } + + public float getExpandedFraction() { + return mExpandedFraction; + } + + public void setBar(PanelBar panelBar) { + mBar = panelBar; + } + + public void collapse() { + // TODO: abort animation or ongoing touch + if (mExpandedHeight > 0) { + fling(-mSelfCollapseVelocityPx, /*always=*/ true); + } + } + + public void expand() { + if (mExpandedHeight < getFullHeight()) { + fling (mSelfExpandVelocityPx, /*always=*/ true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 0ccc41589c1d..0f84c2f27367 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -52,7 +52,6 @@ import android.service.dreams.IDreamManager; import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; -import android.view.Choreographer; import android.view.Display; import android.view.Gravity; import android.view.IWindowManager; @@ -77,7 +76,6 @@ import android.widget.TextView; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.R; -import com.android.systemui.UniverseBackground; import com.android.systemui.recent.RecentTasksLoader; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; @@ -111,7 +109,6 @@ public class PhoneStatusBar extends BaseStatusBar { public static final String ACTION_STATUSBAR_START = "com.android.internal.policy.statusbar.START"; - private static final boolean DIM_BEHIND_EXPANDED_PANEL = true; private static final boolean SHOW_CARRIER_LABEL = true; private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; @@ -159,8 +156,6 @@ public class PhoneStatusBar extends BaseStatusBar { StatusBarWindowView mStatusBarWindow; PhoneStatusBarView mStatusBarView; - UniverseBackground mUniverseBackground; - int mPixelFormat; Object mQueueLock = new Object(); @@ -171,7 +166,7 @@ public class PhoneStatusBar extends BaseStatusBar { LinearLayout mStatusIcons; // expanded notifications - View mNotificationPanel; // the sliding/resizing panel within the notification window + PanelView mNotificationPanel; // the sliding/resizing panel within the notification window ScrollView mScrollView; View mExpandedContents; int mNotificationPanelMarginBottomPx, mNotificationPanelMarginLeftPx; @@ -191,13 +186,8 @@ public class PhoneStatusBar extends BaseStatusBar { private int mCarrierLabelHeight; private TextView mEmergencyCallLabel; - // drag bar - CloseDragHandle mCloseView; - private int mCloseViewHeight; - // position int[] mPositionTmp = new int[2]; - boolean mExpanded; boolean mExpandedVisible; // the date view @@ -222,7 +212,6 @@ public class PhoneStatusBar extends BaseStatusBar { boolean mTracking; VelocityTracker mVelocityTracker; - Choreographer mChoreographer; boolean mAnimating; boolean mClosing; // only valid when mAnimating; indicates the initial acceleration float mAnimY; @@ -262,40 +251,6 @@ public class PhoneStatusBar extends BaseStatusBar { } }; - private final Runnable mStartRevealAnimation = new Runnable() { - @Override - public void run() { - mAnimAccel = mExpandAccelPx; - mAnimVel = mFlingExpandMinVelocityPx; - mAnimY = getStatusBarHeight(); - updateExpandedViewPos((int)mAnimY); - - mAnimating = true; - mAnimatingReveal = true; - resetLastAnimTime(); - mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, - mAnimationCallback, null); - mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, - mRevealAnimationCallback, null); - mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, - mRevealAnimationCallback, null); - } - }; - - private final Runnable mPerformSelfExpandFling = new Runnable() { - @Override - public void run() { - performFling(0, mSelfExpandVelocityPx, true); - } - }; - - private final Runnable mPerformFling = new Runnable() { - @Override - public void run() { - performFling(mFlingY + mViewDelta, mFlingVelocity, false); - } - }; - @Override public void start() { mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) @@ -313,11 +268,6 @@ public class PhoneStatusBar extends BaseStatusBar { if (ENABLE_INTRUDERS) addIntruderView(); - mUniverseBackground = new UniverseBackground(mContext); - mUniverseBackground.setVisibility(View.GONE); - WindowManagerImpl.getDefault().addView(mUniverseBackground, - mUniverseBackground.getLayoutParams(mDisplay)); - // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext); } @@ -345,7 +295,7 @@ public class PhoneStatusBar extends BaseStatusBar { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (mExpanded && !mAnimating) { + if (mExpandedVisible && !mAnimating) { animateCollapse(); } } @@ -353,7 +303,12 @@ public class PhoneStatusBar extends BaseStatusBar { }}); mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar); - mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel); + mStatusBarView.setBar(this); + + PanelHolder holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder); + mStatusBarView.setPanelHolder(holder); + + mNotificationPanel = (PanelView) mStatusBarWindow.findViewById(R.id.notification_panel); // don't allow clicks on the panel to pass through to the background where they will cause the panel to close mNotificationPanel.setOnTouchListener(new View.OnTouchListener() { @Override @@ -380,10 +335,6 @@ public class PhoneStatusBar extends BaseStatusBar { updateShowSearchHoldoff(); - mStatusBarView.mService = this; - - mChoreographer = Choreographer.getInstance(); - try { boolean showNav = mWindowManager.hasNavigationBar(); if (DEBUG) Slog.v(TAG, "hasNavigationBar=" + showNav); @@ -429,10 +380,6 @@ public class PhoneStatusBar extends BaseStatusBar { TickerView tickerView = (TickerView)mStatusBarView.findViewById(R.id.tickerText); tickerView.mTicker = mTicker; - mCloseView = (CloseDragHandle)mStatusBarWindow.findViewById(R.id.close); - mCloseView.mService = this; - mCloseViewHeight = res.getDimensionPixelSize(R.dimen.close_handle_height); - mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); // set the inital view visibility @@ -597,10 +544,6 @@ public class PhoneStatusBar extends BaseStatusBar { return mNaturalBarHeight; } - private int getCloseViewHeight() { - return mCloseViewHeight; - } - private View.OnClickListener mRecentsClickListener = new View.OnClickListener() { public void onClick(View v) { toggleRecentApps(); @@ -1169,20 +1112,6 @@ public class PhoneStatusBar extends BaseStatusBar { } } - final Runnable mAnimationCallback = new Runnable() { - @Override - public void run() { - doAnimation(mChoreographer.getFrameTimeNanos()); - } - }; - - final Runnable mRevealAnimationCallback = new Runnable() { - @Override - public void run() { - doRevealAnimation(mChoreographer.getFrameTimeNanos()); - } - }; - View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { // Because 'v' is a ViewGroup, all its children will be (un)selected @@ -1191,7 +1120,7 @@ public class PhoneStatusBar extends BaseStatusBar { } }; - private void makeExpandedVisible(boolean revealAfterDraw) { + void makeExpandedVisible(boolean revealAfterDraw) { if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); if (mExpandedVisible) { return; @@ -1218,38 +1147,20 @@ public class PhoneStatusBar extends BaseStatusBar { // Updating the window layout will force an expensive traversal/redraw. // Kick off the reveal animation after this is complete to avoid animation latency. if (revealAfterDraw) { - mHandler.post(mStartRevealAnimation); +// mHandler.post(mStartRevealAnimation); } visibilityChanged(true); } - public void animateExpand() { - if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded); - if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { - return ; - } - if (mExpanded) { - return; - } - - prepareTracking(0, true); - mHandler.post(mPerformSelfExpandFling); - } - public void animateCollapse() { animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE); } public void animateCollapse(int flags) { - animateCollapse(flags, 1.0f); - } - - public void animateCollapse(int flags, float velocityMultiplier) { if (SPEW) { - Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded + Slog.d(TAG, "animateCollapse(): " + " mExpandedVisible=" + mExpandedVisible - + " mExpanded=" + mExpanded + " mAnimating=" + mAnimating + " mAnimatingReveal=" + mAnimatingReveal + " mAnimY=" + mAnimY @@ -1267,41 +1178,23 @@ public class PhoneStatusBar extends BaseStatusBar { mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL); } - if (!mExpandedVisible) { - return; - } - - int y; - if (mAnimating || mAnimatingReveal) { - y = (int)mAnimY; - } else { - y = getExpandedViewMaxHeight()-1; - } - // Let the fling think that we're open so it goes in the right direction - // and doesn't try to re-open the windowshade. - mExpanded = true; - prepareTracking(y, false); - performFling(y, -mSelfCollapseVelocityPx*velocityMultiplier, true); + mStatusBarView.collapseAllPanels(true); } - void performExpand() { - if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded); + @Override + public void animateExpand() { + if (SPEW) Slog.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible); if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { return ; } - if (mExpanded) { - return; - } - mExpanded = true; - makeExpandedVisible(false); - updateExpandedViewPos(EXPANDED_FULL_OPEN); + mNotificationPanel.expand(); if (false) postStartTracing(); } - void performCollapse() { - if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded + void makeExpandedInvisible() { + if (SPEW) Slog.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible + " mExpandedVisible=" + mExpandedVisible); if (!mExpandedVisible) { @@ -1309,7 +1202,7 @@ public class PhoneStatusBar extends BaseStatusBar { } // Ensure the panel is fully collapsed (just in case; bug 6765842) - updateExpandedViewPos(0); + mStatusBarView.collapseAllPanels(/*animate=*/ false); mExpandedVisible = false; mPile.setLayoutTransitionsEnabled(false); @@ -1329,11 +1222,6 @@ public class PhoneStatusBar extends BaseStatusBar { setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); } - if (!mExpanded) { - return; - } - mExpanded = false; - // Close any "App info" popups that might have snuck on-screen dismissPopups(); @@ -1343,67 +1231,6 @@ public class PhoneStatusBar extends BaseStatusBar { } } - void resetLastAnimTime() { - mAnimLastTimeNanos = System.nanoTime(); - if (SPEW) { - Throwable t = new Throwable(); - t.fillInStackTrace(); - Slog.d(TAG, "resetting last anim time=" + mAnimLastTimeNanos, t); - } - } - - void doAnimation(long frameTimeNanos) { - if (mAnimating) { - if (SPEW) Slog.d(TAG, "doAnimation dt=" + (frameTimeNanos - mAnimLastTimeNanos)); - if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY); - incrementAnim(frameTimeNanos); - if (SPEW) { - Slog.d(TAG, "doAnimation after mAnimY=" + mAnimY); - Slog.d(TAG, "doAnimation expandedViewMax=" + getExpandedViewMaxHeight()); - } - - if (mAnimY >= getExpandedViewMaxHeight()-1 && !mClosing) { - if (SPEW) Slog.d(TAG, "Animation completed to expanded state."); - mAnimating = false; - updateExpandedViewPos(EXPANDED_FULL_OPEN); - performExpand(); - return; - } - - if (mAnimY == 0 && mAnimAccel == 0 && mClosing) { - if (SPEW) Slog.d(TAG, "Animation completed to collapsed state."); - mAnimating = false; - performCollapse(); - return; - } - - if (mAnimY < getStatusBarHeight() && mClosing) { - // Draw one more frame with the bar positioned at the top of the screen - // before ending the animation so that the user sees the bar in - // its final position. The call to performCollapse() causes a window - // relayout which takes time and might cause the animation to skip - // on the very last frame before the bar disappears if we did it now. - mAnimY = 0; - mAnimAccel = 0; - mAnimVel = 0; - } - - updateExpandedViewPos((int)mAnimY); - mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, - mAnimationCallback, null); - } - } - - void stopTracking() { - if (!mTracking) - return; - mTracking = false; - setPileLayers(View.LAYER_TYPE_NONE); - mVelocityTracker.recycle(); - mVelocityTracker = null; - mCloseView.setPressed(false); - } - /** * Enables or disables layers on the children of the notifications pile. * @@ -1451,148 +1278,6 @@ public class PhoneStatusBar extends BaseStatusBar { } } - void incrementAnim(long frameTimeNanos) { - final long deltaNanos = Math.max(frameTimeNanos - mAnimLastTimeNanos, 0); - final float t = deltaNanos * 0.000000001f; // ns -> s - final float y = mAnimY; - final float v = mAnimVel; // px/s - final float a = mAnimAccel; // px/s/s - mAnimY = y + (v*t) + (0.5f*a*t*t); // px - mAnimVel = v + (a*t); // px/s - mAnimLastTimeNanos = frameTimeNanos; // ns - //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY - // + " mAnimAccel=" + mAnimAccel); - } - - void doRevealAnimation(long frameTimeNanos) { - if (SPEW) { - Slog.d(TAG, "doRevealAnimation: dt=" + (frameTimeNanos - mAnimLastTimeNanos)); - } - final int h = mNotificationPanelMinHeight; - if (mAnimatingReveal && mAnimating && mAnimY < h) { - incrementAnim(frameTimeNanos); - if (mAnimY >= h) { - mAnimY = h; - updateExpandedViewPos((int)mAnimY); - } else { - updateExpandedViewPos((int)mAnimY); - mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, - mRevealAnimationCallback, null); - } - } - } - - void prepareTracking(int y, boolean opening) { - if (CHATTY) { - Slog.d(TAG, "panel: beginning to track the user's touch, y=" + y + " opening=" + opening); - } - - mCloseView.setPressed(true); - - mTracking = true; - setPileLayers(View.LAYER_TYPE_HARDWARE); - mVelocityTracker = VelocityTracker.obtain(); - if (opening) { - makeExpandedVisible(true); - } else { - // it's open, close it? - if (mAnimating) { - mAnimating = false; - mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, - mAnimationCallback, null); - } - updateExpandedViewPos(y + mViewDelta); - } - } - - void performFling(int y, float vel, boolean always) { - if (CHATTY) { - Slog.d(TAG, "panel: will fling, y=" + y + " vel=" + vel + " mExpanded=" + mExpanded); - } - - mAnimatingReveal = false; - - mAnimY = y; - mAnimVel = vel; - - //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); - - if (mExpanded) { - if (!always && ( - vel > mFlingCollapseMinVelocityPx - || (y > (getExpandedViewMaxHeight()*(1f-mCollapseMinDisplayFraction)) && - vel > -mFlingExpandMinVelocityPx))) { - // We are expanded, but they didn't move sufficiently to cause - // us to retract. Animate back to the expanded position. - mAnimAccel = mExpandAccelPx; - if (vel < 0) { - mAnimVel = 0; - } - } - else { - // We are expanded and are now going to animate away. - mAnimAccel = -mCollapseAccelPx; - if (vel > 0) { - mAnimVel = 0; - } - } - } else { - if (always || ( - vel > mFlingExpandMinVelocityPx - || (y > (getExpandedViewMaxHeight()*(1f-mExpandMinDisplayFraction)) && - vel > -mFlingCollapseMinVelocityPx))) { - // We are collapsed, and they moved enough to allow us to - // expand. Animate in the notifications. - mAnimAccel = mExpandAccelPx; - if (vel < 0) { - mAnimVel = 0; - } - } - else { - // We are collapsed, but they didn't move sufficiently to cause - // us to retract. Animate back to the collapsed position. - mAnimAccel = -mCollapseAccelPx; - if (vel > 0) { - mAnimVel = 0; - } - } - } - //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel - // + " mAnimAccel=" + mAnimAccel); - - resetLastAnimTime(); - mAnimating = true; - mClosing = mAnimAccel < 0; - - mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, - mAnimationCallback, null); - mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, - mRevealAnimationCallback, null); - mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, - mAnimationCallback, null); - stopTracking(); - } - - boolean handleUniverseEvent(MotionEvent event) { - if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { - return false; - } - if (mExpanded) { - return false; - } - if (mUniverseBackground.consumeEvent(event)) { - if (mTracking) { - // fling back to the top, starting from the last tracked position. - mFlingY = mTrackingPosition; - mViewDelta = 0; - mFlingVelocity = -1; - mHandler.post(mPerformFling); - } - return true; - } - return false; - } - boolean interceptTouchEvent(MotionEvent event) { if (SPEW) { Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled=" @@ -1612,104 +1297,11 @@ public class PhoneStatusBar extends BaseStatusBar { return false; } - final int y = (int)event.getRawY(); - final int action = event.getAction(); - final int statusBarSize = getStatusBarHeight(); - final int hitSize = statusBarSize*2; - if (action == MotionEvent.ACTION_DOWN) { - if (!areLightsOn()) { - setLightsOn(true); - } - - if (!mExpanded) { - mViewDelta = statusBarSize - y; - } else { - mCloseView.getLocationOnScreen(mAbsPos); - mViewDelta = mAbsPos[1] - + getCloseViewHeight() // XXX: not closeViewHeight, but paddingBottom from the 9patch - + mNotificationPanelBackgroundPadding.top - + mNotificationPanelBackgroundPadding.bottom - - y; - } - if ((!mExpanded && y < hitSize) || - // @@ add taps outside the panel if it's not full-screen - (mExpanded && y > (getExpandedViewMaxHeight()-hitSize))) { - // We drop events at the edge of the screen to make the windowshade come - // down by accident less, especially when pushing open a device with a keyboard - // that rotates (like g1 and droid) - int x = (int)event.getRawX(); - final int edgeBorder = mEdgeBorder; - if (x >= edgeBorder && x < mDisplayMetrics.widthPixels - edgeBorder) { - prepareTracking(y, !mExpanded);// opening if we're not already fully visible - trackMovement(event); - mGestureRec.tag("tracking", mExpanded ? "expanded" : "collapsed"); - } - } - } else if (mTracking) { - trackMovement(event); - if (action == MotionEvent.ACTION_MOVE) { - if (mAnimatingReveal && (y + mViewDelta) < mNotificationPanelMinHeight) { - // nothing - } else { - mAnimatingReveal = false; - updateExpandedViewPos(y + mViewDelta); - } - } else if (action == MotionEvent.ACTION_UP - || action == MotionEvent.ACTION_CANCEL) { - mVelocityTracker.computeCurrentVelocity(1000); - - float yVel = mVelocityTracker.getYVelocity(); - boolean negative = yVel < 0; - - float xVel = mVelocityTracker.getXVelocity(); - if (xVel < 0) { - xVel = -xVel; - } - if (xVel > mFlingGestureMaxXVelocityPx) { - xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis - } - - float vel = (float)Math.hypot(yVel, xVel); - if (vel > mFlingGestureMaxOutputVelocityPx) { - vel = mFlingGestureMaxOutputVelocityPx; - } - if (negative) { - vel = -vel; - } - - if (CHATTY) { - Slog.d(TAG, String.format("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", - mVelocityTracker.getXVelocity(), - mVelocityTracker.getYVelocity(), - xVel, yVel, - vel)); - } - - if (mTrackingPosition == mNotificationPanelMinHeight) { - // start the fling from the tracking position, ignore y and view delta - mFlingY = mTrackingPosition; - mViewDelta = 0; - } else { - mFlingY = y; - } - mFlingVelocity = vel; - mGestureRec.tag("fling " + ((mFlingVelocity > 0) ? "open" : "closed"), - "v=" + mFlingVelocity); - mHandler.post(mPerformFling); - } - - } return false; } - private void trackMovement(MotionEvent event) { - // Add movement to velocity tracker using raw screen X and Y coordinates instead - // of window coordinates because the window frame may be moving at the same time. - float deltaX = event.getRawX() - event.getX(); - float deltaY = event.getRawY() - event.getY(); - event.offsetLocation(deltaX, deltaY); - mVelocityTracker.addMovement(event); - event.offsetLocation(-deltaX, -deltaY); + public GestureRecorder getGestureRecorder() { + return mGestureRec; } @Override // CommandQueue @@ -1913,8 +1505,7 @@ public class PhoneStatusBar extends BaseStatusBar { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mQueueLock) { pw.println("Current Status Bar state:"); - pw.println(" mExpanded=" + mExpanded - + ", mExpandedVisible=" + mExpandedVisible + pw.println(" mExpandedVisible=" + mExpandedVisible + ", mTrackingPosition=" + mTrackingPosition); pw.println(" mTicking=" + mTicking); pw.println(" mTracking=" + mTracking); @@ -1930,7 +1521,6 @@ public class PhoneStatusBar extends BaseStatusBar { + " mViewDelta=" + mViewDelta); pw.println(" mDisplayMetrics=" + mDisplayMetrics); pw.println(" mPile: " + viewInfo(mPile)); - pw.println(" mCloseView: " + viewInfo(mCloseView)); pw.println(" mTickerView: " + viewInfo(mTickerView)); pw.println(" mScrollView: " + viewInfo(mScrollView) + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY()); @@ -2039,83 +1629,13 @@ public class PhoneStatusBar extends BaseStatusBar { } @Override - protected void updateExpandedViewPos(int expandedPosition) { - if (SPEW) { - Slog.d(TAG, "updateExpandedViewPos: expandedPosition=" + expandedPosition - //+ " mTrackingParams.y=" + ((mTrackingParams == null) ? "?" : mTrackingParams.y) - + " mTracking=" + mTracking - + " mTrackingPosition=" + mTrackingPosition - + " mExpandedVisible=" + mExpandedVisible - + " mAnimating=" + mAnimating - + " mAnimatingReveal=" + mAnimatingReveal - + " mClosing=" + mClosing - + " gravity=" + mNotificationPanelGravity); - } - int panelh = 0; - final int disph = getExpandedViewMaxHeight(); - - // If the expanded view is not visible, make sure they're still off screen. - // Maybe the view was resized. - if (!mExpandedVisible) { - if (SPEW) Slog.d(TAG, "updateExpandedViewPos: view not visible, bailing"); - updateExpandedInvisiblePosition(); - return; - } - - // tracking view... - if (expandedPosition == EXPANDED_FULL_OPEN) { - panelh = disph; - } - else if (expandedPosition == EXPANDED_LEAVE_ALONE) { - panelh = mTrackingPosition; - } - else { - if (expandedPosition <= disph) { - panelh = expandedPosition; - } else { - panelh = disph; - } - } - - // catch orientation changes and other peculiar cases - if (panelh > 0 && - ((panelh > disph) || - (panelh < disph && !mTracking && !mAnimating))) { - if (SPEW) Slog.d(TAG, "updateExpandedViewPos: orientation change?"); - panelh = disph; - } else if (panelh < 0) { - panelh = 0; - } - - if (SPEW) Slog.d(TAG, "updateExpandedViewPos: adjusting size to panelh=" + panelh); - - if (panelh == mTrackingPosition) { - if (SPEW) Slog.d(TAG, "updateExpandedViewPos: panelh == mTrackingPosition, bailing"); - return; - } - - mTrackingPosition = panelh; - + public void updateExpandedViewPos(int thingy) { + // TODO + if (DEBUG) Slog.v(TAG, "updateExpandedViewPos"); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mNotificationPanel.getLayoutParams(); - lp.height = panelh; lp.gravity = mNotificationPanelGravity; lp.leftMargin = mNotificationPanelMarginLeftPx; - if (SPEW) { - Slog.v(TAG, "updated cropView height=" + panelh + " grav=" + lp.gravity); - } mNotificationPanel.setLayoutParams(lp); - - final int barh = getCloseViewHeight() + getStatusBarHeight(); - final float frac = saturate((float)(panelh - barh) / (disph - barh)); - - if (DIM_BEHIND_EXPANDED_PANEL && ActivityManager.isHighEndGfx(mDisplay)) { - // woo, special effects - final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2.2f)))); - final int color = ((int)(0xB0 * k)) << 24; - mStatusBarWindow.setBackgroundColor(color); - } - - updateCarrierLabelVisibility(false); } // called by makeStatusbar and also by PhoneStatusBarView @@ -2196,6 +1716,9 @@ public class PhoneStatusBar extends BaseStatusBar { mPostCollapseCleanup = new Runnable() { @Override public void run() { + if (DEBUG) { + Slog.v(TAG, "running post-collapse cleanup"); + } try { mPile.setViewRemoval(true); mBarService.onClearAllNotifications(); @@ -2264,7 +1787,7 @@ public class PhoneStatusBar extends BaseStatusBar { } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // no waiting! - performCollapse(); + makeExpandedInvisible(); } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { updateResources(); @@ -2416,7 +1939,7 @@ public class PhoneStatusBar extends BaseStatusBar { @Override protected boolean shouldDisableNavbarGestures() { - return mExpanded || (mDisabled & StatusBarManager.DISABLE_HOME) != 0; + return mExpandedVisible || (mDisabled & StatusBarManager.DISABLE_HOME) != 0; } private static class FastColorDrawable extends Drawable { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index fd4cff84bbce..6de015349a1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.phone; +import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Rect; import android.os.SystemClock; import android.util.AttributeSet; @@ -35,147 +38,76 @@ import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.policy.FixedSizeDrawable; -public class PhoneStatusBarView extends FrameLayout { +public class PhoneStatusBarView extends PanelBar { private static final String TAG = "PhoneStatusBarView"; - - static final int DIM_ANIM_TIME = 400; - - PhoneStatusBar mService; - boolean mTracking; - int mStartX, mStartY; - ViewGroup mNotificationIcons; - ViewGroup mStatusIcons; - - boolean mNightMode = false; - int mStartAlpha = 0, mEndAlpha = 0; - long mEndTime = 0; - - Rect mButtonBounds = new Rect(); - boolean mCapturingEvents = true; + PhoneStatusBar mBar; + int mScrimColor; public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); } - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mNotificationIcons = (ViewGroup)findViewById(R.id.notificationIcons); - mStatusIcons = (ViewGroup)findViewById(R.id.statusIcons); + public void setBar(PhoneStatusBar bar) { + mBar = bar; } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - //mService.onBarViewAttached(); + public void onAttachedToWindow() { + Resources res = getContext().getResources(); + mScrimColor = res.getColor(R.color.notification_panel_scrim_color); } - + @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mService.updateDisplaySize(); - boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - if (mNightMode != nightMode) { - mNightMode = nightMode; - mStartAlpha = getCurAlpha(); - mEndAlpha = mNightMode ? 0x80 : 0x00; - mEndTime = SystemClock.uptimeMillis() + DIM_ANIM_TIME; - invalidate(); + public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { + if (super.onRequestSendAccessibilityEvent(child, event)) { + // The status bar is very small so augment the view that the user is touching + // with the content of the status bar a whole. This way an accessibility service + // may announce the current item as well as the entire content if appropriate. + AccessibilityEvent record = AccessibilityEvent.obtain(); + onInitializeAccessibilityEvent(record); + dispatchPopulateAccessibilityEvent(record); + event.appendRecord(record); + return true; } + return false; } - int getCurAlpha() { - long time = SystemClock.uptimeMillis(); - if (time > mEndTime) { - return mEndAlpha; - } - return mEndAlpha - - (int)(((mEndAlpha-mStartAlpha) * (mEndTime-time) / DIM_ANIM_TIME)); - } - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mService.updateExpandedViewPos(BaseStatusBar.EXPANDED_LEAVE_ALONE); + public void onPanelPeeked() { + super.onPanelPeeked(); + mBar.makeExpandedVisible(true); } @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); + public void onAllPanelsCollapsed() { + super.onAllPanelsCollapsed(); + mBar.makeExpandedInvisible(); } @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - int alpha = getCurAlpha(); - if (alpha != 0) { - canvas.drawARGB(alpha, 0, 0, 0); - } - if (alpha != mEndAlpha) { - invalidate(); - } - } - - /** - * Gets the left position of v in this view. Throws if v is not - * a child of this. - */ - private int getViewOffset(View v) { - int offset = 0; - while (v != this) { - offset += v.getLeft(); - ViewParent p = v.getParent(); - if (v instanceof View) { - v = (View)p; - } else { - throw new RuntimeException(v + " is not a child of " + this); - } - } - return offset; - } - - /** - * Ensure that, if there is no target under us to receive the touch, - * that we process it ourself. This makes sure that onInterceptTouchEvent() - * is always called for the entire gesture. - */ - @Override public boolean onTouchEvent(MotionEvent event) { - if (!mCapturingEvents) { - return false; - } - if (event.getAction() != MotionEvent.ACTION_DOWN) { - mService.interceptTouchEvent(event); - } - return true; + return mBar.interceptTouchEvent(event) || super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (mButtonBounds.contains((int)event.getX(), (int)event.getY())) { - mCapturingEvents = false; - return false; - } - } - mCapturingEvents = true; - return mService.interceptTouchEvent(event) - ? true : super.onInterceptTouchEvent(event); + return mBar.interceptTouchEvent(event) || super.onInterceptTouchEvent(event); } @Override - public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (super.onRequestSendAccessibilityEvent(child, event)) { - // The status bar is very small so augment the view that the user is touching - // with the content of the status bar a whole. This way an accessibility service - // may announce the current item as well as the entire content if appropriate. - AccessibilityEvent record = AccessibilityEvent.obtain(); - onInitializeAccessibilityEvent(record); - dispatchPopulateAccessibilityEvent(record); - event.appendRecord(record); - return true; + public void panelExpansionChanged(PanelView pv, float frac) { + super.panelExpansionChanged(pv, frac); + + if (mScrimColor != 0 && ActivityManager.isHighEndGfx(mBar.mDisplay)) { + // woo, special effects + final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2.2f)))); + // attenuate background color alpha by k + final int color = (int) ((float)(mScrimColor >>> 24) * k) << 24 | (mScrimColor & 0xFFFFFF); + mBar.mStatusBarWindow.setBackgroundColor(color); } - return false; + + mBar.updateCarrierLabelVisibility(false); } + + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index f53ed0c5c1c0..2d4c9ee44b88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -37,8 +37,6 @@ public class StatusBarWindowView extends FrameLayout private ExpandHelper mExpandHelper; private NotificationRowLayout latestItems; - private boolean mUniverseHandling = false; - PhoneStatusBar mService; public StatusBarWindowView(Context context, AttributeSet attrs) { @@ -73,16 +71,6 @@ public class StatusBarWindowView extends FrameLayout @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mService.handleUniverseEvent(ev)) { - mUniverseHandling = true; - MotionEvent cancellation = MotionEvent.obtain(ev); - cancellation.setAction(MotionEvent.ACTION_CANCEL); - mExpandHelper.onInterceptTouchEvent(cancellation); - latestItems.onInterceptTouchEvent(cancellation); - cancellation.recycle(); - return true; - } - boolean intercept = mExpandHelper.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev); if (intercept) { @@ -96,12 +84,6 @@ public class StatusBarWindowView extends FrameLayout @Override public boolean onTouchEvent(MotionEvent ev) { - if (mUniverseHandling) { - if (!mService.handleUniverseEvent(ev)) { - mUniverseHandling = false; - } - return true; - } boolean handled = mExpandHelper.onTouchEvent(ev) || super.onTouchEvent(ev); return handled; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index 69872df2653a..ffc18c7d0e7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -34,6 +34,7 @@ import android.text.style.RelativeSizeSpan; import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.util.AttributeSet; +import android.util.Slog; import android.view.View; import android.widget.TextView; @@ -173,7 +174,6 @@ public class Clock extends TextView { + "a" + MAGIC2 + format.substring(b + 1); } } - mClockFormat = sdf = new SimpleDateFormat(format); mClockFormatString = format; } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java index 9fee49ba8719..e63735677ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java @@ -166,6 +166,7 @@ public class NotificationRowLayout } public void onChildDismissed(View v) { + if (DEBUG) Slog.v(TAG, "onChildDismissed: " + v + " mRemoveViews=" + mRemoveViews); final View veto = v.findViewById(R.id.veto); if (veto != null && veto.getVisibility() != View.GONE && mRemoveViews) { veto.performClick(); @@ -229,6 +230,7 @@ public class NotificationRowLayout * get removed properly. */ public void setViewRemoval(boolean removeViews) { + if (DEBUG) Slog.v(TAG, "setViewRemoval: " + removeViews); mRemoveViews = removeViews; } diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java index 1fb63db66535..472fd1a4f844 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java @@ -34,6 +34,7 @@ import android.media.AudioManager; import android.media.SoundPool; import android.os.Handler; import android.os.LocalPowerManager; +import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; @@ -228,7 +229,7 @@ public class KeyguardViewMediator implements KeyguardViewCallback { private KeyguardUpdateMonitor mUpdateMonitor; - private boolean mScreenOn = false; + private boolean mScreenOn; // last known state of the cellular connection private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE; @@ -393,6 +394,8 @@ public class KeyguardViewMediator implements KeyguardViewCallback { final ContentResolver cr = mContext.getContentResolver(); mShowLockIcon = (Settings.System.getInt(cr, "show_status_bar_lock", 0) == 1); + mScreenOn = mPM.isScreenOn(); + mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); String soundPath = Settings.System.getString(cr, Settings.System.LOCK_SOUND); if (soundPath != null) { @@ -974,7 +977,7 @@ public class KeyguardViewMediator implements KeyguardViewCallback { * interacts with the keyguard ui should be posted to this handler, rather * than called directly. */ - private Handler mHandler = new Handler() { + private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) { @Override public void handleMessage(Message msg) { switch (msg.what) { diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java index 82181d36359c..86451721adb6 100644 --- a/policy/src/com/android/internal/policy/impl/LockScreen.java +++ b/policy/src/com/android/internal/policy/impl/LockScreen.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; +import android.os.UserId; import android.os.Vibrator; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -275,7 +276,8 @@ class LockScreen extends LinearLayout implements KeyguardScreen { // Update the search icon with drawable from the search .apk if (!mSearchDisabled) { - Intent intent = SearchManager.getAssistIntent(mContext); + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserId.USER_CURRENT); if (intent != null) { // XXX Hack. We need to substitute the icon here but haven't formalized // the public API. The "_google" metadata will be going away, so @@ -309,7 +311,9 @@ class LockScreen extends LinearLayout implements KeyguardScreen { final int resId = mGlowPadView.getResourceIdForTarget(target); switch (resId) { case com.android.internal.R.drawable.ic_action_assist_generic: - Intent assistIntent = SearchManager.getAssistIntent(mContext); + Intent assistIntent = + ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserId.USER_CURRENT); if (assistIntent != null) { launchActivity(assistIntent); } else { @@ -335,6 +339,10 @@ class LockScreen extends LinearLayout implements KeyguardScreen { } } + /** + * Launches the said intent for the current foreground user. + * @param intent + */ private void launchActivity(Intent intent) { intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK @@ -346,7 +354,7 @@ class LockScreen extends LinearLayout implements KeyguardScreen { Log.w(TAG, "can't dismiss keyguard on launch"); } try { - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, UserId.USER_CURRENT); } catch (ActivityNotFoundException e) { Log.w(TAG, "Activity not found for intent + " + intent.getAction()); } @@ -522,7 +530,9 @@ class LockScreen extends LinearLayout implements KeyguardScreen { } else if (disabledBySimState) { Log.v(TAG, "Camera disabled by Sim State"); } - boolean searchActionAvailable = SearchManager.getAssistIntent(mContext) != null; + boolean searchActionAvailable = + ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserId.USER_CURRENT) != null; mCameraDisabled = disabledByAdmin || disabledBySimState || !cameraTargetPresent; mSearchDisabled = disabledBySimState || !searchActionAvailable || !searchTargetPresent; mUnlockWidgetMethods.updateResources(); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 8c627a3d6f3b..769b51355982 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -55,6 +55,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UEventObserver; +import android.os.UserId; import android.os.Vibrator; import android.provider.Settings; @@ -2110,7 +2111,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (searchManager != null) { searchManager.stopSearch(); } - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, UserId.USER_CURRENT); } catch (ActivityNotFoundException e) { Slog.w(TAG, "No activity to handle assist long press action.", e); } @@ -2118,13 +2119,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void launchAssistAction() { sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST); - Intent intent = SearchManager.getAssistIntent(mContext); + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserId.USER_CURRENT); if (intent != null) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); try { - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, UserId.USER_CURRENT); } catch (ActivityNotFoundException e) { Slog.w(TAG, "No activity to handle assist action.", e); } diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 9acffa30ba99..36f2c14924bd 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -28,6 +28,7 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -477,6 +478,11 @@ public class MockContext extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + throw new UnsupportedOperationException(); + } + + @Override public boolean isRestricted() { throw new UnsupportedOperationException(); } diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index 0ec1f137c6f8..0577dbba901e 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -40,12 +40,16 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.content.Context; +import android.content.res.Configuration; import android.util.Log; public class ActivityTestMain extends Activity { static final String TAG = "ActivityTest"; + static final String KEY_CONFIGURATION = "configuration"; + ActivityManager mAm; + Configuration mOverrideConfig; class BroadcastResultReceiver extends BroadcastReceiver { @Override @@ -111,6 +115,12 @@ public class ActivityTestMain extends Activity { super.onCreate(savedInstanceState); mAm = (ActivityManager)getSystemService(ACTIVITY_SERVICE); + if (savedInstanceState != null) { + mOverrideConfig = savedInstanceState.getParcelable(KEY_CONFIGURATION); + if (mOverrideConfig != null) { + applyOverrideConfiguration(mOverrideConfig); + } + } } @Override @@ -182,6 +192,21 @@ public class ActivityTestMain extends Activity { return true; } }); + menu.add("Density!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + if (mOverrideConfig == null) { + mOverrideConfig = new Configuration(); + } + if (mOverrideConfig.densityDpi == Configuration.DENSITY_DPI_UNDEFINED) { + mOverrideConfig.densityDpi = (getApplicationContext().getResources() + .getConfiguration().densityDpi*2)/3; + } else { + mOverrideConfig.densityDpi = Configuration.DENSITY_DPI_UNDEFINED; + } + recreate(); + return true; + } + }); return true; } @@ -191,6 +216,14 @@ public class ActivityTestMain extends Activity { buildUi(); } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (mOverrideConfig != null) { + outState.putParcelable(KEY_CONFIGURATION, mOverrideConfig); + } + } + private View scrollWrap(View view) { ScrollView scroller = new ScrollView(this); scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT, diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 185703309b2e..c783ad62db8a 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -177,6 +177,15 @@ </activity> <activity + android:name="GlyphCacheActivity" + android:label="_GlyphCache"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity android:name="CanvasTextureViewActivity" android:label="_CanvasTextureView"> <intent-filter> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GlyphCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GlyphCacheActivity.java new file mode 100644 index 000000000000..e89b2948062b --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GlyphCacheActivity.java @@ -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. + */ + +package com.android.test.hwui; + + +import android.app.Activity; +import android.os.Bundle; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import static android.widget.LinearLayout.LayoutParams; + +public class GlyphCacheActivity extends Activity { + + private static final String mCharacterSet = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "~!@#$%^&*()_+-={}[]:\";'<>?,./"; + private int mTotalChars = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + ScrollView scrollView = new ScrollView(this); + scrollView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + scrollView.addView(layout); + + while (mTotalChars < 10000) { + layout.addView(createTextView()); + } + setContentView(scrollView); + } + + private TextView createTextView() { + TextView textview = new TextView(this); + textview.setTextSize(6 + (int) (Math.random() * 5) * 10); + textview.setTextColor(0xff << 24 | (int) (Math.random() * 255) << 16 | + (int) (Math.random() * 255) << 8 | (int) (Math.random() * 255) << 16); + textview.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + int numChars = 5 + (int) (Math.random() * 10); + mTotalChars += numChars; + textview.setText(createString(numChars)); + + return textview; + } + + private String createString(int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(mCharacterSet.charAt((int)(Math.random() * mCharacterSet.length()))); + } + return sb.toString(); + } +} diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java index 7cd485ebedd3..3eec7f5bf0e0 100644 --- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java @@ -167,6 +167,9 @@ public class ImageProcessingActivity extends Activity case 14: mTest = new GroupTest(false); break; + case 15: + mTest = new Intrinsics(0); + break; } mTest.createBaseTest(this, mBitmapIn); @@ -179,7 +182,7 @@ public class ImageProcessingActivity extends Activity } void setupTests() { - mTestNames = new String[15]; + mTestNames = new String[16]; mTestNames[0] = "Levels Vec3 Relaxed"; mTestNames[1] = "Levels Vec4 Relaxed"; mTestNames[2] = "Levels Vec3 Full"; @@ -195,6 +198,7 @@ public class ImageProcessingActivity extends Activity mTestNames[12] = "Vignette Approximate Relaxed"; mTestNames[13] = "Group Test (emulated)"; mTestNames[14] = "Group Test (native)"; + mTestNames[15] = "Intrinsics Convolve 3x3"; mTestSpinner.setAdapter(new ArrayAdapter<String>( this, R.layout.spinner_layout, mTestNames)); } diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Intrinsics.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Intrinsics.java new file mode 100644 index 000000000000..ea8a01855016 --- /dev/null +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Intrinsics.java @@ -0,0 +1,62 @@ +/* + * 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.image; + +import java.lang.Math; + +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.Script; +import android.renderscript.ScriptIntrinsicConvolve3x3; +import android.renderscript.Type; +import android.util.Log; +import android.widget.SeekBar; +import android.widget.TextView; + +public class Intrinsics extends TestBase { + private ScriptIntrinsicConvolve3x3 mScript; + + Intrinsics(int id) { + } + + public boolean onBar1Setup(SeekBar b, TextView t) { + t.setText("Strength"); + b.setProgress(50); + return true; + } + + public void onBar1Changed(int progress) { + float s = progress / 100.0f; + float v[] = new float[9]; + v[0] = 0.f; v[1] = -s; v[2] = 0.f; + v[3] = -s; v[4] = s*4+1; v[5] = -s; + v[6] = 0.f; v[7] = -s; v[8] = 0.f; + mScript.setValues(v); + } + + + public void createTest(android.content.res.Resources res) { + mScript = ScriptIntrinsicConvolve3x3.create(mRS, Element.RGBA_8888(mRS)); + } + + public void runTest() { + mScript.forEach(mInPixelsAllocation, mOutPixelsAllocation); + } + +} + diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index c4a69067aec0..0a1191ba96d0 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -917,6 +917,12 @@ public final class BridgeContext extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + // pass + return null; + } + + @Override public String[] databaseList() { // pass return null; diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index 3e207569556a..32261de7c6ab 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -47,37 +47,19 @@ public class ScanResult implements Parcelable { public int frequency; /** - * Time Synchronization Function (tsf) timestamp in microseconds when - * this result was last seen. - */ - public long timestamp; - - /** * We'd like to obtain the following attributes, * but they are not reported via the socket * interface, even though they are known * internally by wpa_supplicant. * {@hide} */ - public ScanResult(String SSID, String BSSID, String caps, int level, int frequency, long tsf) { + public ScanResult(String SSID, String BSSID, String caps, int level, int frequency) { this.SSID = SSID; this.BSSID = BSSID; this.capabilities = caps; this.level = level; this.frequency = frequency; - this.timestamp = tsf; - } - - /** copy constructor {@hide} */ - public ScanResult(ScanResult source) { - if (source != null) { - SSID = source.SSID; - BSSID = source.BSSID; - capabilities = source.capabilities; - level = source.level; - frequency = source.frequency; - timestamp = source.timestamp; - } + //networkConfig = null; } @Override @@ -94,9 +76,7 @@ public class ScanResult implements Parcelable { append(", level: "). append(level). append(", frequency: "). - append(frequency). - append(", timestamp: "). - append(timestamp); + append(frequency); return sb.toString(); } @@ -113,7 +93,6 @@ public class ScanResult implements Parcelable { dest.writeString(capabilities); dest.writeInt(level); dest.writeInt(frequency); - dest.writeLong(timestamp); } /** Implement the Parcelable interface {@hide} */ @@ -125,8 +104,7 @@ public class ScanResult implements Parcelable { in.readString(), in.readString(), in.readInt(), - in.readInt(), - in.readLong() + in.readInt() ); } diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 1b7e3782374e..84c565b0a4ef 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -197,22 +197,8 @@ public class WifiNative { return null; } - /** - * Format of results: - * ================= - * bssid=68:7f:74:d7:1b:6e - * freq=2412 - * level=-43 - * tsf=1344621975160944 - * age=2623 - * flags=[WPA2-PSK-CCMP][WPS][ESS] - * ssid=zubyb - * - * RANGE=ALL gets all scan results - * MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details - */ public String scanResults() { - return doStringCommand("BSS RANGE=ALL MASK=0x1986"); + return doStringCommand("SCAN_RESULTS"); } public boolean startDriver() { diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index bdb02c524d20..28c1c5c344d6 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -891,13 +891,7 @@ public class WifiStateMachine extends StateMachine { * TODO: doc */ public List<ScanResult> syncGetScanResultsList() { - synchronized (mScanResultCache) { - List<ScanResult> scanList = new ArrayList<ScanResult>(); - for(ScanResult result: mScanResults) { - scanList.add(new ScanResult(result)); - } - return scanList; - } + return mScanResults; } /** @@ -1363,103 +1357,131 @@ public class WifiStateMachine extends StateMachine { mContext.sendStickyBroadcast(intent); } - private static final String BSSID_STR = "bssid="; - private static final String FREQ_STR = "freq="; - private static final String LEVEL_STR = "level="; - private static final String TSF_STR = "tsf="; - private static final String FLAGS_STR = "flags="; - private static final String SSID_STR = "ssid="; - private static final String DELIMITER_STR = "===="; /** - * Format: - * bssid=68:7f:76:d7:1a:6e - * freq=2412 - * level=-44 - * tsf=1344626243700342 - * flags=[WPA2-PSK-CCMP][WPS][ESS] - * ssid=zfdy - * ==== - * bssid=68:5f:74:d7:1a:6f - * freq=5180 - * level=-73 - * tsf=1344626243700373 - * flags=[WPA2-PSK-CCMP][WPS][ESS] - * ssid=zuby - * ==== + * Parse the scan result line passed to us by wpa_supplicant (helper). + * @param line the line to parse + * @return the {@link ScanResult} object */ - private void setScanResults(String scanResults) { - String bssid = ""; - int level = 0; - int freq = 0; - long tsf = 0; - String flags = ""; - String ssid = ""; - - if (scanResults == null) { - return; - } - - synchronized(mScanResultCache) { - mScanResults = new ArrayList<ScanResult>(); - String[] lines = scanResults.split("\n"); - - for (String line : lines) { - if (line.startsWith(BSSID_STR)) { - bssid = line.substring(BSSID_STR.length()); - } else if (line.startsWith(FREQ_STR)) { - try { - freq = Integer.parseInt(line.substring(FREQ_STR.length())); - } catch (NumberFormatException e) { - freq = 0; - } - } else if (line.startsWith(LEVEL_STR)) { + private ScanResult parseScanResult(String line) { + ScanResult scanResult = null; + if (line != null) { + /* + * Cache implementation (LinkedHashMap) is not synchronized, thus, + * must synchronized here! + */ + synchronized (mScanResultCache) { + String[] result = scanResultPattern.split(line); + if (3 <= result.length && result.length <= 5) { + String bssid = result[0]; + // bssid | frequency | level | flags | ssid + int frequency; + int level; try { - level = Integer.parseInt(line.substring(LEVEL_STR.length())); + frequency = Integer.parseInt(result[1]); + level = Integer.parseInt(result[2]); /* some implementations avoid negative values by adding 256 * so we need to adjust for that here. */ if (level > 0) level -= 256; - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { + frequency = 0; level = 0; } - } else if (line.startsWith(TSF_STR)) { - try { - tsf = Long.parseLong(line.substring(TSF_STR.length())); - } catch (NumberFormatException e) { - tsf = 0; - } - } else if (line.startsWith(FLAGS_STR)) { - flags = line.substring(FLAGS_STR.length()); - } else if (line.startsWith(SSID_STR)) { - ssid = line.substring(SSID_STR.length()); - if (ssid == null) ssid = ""; - } else if (line.startsWith(DELIMITER_STR)) { - if (bssid != null) { - String key = bssid + ssid; - ScanResult scanResult = mScanResultCache.get(key); - if (scanResult != null) { - scanResult.level = level; - scanResult.SSID = ssid; - scanResult.capabilities = flags; - scanResult.frequency = freq; - scanResult.timestamp = tsf; + + /* + * The formatting of the results returned by + * wpa_supplicant is intended to make the fields + * line up nicely when printed, + * not to make them easy to parse. So we have to + * apply some heuristics to figure out which field + * is the SSID and which field is the flags. + */ + String ssid; + String flags; + if (result.length == 4) { + if (result[3].charAt(0) == '[') { + flags = result[3]; + ssid = ""; } else { + flags = ""; + ssid = result[3]; + } + } else if (result.length == 5) { + flags = result[3]; + ssid = result[4]; + } else { + // Here, we must have 3 fields: no flags and ssid + // set + flags = ""; + ssid = ""; + } + + // bssid + ssid is the hash key + String key = bssid + ssid; + scanResult = mScanResultCache.get(key); + if (scanResult != null) { + scanResult.level = level; + scanResult.SSID = ssid; + scanResult.capabilities = flags; + scanResult.frequency = frequency; + } else { + // Do not add scan results that have no SSID set + if (0 < ssid.trim().length()) { scanResult = new ScanResult( - ssid, bssid, flags, level, freq, tsf); + ssid, bssid, flags, level, frequency); mScanResultCache.put(key, scanResult); } - mScanResults.add(scanResult); - } - bssid = null; - level = 0; - freq = 0; - tsf = 0; - flags = ""; - ssid = ""; + } + } else { + loge("Misformatted scan result text with " + + result.length + " fields: " + line); + } + } + } + + return scanResult; + } + + /** + * scanResults input format + * 00:bb:cc:dd:cc:ee 2427 166 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net1 + * 00:bb:cc:dd:cc:ff 2412 165 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net2 + */ + private void setScanResults(String scanResults) { + if (scanResults == null) { + return; + } + + List<ScanResult> scanList = new ArrayList<ScanResult>(); + + int lineCount = 0; + + int scanResultsLen = scanResults.length(); + // Parse the result string, keeping in mind that the last line does + // not end with a newline. + for (int lineBeg = 0, lineEnd = 0; lineEnd <= scanResultsLen; ++lineEnd) { + if (lineEnd == scanResultsLen || scanResults.charAt(lineEnd) == '\n') { + ++lineCount; + + if (lineCount == 1) { + lineBeg = lineEnd + 1; + continue; } + if (lineEnd > lineBeg) { + String line = scanResults.substring(lineBeg, lineEnd); + ScanResult scanResult = parseScanResult(line); + if (scanResult != null) { + scanList.add(scanResult); + } else { + //TODO: hidden network handling + } + } + lineBeg = lineEnd + 1; } } + + mScanResults = scanList; } /* @@ -2805,7 +2827,7 @@ public class WifiStateMachine extends StateMachine { if (DBG) log(getName() + "\n"); mIsRunning = false; updateBatteryWorkSource(null); - mScanResults = new ArrayList<ScanResult>(); + mScanResults = null; if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P); mContext.unregisterReceiver(mScreenReceiver); |