summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt2
-rw-r--r--core/java/android/app/ActivityThread.java84
-rw-r--r--core/java/android/app/ContextImpl.java47
-rw-r--r--core/java/android/app/LoadedApk.java18
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/content/ContextWrapper.java6
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java8
-rw-r--r--core/java/android/content/res/Resources.java20
-rw-r--r--core/java/android/hardware/display/DisplayManager.java2
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java23
-rw-r--r--core/java/android/view/CompatibilityInfoHolder.java45
-rw-r--r--core/java/android/view/Display.java27
-rw-r--r--core/java/android/view/DisplayAdjustments.java93
-rw-r--r--core/java/android/view/DisplayInfo.java30
-rw-r--r--core/java/android/view/ViewGroup.java4
-rw-r--r--core/java/android/view/ViewOverlay.java11
-rw-r--r--core/java/android/view/ViewRootImpl.java14
-rw-r--r--core/java/android/widget/AbsListView.java49
-rw-r--r--core/java/android/widget/FastScroller.java1565
-rw-r--r--core/res/res/values/dimens.xml2
-rwxr-xr-xcore/res/res/values/symbols.xml1
-rw-r--r--docs/html/distribute/distribute_toc.cs1
-rw-r--r--docs/html/distribute/googleplay/edu/contact.jd2
-rw-r--r--docs/html/distribute/googleplay/edu/guidelines.jd252
-rw-r--r--docs/html/distribute/googleplay/edu/start.jd324
-rw-r--r--docs/html/distribute/googleplay/publish/preparing.jd4
-rw-r--r--docs/html/images/gp-edu-optin.pngbin0 -> 44831 bytes
-rw-r--r--docs/html/images/gp-edu-process.pngbin0 -> 41824 bytes
-rw-r--r--opengl/java/android/opengl/EGL14.java1
-rw-r--r--opengl/java/android/opengl/EGLConfig.java2
-rw-r--r--opengl/java/android/opengl/EGLContext.java2
-rw-r--r--opengl/java/android/opengl/EGLDisplay.java2
-rw-r--r--opengl/java/android/opengl/EGLExt.java1
-rw-r--r--opengl/java/android/opengl/EGLSurface.java2
-rw-r--r--opengl/java/android/opengl/Matrix.java108
-rw-r--r--services/java/com/android/server/am/ActivityStack.java2
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java6
-rw-r--r--test-runner/src/android/test/mock/MockContext.java4
38 files changed, 1861 insertions, 913 deletions
diff --git a/api/current.txt b/api/current.txt
index 626f52c..a6f2cf5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16899,7 +16899,7 @@ package android.opengl {
}
public class Matrix {
- ctor public Matrix();
+ ctor public deprecated Matrix();
method public static void frustumM(float[], int, float, float, float, float, float, float);
method public static boolean invertM(float[], int, float[], int);
method public static float length(float, float, float);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4a41896..066775d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.view.DisplayAdjustments.DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN;
+
import android.app.backup.BackupAgent;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
@@ -75,7 +77,7 @@ import android.util.Log;
import android.util.LogPrinter;
import android.util.PrintWriterPrinter;
import android.util.Slog;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
import android.view.Display;
import android.view.HardwareRenderer;
import android.view.View;
@@ -209,8 +211,8 @@ public final class ActivityThread {
= new HashMap<String, WeakReference<LoadedApk>>();
final HashMap<String, WeakReference<LoadedApk>> mResourcePackages
= new HashMap<String, WeakReference<LoadedApk>>();
- final HashMap<CompatibilityInfo, DisplayMetrics> mDefaultDisplayMetrics
- = new HashMap<CompatibilityInfo, DisplayMetrics>();
+ final HashMap<DisplayAdjustments, DisplayMetrics> mDefaultDisplayMetrics
+ = new HashMap<DisplayAdjustments, DisplayMetrics>();
final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
= new HashMap<ResourcesKey, WeakReference<Resources> >();
final ArrayList<ActivityClientRecord> mRelaunchingActivities
@@ -1554,6 +1556,7 @@ public final class ActivityThread {
}
private class Idler implements MessageQueue.IdleHandler {
+ @Override
public final boolean queueIdle() {
ActivityClientRecord a = mNewActivities;
boolean stopProfiling = false;
@@ -1592,6 +1595,7 @@ public final class ActivityThread {
}
final class GcIdler implements MessageQueue.IdleHandler {
+ @Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
@@ -1604,8 +1608,10 @@ public final class ActivityThread {
final private Configuration mOverrideConfiguration;
final private float mScale;
final private int mHash;
+ final private IBinder mToken;
- ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
+ ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration,
+ float scale, IBinder token) {
mResDir = resDir;
mDisplayId = displayId;
if (overrideConfiguration != null) {
@@ -1621,6 +1627,12 @@ public final class ActivityThread {
hash = 31 * hash + (mOverrideConfiguration != null
? mOverrideConfiguration.hashCode() : 0);
hash = 31 * hash + Float.floatToIntBits(mScale);
+ if (DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN) {
+ mToken = token;
+ hash = 31 * hash + (mToken == null ? 0 : mToken.hashCode());
+ } else {
+ mToken = null;
+ }
mHash = hash;
}
@@ -1693,9 +1705,13 @@ public final class ActivityThread {
mDefaultDisplayMetrics.clear();
}
- DisplayMetrics getDisplayMetricsLocked(int displayId, CompatibilityInfo ci) {
+ DisplayMetrics getDisplayMetricsLocked(int displayId) {
+ return getDisplayMetricsLocked(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ }
+
+ DisplayMetrics getDisplayMetricsLocked(int displayId, DisplayAdjustments daj) {
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
- DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(ci) : null;
+ DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(daj) : null;
if (dm != null) {
return dm;
}
@@ -1709,12 +1725,10 @@ public final class ActivityThread {
}
if (isDefaultDisplay) {
- mDefaultDisplayMetrics.put(ci, dm);
+ mDefaultDisplayMetrics.put(daj, dm);
}
- CompatibilityInfoHolder cih = new CompatibilityInfoHolder();
- cih.set(ci);
- Display d = displayManager.getCompatibleDisplay(displayId, cih);
+ Display d = displayManager.getCompatibleDisplay(displayId, daj);
if (d != null) {
d.getMetrics(dm);
} else {
@@ -1736,7 +1750,7 @@ public final class ActivityThread {
if (config == null) {
return null;
}
- if (compat != null && !compat.supportsScreen()) {
+ if (!compat.supportsScreen()) {
mMainThreadConfig.setTo(config);
config = mMainThreadConfig;
compat.applyToConfiguration(displayDensity, config);
@@ -1748,21 +1762,19 @@ public final class ActivityThread {
* Creates the top level Resources for applications with the given compatibility info.
*
* @param resDir the resource directory.
- * @param compInfo the compability info. It will use the default compatibility info when it's
- * null.
+ * @param compatInfo the compability info. Must not be null.
+ * @param token the application token for determining stack bounds.
*/
- Resources getTopLevelResources(String resDir,
- int displayId, Configuration overrideConfiguration,
- CompatibilityInfo compInfo) {
- ResourcesKey key = new ResourcesKey(resDir,
- displayId, overrideConfiguration,
- compInfo.applicationScale);
+ Resources getTopLevelResources(String resDir, int displayId,
+ Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
+ final float scale = compatInfo.applicationScale;
+ ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
+ token);
Resources r;
synchronized (mPackages) {
// Resources is app scale dependent.
if (false) {
- Slog.w(TAG, "getTopLevelResources: " + resDir + " / "
- + compInfo.applicationScale);
+ Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
}
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
@@ -1787,7 +1799,7 @@ public final class ActivityThread {
}
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
- DisplayMetrics dm = getDisplayMetricsLocked(displayId, null);
+ DisplayMetrics dm = getDisplayMetricsLocked(displayId);
Configuration config;
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
if (!isDefaultDisplay || key.mOverrideConfiguration != null) {
@@ -1801,7 +1813,7 @@ public final class ActivityThread {
} else {
config = getConfiguration();
}
- r = new Resources(assets, dm, config, compInfo);
+ r = new Resources(assets, dm, config, compatInfo, token);
if (false) {
Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ r.getConfiguration() + " appScale="
@@ -1831,7 +1843,7 @@ public final class ActivityThread {
int displayId, Configuration overrideConfiguration,
LoadedApk pkgInfo) {
return getTopLevelResources(resDir, displayId, overrideConfiguration,
- pkgInfo.mCompatibilityInfo.get());
+ pkgInfo.getCompatibilityInfo(), null);
}
final Handler getHandler() {
@@ -2005,10 +2017,8 @@ public final class ActivityThread {
LoadedApk info = new LoadedApk(this, "android", context, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
context.init(info, null, this);
- context.getResources().updateConfiguration(
- getConfiguration(), getDisplayMetricsLocked(
- Display.DEFAULT_DISPLAY,
- CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO));
+ context.getResources().updateConfiguration(getConfiguration(),
+ getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));
mSystemContext = context;
//Slog.i(TAG, "Created system resources " + context.getResources()
// + ": " + context.getResources().getConfiguration());
@@ -2034,7 +2044,7 @@ public final class ActivityThread {
dalvik.system.VMRuntime.getRuntime().startJitCompilation();
}
}
-
+
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
@@ -2301,7 +2311,7 @@ public final class ActivityThread {
DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
for (int displayId : dm.getDisplayIds()) {
if (displayId != Display.DEFAULT_DISPLAY) {
- Display display = dm.getRealDisplay(displayId);
+ Display display = dm.getRealDisplay(displayId, r.token);
baseContext = appContext.createDisplayContext(display);
break;
}
@@ -3408,11 +3418,11 @@ public final class ActivityThread {
private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
LoadedApk apk = peekPackageInfo(data.pkg, false);
if (apk != null) {
- apk.mCompatibilityInfo.set(data.info);
+ apk.setCompatibilityInfo(data.info);
}
apk = peekPackageInfo(data.pkg, true);
if (apk != null) {
- apk.mCompatibilityInfo.set(data.info);
+ apk.setCompatibilityInfo(data.info);
}
handleConfigurationChanged(mConfiguration, data.info);
WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration);
@@ -3844,8 +3854,9 @@ public final class ActivityThread {
for (ActivityClientRecord ar : mActivities.values()) {
Activity a = ar.activity;
if (a != null) {
- Configuration thisConfig = applyConfigCompatMainThread(mCurDefaultDisplayDpi,
- newConfig, ar.packageInfo.mCompatibilityInfo.getIfNeeded());
+ Configuration thisConfig = applyConfigCompatMainThread(
+ mCurDefaultDisplayDpi, newConfig,
+ ar.packageInfo.getCompatibilityInfo());
if (!ar.activity.mFinished && (allActivities || !ar.paused)) {
// If the activity is currently resumed, its configuration
// needs to change right now.
@@ -3945,8 +3956,7 @@ public final class ActivityThread {
}
int changes = mResConfiguration.updateFrom(config);
flushDisplayMetricsLocked();
- DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(
- Display.DEFAULT_DISPLAY, null);
+ DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
if (compat != null && (mResCompatibilityInfo == null ||
!mResCompatibilityInfo.equals(compat))) {
@@ -3986,7 +3996,7 @@ public final class ActivityThread {
}
tmpConfig.setTo(config);
if (!isDefaultDisplay) {
- dm = getDisplayMetricsLocked(displayId, null);
+ dm = getDisplayMetricsLocked(displayId);
applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
}
if (overrideConfig != null) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 992d8b7..155aac1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -99,7 +99,7 @@ import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.WindowManagerImpl;
@@ -205,6 +205,8 @@ class ContextImpl extends Context {
private static final String[] EMPTY_FILE_LIST = {};
+ final private DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
+
/**
* Override this class when the system service constructor needs a
* ContextImpl. Else, use StaticServiceFetcher below.
@@ -1830,10 +1832,8 @@ class ContextImpl extends Context {
ContextImpl c = new ContextImpl();
c.init(mPackageInfo, null, mMainThread);
- c.mResources = mMainThread.getTopLevelResources(
- mPackageInfo.getResDir(),
- getDisplayId(), overrideConfiguration,
- mResources.getCompatibilityInfo());
+ c.mResources = mMainThread.getTopLevelResources(mPackageInfo.getResDir(), getDisplayId(),
+ overrideConfiguration, mResources.getCompatibilityInfo(), mActivityToken);
return c;
}
@@ -1844,17 +1844,13 @@ class ContextImpl extends Context {
}
int displayId = display.getDisplayId();
- CompatibilityInfo ci = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- CompatibilityInfoHolder cih = getCompatibilityInfo(displayId);
- if (cih != null) {
- ci = cih.get();
- }
ContextImpl context = new ContextImpl();
context.init(mPackageInfo, null, mMainThread);
context.mDisplay = display;
- context.mResources = mMainThread.getTopLevelResources(
- mPackageInfo.getResDir(), displayId, null, ci);
+ DisplayAdjustments daj = getDisplayAdjustments(displayId);
+ context.mResources = mMainThread.getTopLevelResources(mPackageInfo.getResDir(), displayId,
+ null, daj.getCompatibilityInfo(), null);
return context;
}
@@ -1868,8 +1864,8 @@ class ContextImpl extends Context {
}
@Override
- public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
- return displayId == Display.DEFAULT_DISPLAY ? mPackageInfo.mCompatibilityInfo : null;
+ public DisplayAdjustments getDisplayAdjustments(int displayId) {
+ return mDisplayAdjustments;
}
private File getDataDirFile() {
@@ -1921,6 +1917,7 @@ class ContextImpl extends Context {
mUser = context.mUser;
mDisplay = context.mDisplay;
mOuterContext = this;
+ mDisplayAdjustments.setCompatibilityInfo(mPackageInfo.getCompatibilityInfo());
}
final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) {
@@ -1933,16 +1930,26 @@ class ContextImpl extends Context {
mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
mResources = mPackageInfo.getResources(mainThread);
- if (mResources != null && container != null
- && container.getCompatibilityInfo().applicationScale !=
- mResources.getCompatibilityInfo().applicationScale) {
+ CompatibilityInfo compatInfo =
+ container == null ? null : container.getCompatibilityInfo();
+ if (mResources != null &&
+ ((compatInfo != null && compatInfo.applicationScale !=
+ mResources.getCompatibilityInfo().applicationScale)
+ || activityToken != null)) {
if (DEBUG) {
Log.d(TAG, "loaded context has different scaling. Using container's" +
" compatiblity info:" + container.getDisplayMetrics());
}
- mResources = mainThread.getTopLevelResources(
- mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY,
- null, container.getCompatibilityInfo());
+ if (compatInfo == null) {
+ compatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ }
+ mDisplayAdjustments.setCompatibilityInfo(compatInfo);
+ mDisplayAdjustments.setActivityToken(activityToken);
+ mResources = mainThread.getTopLevelResources(mPackageInfo.getResDir(),
+ Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
+ } else {
+ mDisplayAdjustments.setCompatibilityInfo(packageInfo.getCompatibilityInfo());
+ mDisplayAdjustments.setActivityToken(activityToken);
}
mMainThread = mainThread;
mActivityToken = activityToken;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 2224490..573a6aa 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -40,7 +40,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.Slog;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
import android.view.Display;
import java.io.File;
@@ -84,7 +84,7 @@ public final class LoadedApk {
private final ClassLoader mBaseClassLoader;
private final boolean mSecurityViolation;
private final boolean mIncludeCode;
- public final CompatibilityInfoHolder mCompatibilityInfo = new CompatibilityInfoHolder();
+ private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
Resources mResources;
private ClassLoader mClassLoader;
private Application mApplication;
@@ -132,7 +132,7 @@ public final class LoadedApk {
mBaseClassLoader = baseLoader;
mSecurityViolation = securityViolation;
mIncludeCode = includeCode;
- mCompatibilityInfo.set(compatInfo);
+ mDisplayAdjustments.setCompatibilityInfo(compatInfo);
if (mAppDir == null) {
if (ActivityThread.mSystemContext == null) {
@@ -141,7 +141,7 @@ public final class LoadedApk {
ActivityThread.mSystemContext.getResources().updateConfiguration(
mainThread.getConfiguration(),
mainThread.getDisplayMetricsLocked(
- Display.DEFAULT_DISPLAY, compatInfo),
+ Display.DEFAULT_DISPLAY, mDisplayAdjustments),
compatInfo);
//Slog.i(TAG, "Created system resources "
// + mSystemContext.getResources() + ": "
@@ -169,7 +169,7 @@ public final class LoadedApk {
mIncludeCode = true;
mClassLoader = systemContext.getClassLoader();
mResources = systemContext.getResources();
- mCompatibilityInfo.set(compatInfo);
+ mDisplayAdjustments.setCompatibilityInfo(compatInfo);
}
public String getPackageName() {
@@ -184,6 +184,14 @@ public final class LoadedApk {
return mSecurityViolation;
}
+ public CompatibilityInfo getCompatibilityInfo() {
+ return mDisplayAdjustments.getCompatibilityInfo();
+ }
+
+ public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
+ mDisplayAdjustments.setCompatibilityInfo(compatInfo);
+ }
+
/**
* Gets the array of shared libraries that are listed as
* used by the given package.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7c9117c..d8bf6ba 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -35,7 +35,7 @@ import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
import android.view.Display;
import android.view.WindowManager;
@@ -2760,15 +2760,15 @@ public abstract class Context {
public abstract Context createDisplayContext(Display display);
/**
- * Gets the compatibility info holder for this context. This information
- * is provided on a per-application basis and is used to simulate lower density
- * display metrics for legacy applications.
+ * Gets the display adjustments holder for this context. This information
+ * is provided on a per-application or activity basis and is used to simulate lower density
+ * display metrics for legacy applications and restricted screen sizes.
*
* @param displayId The display id for which to get compatibility info.
* @return The compatibility info holder, or null if not required by the application.
* @hide
*/
- public abstract CompatibilityInfoHolder getCompatibilityInfo(int displayId);
+ public abstract DisplayAdjustments getDisplayAdjustments(int displayId);
/**
* Indicates whether this Context is restricted.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 2f1bf8c..606a1f4 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -35,7 +35,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
import android.view.Display;
import java.io.File;
@@ -646,7 +646,7 @@ public class ContextWrapper extends Context {
/** @hide */
@Override
- public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
- return mBase.getCompatibilityInfo(displayId);
+ public DisplayAdjustments getDisplayAdjustments(int displayId) {
+ return mBase.getDisplayAdjustments(displayId);
}
}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 28c751c..da35ee9 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -471,8 +471,7 @@ public class CompatibilityInfo implements Parcelable {
* Compute the frame Rect for applications runs under compatibility mode.
*
* @param dm the display metrics used to compute the frame size.
- * @param orientation the orientation of the screen.
- * @param outRect the output parameter which will contain the result.
+ * @param outDm If non-null the width and height will be set to their scaled values.
* @return Returns the scaling factor for the window.
*/
public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) {
@@ -518,6 +517,9 @@ public class CompatibilityInfo implements Parcelable {
@Override
public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
try {
CompatibilityInfo oc = (CompatibilityInfo)o;
if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
@@ -579,10 +581,12 @@ public class CompatibilityInfo implements Parcelable {
public static final Parcelable.Creator<CompatibilityInfo> CREATOR
= new Parcelable.Creator<CompatibilityInfo>() {
+ @Override
public CompatibilityInfo createFromParcel(Parcel source) {
return new CompatibilityInfo(source);
}
+ @Override
public CompatibilityInfo[] newArray(int size) {
return new CompatibilityInfo[size];
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index cff974d..c24e0ee 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -28,6 +28,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -35,6 +36,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.TypedValue;
import android.util.LongSparseArray;
+import android.view.DisplayAdjustments;
import java.io.IOException;
import java.io.InputStream;
@@ -117,8 +119,9 @@ public class Resources {
private final Configuration mConfiguration = new Configuration();
/*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
private NativePluralRules mPluralRule;
-
- private CompatibilityInfo mCompatibilityInfo;
+
+ private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ private WeakReference<IBinder> mToken;
static {
sPreloadedDrawables = new LongSparseArray[2];
@@ -173,7 +176,7 @@ public class Resources {
* selecting/computing resource values (optional).
*/
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
- this(assets, metrics, config, null);
+ this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
/**
@@ -184,15 +187,16 @@ public class Resources {
* selecting/computing resource values.
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
- * @param compInfo this resource's compatibility info. It will use the default compatibility
- * info when it's null.
+ * @param compatInfo this resource's compatibility info. Must not be null.
+ * @param token The Activity token for determining stack affiliation. Usually null.
* @hide
*/
- public Resources(AssetManager assets, DisplayMetrics metrics,
- Configuration config, CompatibilityInfo compInfo) {
+ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
+ CompatibilityInfo compatInfo, IBinder token) {
mAssets = assets;
mMetrics.setToDefaults();
- mCompatibilityInfo = compInfo;
+ mCompatibilityInfo = compatInfo;
+ mToken = new WeakReference<IBinder>(token);
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index dcf50cd..9e2e4ba 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -159,7 +159,7 @@ public final class DisplayManager {
Display display = mDisplays.get(displayId);
if (display == null) {
display = mGlobal.getCompatibleDisplay(displayId,
- mContext.getCompatibilityInfo(displayId));
+ mContext.getDisplayAdjustments(displayId));
if (display != null) {
mDisplays.put(displayId, display);
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 3ab882d..320185d 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -28,7 +28,7 @@ import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -164,18 +164,18 @@ public final class DisplayManagerGlobal {
* Gets information about a logical display.
*
* The display metrics may be adjusted to provide compatibility
- * for legacy applications.
+ * for legacy applications or limited screen areas.
*
* @param displayId The logical display id.
- * @param cih The compatibility info, or null if none is required.
+ * @param daj The compatibility info and activityToken.
* @return The display object, or null if there is no display with the given id.
*/
- public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) {
+ public Display getCompatibleDisplay(int displayId, DisplayAdjustments daj) {
DisplayInfo displayInfo = getDisplayInfo(displayId);
if (displayInfo == null) {
return null;
}
- return new Display(this, displayId, displayInfo, cih);
+ return new Display(this, displayId, displayInfo, daj);
}
/**
@@ -185,7 +185,18 @@ public final class DisplayManagerGlobal {
* @return The display object, or null if there is no display with the given id.
*/
public Display getRealDisplay(int displayId) {
- return getCompatibleDisplay(displayId, null);
+ return getCompatibleDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ }
+
+ /**
+ * Gets information about a logical display without applying any compatibility metrics.
+ *
+ * @param displayId The logical display id.
+ * @param IBinder the activity token for this display.
+ * @return The display object, or null if there is no display with the given id.
+ */
+ public Display getRealDisplay(int displayId, IBinder token) {
+ return getCompatibleDisplay(displayId, new DisplayAdjustments(token));
}
public void registerDisplayListener(DisplayListener listener, Handler handler) {
diff --git a/core/java/android/view/CompatibilityInfoHolder.java b/core/java/android/view/CompatibilityInfoHolder.java
deleted file mode 100644
index fc8d684..0000000
--- a/core/java/android/view/CompatibilityInfoHolder.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.content.res.CompatibilityInfo;
-
-/** @hide */
-public class CompatibilityInfoHolder {
- private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
-
- public void set(CompatibilityInfo compatInfo) {
- if (compatInfo != null && (compatInfo.isScalingRequired()
- || !compatInfo.supportsScreen())) {
- mCompatInfo = compatInfo;
- } else {
- mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- }
- }
-
- public CompatibilityInfo get() {
- return mCompatInfo;
- }
-
- public CompatibilityInfo getIfNeeded() {
- CompatibilityInfo ci = mCompatInfo;
- if (ci == null || ci == CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO) {
- return null;
- }
- return ci;
- }
-}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4d984fd..0e34435 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -16,6 +16,7 @@
package android.view;
+import android.content.res.CompatibilityInfo;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -60,7 +61,7 @@ public final class Display {
private final String mAddress;
private final int mOwnerUid;
private final String mOwnerPackageName;
- private final CompatibilityInfoHolder mCompatibilityInfo;
+ private final DisplayAdjustments mDisplayAdjustments;
private DisplayInfo mDisplayInfo; // never null
private boolean mIsValid;
@@ -203,11 +204,11 @@ public final class Display {
*/
public Display(DisplayManagerGlobal global,
int displayId, DisplayInfo displayInfo /*not null*/,
- CompatibilityInfoHolder compatibilityInfo) {
+ DisplayAdjustments daj) {
mGlobal = global;
mDisplayId = displayId;
mDisplayInfo = displayInfo;
- mCompatibilityInfo = compatibilityInfo;
+ mDisplayAdjustments = daj;
mIsValid = true;
// Cache properties that cannot change as long as the display is valid.
@@ -348,11 +349,11 @@ public final class Display {
/**
* Gets the compatibility info used by this display instance.
*
- * @return The compatibility info holder, or null if none is required.
+ * @return The display adjustments holder, or null if none is required.
* @hide
*/
- public CompatibilityInfoHolder getCompatibilityInfo() {
- return mCompatibilityInfo;
+ public DisplayAdjustments getDisplayAdjustments() {
+ return mDisplayAdjustments;
}
/**
@@ -393,7 +394,7 @@ public final class Display {
public void getSize(Point outSize) {
synchronized (this) {
updateDisplayInfoLocked();
- mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+ mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments);
outSize.x = mTempMetrics.widthPixels;
outSize.y = mTempMetrics.heightPixels;
}
@@ -408,7 +409,7 @@ public final class Display {
public void getRectSize(Rect outSize) {
synchronized (this) {
updateDisplayInfoLocked();
- mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+ mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments);
outSize.set(0, 0, mTempMetrics.widthPixels, mTempMetrics.heightPixels);
}
}
@@ -573,7 +574,7 @@ public final class Display {
public void getMetrics(DisplayMetrics outMetrics) {
synchronized (this) {
updateDisplayInfoLocked();
- mDisplayInfo.getAppMetrics(outMetrics, mCompatibilityInfo);
+ mDisplayInfo.getAppMetrics(outMetrics, mDisplayAdjustments);
}
}
@@ -611,7 +612,9 @@ public final class Display {
public void getRealMetrics(DisplayMetrics outMetrics) {
synchronized (this) {
updateDisplayInfoLocked();
- mDisplayInfo.getLogicalMetrics(outMetrics, null);
+ mDisplayInfo.getLogicalMetrics(outMetrics,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,
+ mDisplayAdjustments.getActivityToken());
}
}
@@ -658,7 +661,7 @@ public final class Display {
long now = SystemClock.uptimeMillis();
if (now > mLastCachedAppSizeUpdate + CACHED_APP_SIZE_DURATION_MILLIS) {
updateDisplayInfoLocked();
- mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+ mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments);
mCachedAppWidthCompat = mTempMetrics.widthPixels;
mCachedAppHeightCompat = mTempMetrics.heightPixels;
mLastCachedAppSizeUpdate = now;
@@ -670,7 +673,7 @@ public final class Display {
public String toString() {
synchronized (this) {
updateDisplayInfoLocked();
- mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+ mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments);
return "Display id " + mDisplayId + ": " + mDisplayInfo
+ ", " + mTempMetrics + ", isValid=" + mIsValid;
}
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
new file mode 100644
index 0000000..4a234ad
--- /dev/null
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.res.CompatibilityInfo;
+import android.os.IBinder;
+
+import com.android.internal.util.Objects;
+
+/** @hide */
+public class DisplayAdjustments {
+ public static final boolean DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN = false;
+
+ public static final DisplayAdjustments DEFAULT_DISPLAY_ADJUSTMENTS = new DisplayAdjustments();
+
+ private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ private volatile IBinder mActivityToken;
+
+ public DisplayAdjustments() {
+ }
+
+ public DisplayAdjustments(IBinder token) {
+ mActivityToken = token;
+ }
+
+ public DisplayAdjustments(CompatibilityInfo compatInfo, IBinder token) {
+ setCompatibilityInfo(compatInfo);
+ mActivityToken = token;
+ }
+
+ public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
+ if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
+ throw new IllegalArgumentException(
+ "setCompatbilityInfo: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+ }
+ if (compatInfo != null && (compatInfo.isScalingRequired()
+ || !compatInfo.supportsScreen())) {
+ mCompatInfo = compatInfo;
+ } else {
+ mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ }
+ }
+
+ public CompatibilityInfo getCompatibilityInfo() {
+ return mCompatInfo;
+ }
+
+ public void setActivityToken(IBinder token) {
+ if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
+ throw new IllegalArgumentException(
+ "setActivityToken: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+ }
+ mActivityToken = token;
+ }
+
+ public IBinder getActivityToken() {
+ return mActivityToken;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = hash * 31 + mCompatInfo.hashCode();
+ if (DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN) {
+ hash = hash * 31 + (mActivityToken == null ? 0 : mActivityToken.hashCode());
+ }
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof DisplayAdjustments)) {
+ return false;
+ }
+ DisplayAdjustments daj = (DisplayAdjustments)o;
+ return Objects.equal(daj.mCompatInfo, mCompatInfo) &&
+ Objects.equal(daj.mActivityToken, mActivityToken);
+ }
+}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 1442cb7..9a9c4cd 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -17,6 +17,7 @@
package android.view;
import android.content.res.CompatibilityInfo;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
@@ -343,12 +344,22 @@ public final class DisplayInfo implements Parcelable {
return 0;
}
- public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) {
- getMetricsWithSize(outMetrics, cih, appWidth, appHeight);
+ public void getAppMetrics(DisplayMetrics outMetrics) {
+ getAppMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
- public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) {
- getMetricsWithSize(outMetrics, cih, logicalWidth, logicalHeight);
+ public void getAppMetrics(DisplayMetrics outMetrics, DisplayAdjustments displayAdjustments) {
+ getMetricsWithSize(outMetrics, displayAdjustments.getCompatibilityInfo(),
+ displayAdjustments.getActivityToken(), appWidth, appHeight);
+ }
+
+ public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci, IBinder token) {
+ getMetricsWithSize(outMetrics, ci, token, appWidth, appHeight);
+ }
+
+ public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+ IBinder token) {
+ getMetricsWithSize(outMetrics, compatInfo, token, logicalWidth, logicalHeight);
}
public int getNaturalWidth() {
@@ -368,8 +379,8 @@ public final class DisplayInfo implements Parcelable {
return Display.hasAccess(uid, flags, ownerUid);
}
- private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfoHolder cih,
- int width, int height) {
+ private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+ IBinder token, int width, int height) {
outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
@@ -380,11 +391,8 @@ public final class DisplayInfo implements Parcelable {
outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
- if (cih != null) {
- CompatibilityInfo ci = cih.getIfNeeded();
- if (ci != null) {
- ci.applyToDisplayMetrics(outMetrics);
- }
+ if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
+ compatInfo.applyToDisplayMetrics(outMetrics);
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d3f9174..a40582b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4426,8 +4426,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Quick invalidation method that simply transforms the dirty rect into the parent's
* coordinate system, pruning the invalidation if the parent has already been invalidated.
+ *
+ * @hide
*/
- private ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) {
+ protected ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
dirty.offset(left - mScrollX, top - mScrollY);
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index 2d86bfe..975931a 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -300,6 +300,17 @@ public class ViewOverlay {
}
}
+ /**
+ * @hide
+ */
+ @Override
+ protected ViewParent invalidateChildInParentFast(int left, int top, Rect dirty) {
+ if (mHostView instanceof ViewGroup) {
+ return ((ViewGroup) mHostView).invalidateChildInParentFast(left, top, dirty);
+ }
+ return null;
+ }
+
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
if (mHostView != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9bc66be..93c6d6e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -239,7 +239,7 @@ public final class ViewRootImpl implements ViewParent,
boolean mAdded;
boolean mAddedTouchMode;
- final CompatibilityInfoHolder mCompatibilityInfo;
+ final DisplayAdjustments mDisplayAdjustments;
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
@@ -336,8 +336,7 @@ public final class ViewRootImpl implements ViewParent,
mDisplay = display;
mBasePackageName = context.getBasePackageName();
- CompatibilityInfoHolder cih = display.getCompatibilityInfo();
- mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder();
+ mDisplayAdjustments = display.getDisplayAdjustments();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
@@ -444,8 +443,9 @@ public final class ViewRootImpl implements ViewParent,
}
}
- CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
+ CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();
mTranslator = compatibilityInfo.getTranslator();
+ mDisplayAdjustments.setActivityToken(attrs.token);
// If the application owns the surface, don't enable hardware acceleration
if (mSurfaceHolder == null) {
@@ -1136,7 +1136,7 @@ public final class ViewRootImpl implements ViewParent,
surfaceChanged = true;
params = lp;
}
- CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
+ CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();
if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
params = lp;
mFullRedrawNeeded = true;
@@ -2847,8 +2847,8 @@ public final class ViewRootImpl implements ViewParent,
+ mWindowAttributes.getTitle()
+ ": " + config);
- CompatibilityInfo ci = mCompatibilityInfo.getIfNeeded();
- if (ci != null) {
+ CompatibilityInfo ci = mDisplayAdjustments.getCompatibilityInfo();
+ if (!ci.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
config = new Configuration(config);
ci.applyToConfiguration(mNoncompatDensity, config);
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1991af1..bb1f954 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1280,12 +1280,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
- * If fast scroll is visible, then don't draw the vertical scrollbar.
+ * If fast scroll is enabled, then don't draw the vertical scrollbar.
* @hide
*/
@Override
protected boolean isVerticalScrollBarHidden() {
- return mFastScroller != null && mFastScroller.isVisible();
+ return mFastScrollEnabled;
}
/**
@@ -1337,7 +1337,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
void invokeOnItemScrollListener() {
if (mFastScroller != null) {
- mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
+ mFastScroller.onScroll(mFirstPosition, getChildCount(), mItemCount);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
@@ -2009,7 +2009,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
mRecycler.markChildrenDirty();
}
-
+
if (mFastScroller != null && mItemCount != mOldItemCount) {
mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
}
@@ -3752,18 +3752,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
canvas.restoreToCount(restoreCount);
}
}
- if (mFastScroller != null) {
- final int scrollY = mScrollY;
- if (scrollY != 0) {
- // Pin to the top/bottom during overscroll
- int restoreCount = canvas.save();
- canvas.translate(0, scrollY);
- mFastScroller.draw(canvas);
- canvas.restoreToCount(restoreCount);
- } else {
- mFastScroller.draw(canvas);
- }
- }
}
/**
@@ -3820,11 +3808,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return false;
}
- if (mFastScroller != null) {
- boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
- if (intercepted) {
- return true;
- }
+ if (mFastScroller != null && mFastScroller.onInterceptTouchEvent(ev)) {
+ return true;
}
switch (action & MotionEvent.ACTION_MASK) {
@@ -5672,78 +5657,96 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return mDefInputConnection.sendKeyEvent(event);
}
+ @Override
public CharSequence getTextBeforeCursor(int n, int flags) {
if (mTarget == null) return "";
return mTarget.getTextBeforeCursor(n, flags);
}
+ @Override
public CharSequence getTextAfterCursor(int n, int flags) {
if (mTarget == null) return "";
return mTarget.getTextAfterCursor(n, flags);
}
+ @Override
public CharSequence getSelectedText(int flags) {
if (mTarget == null) return "";
return mTarget.getSelectedText(flags);
}
+ @Override
public int getCursorCapsMode(int reqModes) {
if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
return mTarget.getCursorCapsMode(reqModes);
}
+ @Override
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
return getTarget().getExtractedText(request, flags);
}
+ @Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
return getTarget().deleteSurroundingText(beforeLength, afterLength);
}
+ @Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
return getTarget().setComposingText(text, newCursorPosition);
}
+ @Override
public boolean setComposingRegion(int start, int end) {
return getTarget().setComposingRegion(start, end);
}
+ @Override
public boolean finishComposingText() {
return mTarget == null || mTarget.finishComposingText();
}
+ @Override
public boolean commitText(CharSequence text, int newCursorPosition) {
return getTarget().commitText(text, newCursorPosition);
}
+ @Override
public boolean commitCompletion(CompletionInfo text) {
return getTarget().commitCompletion(text);
}
+ @Override
public boolean commitCorrection(CorrectionInfo correctionInfo) {
return getTarget().commitCorrection(correctionInfo);
}
+ @Override
public boolean setSelection(int start, int end) {
return getTarget().setSelection(start, end);
}
+ @Override
public boolean performContextMenuAction(int id) {
return getTarget().performContextMenuAction(id);
}
+ @Override
public boolean beginBatchEdit() {
return getTarget().beginBatchEdit();
}
+ @Override
public boolean endBatchEdit() {
return getTarget().endBatchEdit();
}
+ @Override
public boolean clearMetaKeyStates(int states) {
return getTarget().clearMetaKeyStates(states);
}
+ @Override
public boolean performPrivateCommand(String action, Bundle data) {
return getTarget().performPrivateCommand(action, data);
}
@@ -6037,9 +6040,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
- *
+ *
* @param handler The OnClickHandler to use when inflating RemoteViews.
- *
+ *
* @hide
*/
public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index aa33384..62e1578 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -16,49 +16,66 @@
package android.widget;
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.NinePatchDrawable;
-import android.os.Handler;
-import android.os.SystemClock;
+import android.os.Build;
+import android.text.TextUtils.TruncateAt;
+import android.util.IntProperty;
+import android.util.MathUtils;
+import android.util.Property;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.MeasureSpec;
import android.view.ViewConfiguration;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroupOverlay;
import android.widget.AbsListView.OnScrollListener;
+import com.android.internal.R;
+
/**
* Helper class for AbsListView to draw and control the Fast Scroll thumb
*/
class FastScroller {
- private static final String TAG = "FastScroller";
+ /** Duration of fade-out animation. */
+ private static final int DURATION_FADE_OUT = 300;
+
+ /** Duration of fade-in animation. */
+ private static final int DURATION_FADE_IN = 150;
+
+ /** Duration of transition cross-fade animation. */
+ private static final int DURATION_CROSS_FADE = 50;
+
+ /** Duration of transition resize animation. */
+ private static final int DURATION_RESIZE = 100;
+
+ /** Inactivity timeout before fading controls. */
+ private static final long FADE_TIMEOUT = 1500;
- // Minimum number of pages to justify showing a fast scroll thumb
- private static int MIN_PAGES = 4;
- // Scroll thumb not showing
+ /** Minimum number of pages to justify showing a fast scroll thumb. */
+ private static final int MIN_PAGES = 4;
+
+ /** Scroll thumb and preview not showing. */
private static final int STATE_NONE = 0;
- // Not implemented yet - fade-in transition
- @SuppressWarnings("unused")
- private static final int STATE_ENTER = 1;
- // Scroll thumb visible and moving along with the scrollbar
- private static final int STATE_VISIBLE = 2;
- // Scroll thumb being dragged by user
- private static final int STATE_DRAGGING = 3;
- // Scroll thumb fading out due to inactivity timeout
- private static final int STATE_EXIT = 4;
-
- private static final int[] PRESSED_STATES = new int[] {
- android.R.attr.state_pressed
- };
- private static final int[] DEFAULT_STATES = new int[0];
+ /** Scroll thumb visible and moving along with the scrollbar. */
+ private static final int STATE_VISIBLE = 1;
+
+ /** Scroll thumb and preview being dragged by user. */
+ private static final int STATE_DRAGGING = 2;
+ /** Styleable attributes. */
private static final int[] ATTRS = new int[] {
android.R.attr.fastScrollTextColor,
android.R.attr.fastScrollThumbDrawable,
@@ -68,6 +85,7 @@ class FastScroller {
android.R.attr.fastScrollOverlayPosition
};
+ // Styleable attribute indices.
private static final int TEXT_COLOR = 0;
private static final int THUMB_DRAWABLE = 1;
private static final int TRACK_DRAWABLE = 2;
@@ -75,113 +93,247 @@ class FastScroller {
private static final int PREVIEW_BACKGROUND_RIGHT = 4;
private static final int OVERLAY_POSITION = 5;
+ // Positions for preview image and text.
private static final int OVERLAY_FLOATING = 0;
private static final int OVERLAY_AT_THUMB = 1;
- private Drawable mThumbDrawable;
- private Drawable mOverlayDrawable;
- private Drawable mTrackDrawable;
+ // Indices for mPreviewResId.
+ private static final int PREVIEW_LEFT = 0;
+ private static final int PREVIEW_RIGHT = 1;
+
+ /** Delay before considering a tap in the thumb area to be a drag. */
+ private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+
+ private final Rect mTempBounds = new Rect();
+ private final Rect mTempMargins = new Rect();
- private Drawable mOverlayDrawableLeft;
- private Drawable mOverlayDrawableRight;
+ private final AbsListView mList;
+ private final ViewGroupOverlay mOverlay;
+ private final TextView mPrimaryText;
+ private final TextView mSecondaryText;
+ private final ImageView mThumbImage;
+ private final ImageView mTrackImage;
+ private final ImageView mPreviewImage;
- int mThumbH;
- int mThumbW;
- int mThumbY;
+ /**
+ * Preview image resource IDs for left- and right-aligned layouts. See
+ * {@link #PREVIEW_LEFT} and {@link #PREVIEW_RIGHT}.
+ */
+ private final int[] mPreviewResId = new int[2];
- private RectF mOverlayPos;
- private int mOverlaySize;
- private int mOverlayPadding;
+ /**
+ * Padding in pixels around the preview text. Applied as layout margins to
+ * the preview text and padding to the preview image.
+ */
+ private final int mPreviewPadding;
- AbsListView mList;
- boolean mScrollCompleted;
- private int mVisibleItem;
- private Paint mPaint;
- private int mListOffset;
+ /** Whether there is a track image to display. */
+ private final boolean mHasTrackImage;
+
+ /** Set containing decoration transition animations. */
+ private AnimatorSet mDecorAnimation;
+
+ /** Set containing preview text transition animations. */
+ private AnimatorSet mPreviewAnimation;
+
+ /** Whether the primary text is showing. */
+ private boolean mShowingPrimary;
+
+ /** Whether we're waiting for completion of scrollTo(). */
+ private boolean mScrollCompleted;
+
+ /** The position of the first visible item in the list. */
+ private int mFirstVisibleItem;
+
+ /** The number of headers at the top of the view. */
+ private int mHeaderCount;
+
+ /** The number of items in the list. */
private int mItemCount = -1;
+
+ /** The index of the current section. */
+ private int mCurrentSection = -1;
+
+ /** Whether the list is long enough to need a fast scroller. */
private boolean mLongList;
- private Object [] mSections;
- private String mSectionText;
- private boolean mDrawOverlay;
- private ScrollFade mScrollFade;
+ private Object[] mSections;
+ /**
+ * Current decoration state, one of:
+ * <ul>
+ * <li>{@link #STATE_NONE}, nothing visible
+ * <li>{@link #STATE_VISIBLE}, showing track and thumb
+ * <li>{@link #STATE_DRAGGING}, visible and showing preview
+ * </ul>
+ */
private int mState;
- private Handler mHandler = new Handler();
-
- BaseAdapter mListAdapter;
+ private BaseAdapter mListAdapter;
private SectionIndexer mSectionIndexer;
- private boolean mChangedBounds;
-
- private int mPosition;
+ /** Whether decorations should be laid out from right to left. */
+ private boolean mLayoutFromRight;
+ /** Whether the scrollbar and decorations should always be shown. */
private boolean mAlwaysShow;
+ /**
+ * Position for the preview image and text. One of:
+ * <ul>
+ * <li>{@link #OVERLAY_AT_THUMB}
+ * <li>{@link #OVERLAY_FLOATING}
+ * </ul>
+ */
private int mOverlayPosition;
+ /** Whether to precisely match the thumb position to the list. */
private boolean mMatchDragPosition;
- float mInitialTouchY;
- boolean mPendingDrag;
+ private float mInitialTouchY;
+ private boolean mHasPendingDrag;
private int mScaledTouchSlop;
- private static final int FADE_TIMEOUT = 1500;
- private static final int PENDING_DRAG_DELAY = 180;
-
- private final Rect mTmpRect = new Rect();
-
private final Runnable mDeferStartDrag = new Runnable() {
@Override
public void run() {
if (mList.mIsAttached) {
beginDrag();
- final int viewHeight = mList.getHeight();
- // Jitter
- int newThumbY = (int) mInitialTouchY - mThumbH + 10;
- if (newThumbY < 0) {
- newThumbY = 0;
- } else if (newThumbY + mThumbH > viewHeight) {
- newThumbY = viewHeight - mThumbH;
- }
- mThumbY = newThumbY;
- scrollTo((float) mThumbY / (viewHeight - mThumbH));
+ final float pos = getPosFromMotionEvent(mInitialTouchY);
+ scrollTo(pos);
}
- mPendingDrag = false;
+ mHasPendingDrag = false;
+ }
+ };
+
+ /**
+ * Used to delay hiding fast scroll decorations.
+ */
+ private final Runnable mDeferHide = new Runnable() {
+ @Override
+ public void run() {
+ setState(STATE_NONE);
+ }
+ };
+
+ /**
+ * Used to effect a transition from primary to secondary text.
+ */
+ private final AnimatorListener mSwitchPrimaryListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mShowingPrimary = !mShowingPrimary;
}
};
public FastScroller(Context context, AbsListView listView) {
mList = listView;
- init(context);
+ mOverlay = listView.getOverlay();
+
+ mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+ final Resources res = context.getResources();
+ final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS);
+
+ mTrackImage = new ImageView(context);
+
+ // Add track to overlay if it has an image.
+ final int trackResId = ta.getResourceId(TRACK_DRAWABLE, 0);
+ if (trackResId != 0) {
+ mHasTrackImage = true;
+ mTrackImage.setBackgroundResource(trackResId);
+ mOverlay.add(mTrackImage);
+ } else {
+ mHasTrackImage = false;
+ }
+
+ mThumbImage = new ImageView(context);
+
+ // Add thumb to overlay if it has an image.
+ final Drawable thumbDrawable = ta.getDrawable(THUMB_DRAWABLE);
+ if (thumbDrawable != null) {
+ mThumbImage.setImageDrawable(thumbDrawable);
+ mOverlay.add(mThumbImage);
+ }
+
+ // If necessary, apply minimum thumb width and height.
+ if (thumbDrawable.getIntrinsicWidth() <= 0 || thumbDrawable.getIntrinsicHeight() <= 0) {
+ mThumbImage.setMinimumWidth(res.getDimensionPixelSize(R.dimen.fastscroll_thumb_width));
+ mThumbImage.setMinimumHeight(
+ res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height));
+ }
+
+ final int previewSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size);
+ mPreviewImage = new ImageView(context);
+ mPreviewImage.setMinimumWidth(previewSize);
+ mPreviewImage.setMinimumHeight(previewSize);
+ mPreviewImage.setAlpha(0f);
+ mOverlay.add(mPreviewImage);
+
+ mPreviewPadding = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_padding);
+
+ mPrimaryText = createPreviewTextView(context, ta);
+ mOverlay.add(mPrimaryText);
+ mSecondaryText = createPreviewTextView(context, ta);
+ mOverlay.add(mSecondaryText);
+
+ mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(PREVIEW_BACKGROUND_LEFT, 0);
+ mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(PREVIEW_BACKGROUND_RIGHT, 0);
+ mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING);
+ ta.recycle();
+
+ mScrollCompleted = true;
+ mState = STATE_VISIBLE;
+ mMatchDragPosition =
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
+
+ getSectionsFromIndexer();
+ refreshDrawablePressedState();
+ setScrollbarPosition(mList.getVerticalScrollbarPosition());
+
+ mList.postDelayed(mDeferHide, FADE_TIMEOUT);
}
+ /**
+ * @param alwaysShow Whether the fast scroll thumb should always be shown
+ */
public void setAlwaysShow(boolean alwaysShow) {
mAlwaysShow = alwaysShow;
+
if (alwaysShow) {
- mHandler.removeCallbacks(mScrollFade);
setState(STATE_VISIBLE);
} else if (mState == STATE_VISIBLE) {
- mHandler.postDelayed(mScrollFade, FADE_TIMEOUT);
+ mList.postDelayed(mDeferHide, FADE_TIMEOUT);
}
}
+ /**
+ * @return Whether the fast scroll thumb will always be shown
+ * @see #setAlwaysShow(boolean)
+ */
public boolean isAlwaysShowEnabled() {
return mAlwaysShow;
}
- private void refreshDrawableState() {
- int[] state = mState == STATE_DRAGGING ? PRESSED_STATES : DEFAULT_STATES;
+ /**
+ * Immediately transitions the fast scroller decorations to a hidden state.
+ */
+ public void stop() {
+ setState(STATE_NONE);
+ }
- if (mThumbDrawable != null && mThumbDrawable.isStateful()) {
- mThumbDrawable.setState(state);
- }
- if (mTrackDrawable != null && mTrackDrawable.isStateful()) {
- mTrackDrawable.setState(state);
+ /**
+ * @return Whether the fast scroll thumb should be shown.
+ */
+ public boolean shouldShow() {
+ // Don't show if the list is as tall as or shorter than the thumbnail.
+ if (mList.getHeight() <= mThumbImage.getHeight()) {
+ return false;
}
+
+ return true;
}
public void setScrollbarPosition(int position) {
@@ -189,374 +341,403 @@ class FastScroller {
position = mList.isLayoutRtl() ?
View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
}
- mPosition = position;
- switch (position) {
- default:
- case View.SCROLLBAR_POSITION_RIGHT:
- mOverlayDrawable = mOverlayDrawableRight;
- break;
- case View.SCROLLBAR_POSITION_LEFT:
- mOverlayDrawable = mOverlayDrawableLeft;
- break;
+
+ mLayoutFromRight = position != View.SCROLLBAR_POSITION_LEFT;
+
+ final int previewResId = mPreviewResId[mLayoutFromRight ? PREVIEW_RIGHT : PREVIEW_LEFT];
+ mPreviewImage.setBackgroundResource(previewResId);
+
+ // Add extra padding for text.
+ final Drawable background = mPreviewImage.getBackground();
+ if (background != null) {
+ final Rect padding = mTempBounds;
+ background.getPadding(padding);
+ padding.offset(mPreviewPadding, mPreviewPadding);
+ mPreviewImage.setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
+
+ updateLayout();
}
public int getWidth() {
- return mThumbW;
+ return mThumbImage.getWidth();
}
- public void setState(int state) {
- switch (state) {
- case STATE_NONE:
- mHandler.removeCallbacks(mScrollFade);
- mList.invalidate();
- break;
- case STATE_VISIBLE:
- if (mState != STATE_VISIBLE) { // Optimization
- resetThumbPos();
- }
- // Fall through
- case STATE_DRAGGING:
- mHandler.removeCallbacks(mScrollFade);
- break;
- case STATE_EXIT:
- final int viewWidth = mList.getWidth();
- final int top = mThumbY;
- final int bottom = mThumbY + mThumbH;
- final int left;
- final int right;
- switch (mList.getLayoutDirection()) {
- case View.LAYOUT_DIRECTION_RTL:
- left = 0;
- right = mThumbW;
- break;
- case View.LAYOUT_DIRECTION_LTR:
- default:
- left = viewWidth - mThumbW;
- right = viewWidth;
- }
- mList.invalidate(left, top, right, bottom);
- break;
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ updateLayout();
+ }
+
+ public void onItemCountChanged(int oldTotalItemCount, int totalItemCount) {
+ final int visibleItemCount = mList.getChildCount();
+ final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
+ if (hasMoreItems && mState != STATE_DRAGGING) {
+ final int firstVisibleItem = mList.getFirstVisiblePosition();
+ setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
}
- mState = state;
- refreshDrawableState();
}
- public int getState() {
- return mState;
+ /**
+ * Creates a view into which preview text can be placed.
+ */
+ private TextView createPreviewTextView(Context context, TypedArray ta) {
+ final LayoutParams params = new LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ final Resources res = context.getResources();
+ final int minSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size);
+ final ColorStateList textColor = ta.getColorStateList(TEXT_COLOR);
+ final float textSize = res.getDimension(R.dimen.fastscroll_overlay_text_size);
+ final TextView textView = new TextView(context);
+ textView.setLayoutParams(params);
+ textView.setTextColor(textColor);
+ textView.setTextSize(textSize);
+ textView.setSingleLine(true);
+ textView.setEllipsize(TruncateAt.MIDDLE);
+ textView.setGravity(Gravity.CENTER);
+ textView.setAlpha(0f);
+
+ // Manually propagate inherited layout direction.
+ textView.setLayoutDirection(mList.getLayoutDirection());
+
+ return textView;
}
- private void resetThumbPos() {
- final int viewWidth = mList.getWidth();
- // Bounds are always top right. Y coordinate get's translated during draw
- switch (mPosition) {
- case View.SCROLLBAR_POSITION_RIGHT:
- mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
- break;
- case View.SCROLLBAR_POSITION_LEFT:
- mThumbDrawable.setBounds(0, 0, mThumbW, mThumbH);
- break;
+ /**
+ * Measures and layouts the scrollbar and decorations.
+ */
+ private void updateLayout() {
+ layoutThumb();
+ layoutTrack();
+
+ final Rect bounds = mTempBounds;
+ measurePreview(mPrimaryText, bounds);
+ applyLayout(mPrimaryText, bounds);
+ measurePreview(mSecondaryText, bounds);
+ applyLayout(mSecondaryText, bounds);
+
+ if (mPreviewImage != null) {
+ // Apply preview image padding.
+ bounds.left -= mPreviewImage.getPaddingLeft();
+ bounds.top -= mPreviewImage.getPaddingTop();
+ bounds.right += mPreviewImage.getPaddingRight();
+ bounds.bottom += mPreviewImage.getPaddingBottom();
+ applyLayout(mPreviewImage, bounds);
}
- mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX);
}
- private void useThumbDrawable(Context context, Drawable drawable) {
- mThumbDrawable = drawable;
- if (drawable instanceof NinePatchDrawable) {
- mThumbW = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.fastscroll_thumb_width);
- mThumbH = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.fastscroll_thumb_height);
+ /**
+ * Layouts a view within the specified bounds and pins the pivot point to
+ * the appropriate edge.
+ *
+ * @param view The view to layout.
+ * @param bounds Bounds at which to layout the view.
+ */
+ private void applyLayout(View view, Rect bounds) {
+ view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ view.setPivotX(mLayoutFromRight ? bounds.right - bounds.left : 0);
+ }
+
+ /**
+ * Measures the preview text bounds, taking preview image padding into
+ * account. This method should only be called after {@link #layoutThumb()}
+ * and {@link #layoutTrack()} have both been called at least once.
+ *
+ * @param v The preview text view to measure.
+ * @param out Rectangle into which measured bounds are placed.
+ */
+ private void measurePreview(View v, Rect out) {
+ // Apply the preview image's padding as layout margins.
+ final Rect margins = mTempMargins;
+ margins.left = mPreviewImage.getPaddingLeft();
+ margins.top = mPreviewImage.getPaddingTop();
+ margins.right = mPreviewImage.getPaddingRight();
+ margins.bottom = mPreviewImage.getPaddingBottom();
+
+ if (mOverlayPosition == OVERLAY_AT_THUMB) {
+ measureViewToSide(v, mThumbImage, margins, out);
} else {
- mThumbW = drawable.getIntrinsicWidth();
- mThumbH = drawable.getIntrinsicHeight();
+ measureFloating(v, margins, out);
}
- mChangedBounds = true;
}
- private void init(Context context) {
- // Get both the scrollbar states drawables
- final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS);
- useThumbDrawable(context, ta.getDrawable(THUMB_DRAWABLE));
- mTrackDrawable = ta.getDrawable(TRACK_DRAWABLE);
-
- mOverlayDrawableLeft = ta.getDrawable(PREVIEW_BACKGROUND_LEFT);
- mOverlayDrawableRight = ta.getDrawable(PREVIEW_BACKGROUND_RIGHT);
- mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING);
-
- mScrollCompleted = true;
-
- getSectionsFromIndexer();
+ /**
+ * Measures the bounds for a view that should be laid out against the edge
+ * of an adjacent view. If no adjacent view is provided, lays out against
+ * the list edge.
+ *
+ * @param view The view to measure for layout.
+ * @param adjacent (Optional) The adjacent view, may be null to align to the
+ * list edge.
+ * @param margins Layout margins to apply to the view.
+ * @param out Rectangle into which measured bounds are placed.
+ */
+ private void measureViewToSide(View view, View adjacent, Rect margins, Rect out) {
+ final int marginLeft;
+ final int marginTop;
+ final int marginRight;
+ if (margins == null) {
+ marginLeft = 0;
+ marginTop = 0;
+ marginRight = 0;
+ } else {
+ marginLeft = margins.left;
+ marginTop = margins.top;
+ marginRight = margins.right;
+ }
- final Resources res = context.getResources();
- mOverlaySize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.fastscroll_overlay_size);
- mOverlayPadding = res.getDimensionPixelSize(
- com.android.internal.R.dimen.fastscroll_overlay_padding);
- mOverlayPos = new RectF();
- mScrollFade = new ScrollFade();
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
- mPaint.setTextAlign(Paint.Align.CENTER);
- mPaint.setTextSize(mOverlaySize / 2);
-
- ColorStateList textColor = ta.getColorStateList(TEXT_COLOR);
- int textColorNormal = textColor.getDefaultColor();
- mPaint.setColor(textColorNormal);
- mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-
- // to show mOverlayDrawable properly
- if (mList.getWidth() > 0 && mList.getHeight() > 0) {
- onSizeChanged(mList.getWidth(), mList.getHeight(), 0, 0);
- }
-
- mState = STATE_NONE;
- refreshDrawableState();
+ final int listWidth = mList.getWidth();
+ final int maxWidth;
+ if (adjacent == null) {
+ maxWidth = listWidth;
+ } else if (mLayoutFromRight) {
+ maxWidth = adjacent.getLeft();
+ } else {
+ maxWidth = listWidth - adjacent.getRight();
+ }
- ta.recycle();
+ final int adjMaxWidth = maxWidth - marginLeft - marginRight;
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ view.measure(widthMeasureSpec, heightMeasureSpec);
+
+ // Align to the left or right.
+ final int width = view.getMeasuredWidth();
+ final int left;
+ final int right;
+ if (mLayoutFromRight) {
+ right = (adjacent == null ? listWidth : adjacent.getLeft()) - marginRight;
+ left = right - width;
+ } else {
+ left = (adjacent == null ? 0 : adjacent.getRight()) + marginLeft;
+ right = left + width;
+ }
- mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ // Don't adjust the vertical position.
+ final int top = marginTop;
+ final int bottom = top + view.getMeasuredHeight();
+ out.set(left, top, right, bottom);
+ }
- mMatchDragPosition = context.getApplicationInfo().targetSdkVersion >=
- android.os.Build.VERSION_CODES.HONEYCOMB;
+ private void measureFloating(View preview, Rect margins, Rect out) {
+ final int marginLeft;
+ final int marginTop;
+ final int marginRight;
+ if (margins == null) {
+ marginLeft = 0;
+ marginTop = 0;
+ marginRight = 0;
+ } else {
+ marginLeft = margins.left;
+ marginTop = margins.top;
+ marginRight = margins.right;
+ }
- setScrollbarPosition(mList.getVerticalScrollbarPosition());
+ final View list = mList;
+ final int listWidth = list.getWidth();
+ final int adjMaxWidth = listWidth - marginLeft - marginRight;
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ preview.measure(widthMeasureSpec, heightMeasureSpec);
+
+ // Align at the vertical center, 10% from the top.
+ final int width = preview.getMeasuredWidth();
+ final int top = list.getHeight() / 10 + marginTop;
+ final int bottom = top + preview.getMeasuredHeight();
+ final int left = (listWidth - width) / 2;
+ final int right = left + width;
+ out.set(left, top, right, bottom);
}
- void stop() {
- setState(STATE_NONE);
+ /**
+ * Lays out the thumb according to the current scrollbar position.
+ */
+ private void layoutThumb() {
+ final Rect bounds = mTempBounds;
+ measureViewToSide(mThumbImage, null, null, bounds);
+ applyLayout(mThumbImage, bounds);
}
- boolean isVisible() {
- return !(mState == STATE_NONE);
+ /**
+ * Lays out the track centered on the thumb, if available, or against the
+ * edge if no thumb is available. Must be called after {@link #layoutThumb}.
+ */
+ private void layoutTrack() {
+ final View track = mTrackImage;
+ final View thumb = mThumbImage;
+ final View list = mList;
+ final int listWidth = list.getWidth();
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(listWidth, MeasureSpec.AT_MOST);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ track.measure(widthMeasureSpec, heightMeasureSpec);
+
+ final int trackWidth = track.getMeasuredWidth();
+ final int thumbHalfHeight = thumb == null ? 0 : thumb.getHeight() / 2;
+ final int left = thumb == null ? listWidth - trackWidth :
+ thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
+ final int right = left + trackWidth;
+ final int top = thumbHalfHeight;
+ final int bottom = list.getHeight() - thumbHalfHeight;
+ track.layout(left, top, right, bottom);
}
- public void draw(Canvas canvas) {
+ private void setState(int state) {
+ mList.removeCallbacks(mDeferHide);
- if (mState == STATE_NONE) {
- // No need to draw anything
- return;
+ if (mAlwaysShow && state == STATE_NONE) {
+ state = STATE_VISIBLE;
}
- final int y = mThumbY;
- final int viewWidth = mList.getWidth();
- final FastScroller.ScrollFade scrollFade = mScrollFade;
+ if (state == mState) {
+ return;
+ }
- int alpha = -1;
- if (mState == STATE_EXIT) {
- alpha = scrollFade.getAlpha();
- if (alpha < ScrollFade.ALPHA_MAX / 2) {
- mThumbDrawable.setAlpha(alpha * 2);
- }
- int left = 0;
- switch (mPosition) {
- case View.SCROLLBAR_POSITION_RIGHT:
- left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
- break;
- case View.SCROLLBAR_POSITION_LEFT:
- left = -mThumbW + (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
- break;
- }
- mThumbDrawable.setBounds(left, 0, left + mThumbW, mThumbH);
- mChangedBounds = true;
- }
-
- if (mTrackDrawable != null) {
- final Rect thumbBounds = mThumbDrawable.getBounds();
- final int left = thumbBounds.left;
- final int halfThumbHeight = (thumbBounds.bottom - thumbBounds.top) / 2;
- final int trackWidth = mTrackDrawable.getIntrinsicWidth();
- final int trackLeft = (left + mThumbW / 2) - trackWidth / 2;
- mTrackDrawable.setBounds(trackLeft, halfThumbHeight,
- trackLeft + trackWidth, mList.getHeight() - halfThumbHeight);
- mTrackDrawable.draw(canvas);
- }
-
- canvas.translate(0, y);
- mThumbDrawable.draw(canvas);
- canvas.translate(0, -y);
-
- // If user is dragging the scroll bar, draw the alphabet overlay
- if (mState == STATE_DRAGGING && mDrawOverlay) {
- final Drawable overlay = mOverlayDrawable;
- final Paint paint = mPaint;
- final String sectionText = mSectionText;
- final Rect tmpRect = mTmpRect;
-
- // TODO: Use a text view in an overlay for transition animations and
- // handling of text overflow.
- paint.getTextBounds(sectionText, 0, sectionText.length(), tmpRect);
- final int textWidth = tmpRect.width();
- final int textHeight = tmpRect.height();
-
- overlay.getPadding(tmpRect);
- final int overlayWidth = Math.max(
- mOverlaySize, textWidth + tmpRect.left + tmpRect.right + mOverlayPadding * 2);
- final int overlayHeight = Math.max(
- mOverlaySize, textHeight + tmpRect.top + tmpRect.bottom + mOverlayPadding * 2);
- final RectF pos = mOverlayPos;
-
- if (mOverlayPosition == OVERLAY_AT_THUMB) {
- final Rect thumbBounds = mThumbDrawable.getBounds();
-
- switch (mPosition) {
- case View.SCROLLBAR_POSITION_LEFT:
- pos.left = Math.min(
- thumbBounds.right + mThumbW, mList.getWidth() - overlayWidth);
- break;
- case View.SCROLLBAR_POSITION_RIGHT:
- default:
- pos.left = Math.max(0, thumbBounds.left - mThumbW - overlayWidth);
- break;
- }
+ switch (state) {
+ case STATE_NONE:
+ transitionToHidden();
+ break;
+ case STATE_VISIBLE:
+ transitionToVisible();
+ break;
+ case STATE_DRAGGING:
+ transitionToDragging();
+ break;
+ }
- pos.top = Math.max(0, Math.min(
- y + (mThumbH - overlayHeight) / 2, mList.getHeight() - overlayHeight));
- }
+ mState = state;
- pos.right = pos.left + overlayWidth;
- pos.bottom = pos.top + overlayHeight;
+ refreshDrawablePressedState();
+ }
- overlay.setBounds((int) pos.left, (int) pos.top, (int) pos.right, (int) pos.bottom);
- overlay.draw(canvas);
+ private void refreshDrawablePressedState() {
+ final boolean isPressed = mState == STATE_DRAGGING;
+ mThumbImage.setPressed(isPressed);
+ mTrackImage.setPressed(isPressed);
+ }
- final float hOff = (tmpRect.right - tmpRect.left) / 2.0f;
- final float vOff = (tmpRect.bottom - tmpRect.top) / 2.0f;
- final float cX = pos.centerX() - hOff;
- final float cY = pos.centerY() + (overlayHeight / 4.0f) - paint.descent() - vOff;
- canvas.drawText(mSectionText, cX, cY, paint);
- } else if (mState == STATE_EXIT) {
- if (alpha == 0) { // Done with exit
- setState(STATE_NONE);
- } else {
- final int left, right, top, bottom;
- if (mTrackDrawable != null) {
- top = 0;
- bottom = mList.getHeight();
- } else {
- top = y;
- bottom = y + mThumbH;
- }
- switch (mList.getLayoutDirection()) {
- case View.LAYOUT_DIRECTION_RTL:
- left = 0;
- right = mThumbW;
- break;
- case View.LAYOUT_DIRECTION_LTR:
- default:
- left = viewWidth - mThumbW;
- right = viewWidth;
- }
- mList.invalidate(left, top, right, bottom);
- }
+ /**
+ * Shows nothing.
+ */
+ private void transitionToHidden() {
+ if (mDecorAnimation != null) {
+ mDecorAnimation.cancel();
}
+
+ final Animator fadeOut = groupAnimatorOfFloat(View.ALPHA, 0f, mThumbImage, mTrackImage,
+ mPreviewImage, mPrimaryText, mSecondaryText).setDuration(DURATION_FADE_OUT);
+
+ // Push the thumb and track outside the list bounds.
+ final float offset = mLayoutFromRight ? mThumbImage.getWidth() : -mThumbImage.getWidth();
+ final Animator slideOut = groupAnimatorOfFloat(
+ View.TRANSLATION_X, offset, mThumbImage, mTrackImage)
+ .setDuration(DURATION_FADE_OUT);
+
+ mDecorAnimation = new AnimatorSet();
+ mDecorAnimation.playTogether(fadeOut, slideOut);
+ mDecorAnimation.start();
}
- void onSizeChanged(int w, int h, int oldw, int oldh) {
- if (mThumbDrawable != null) {
- switch (mPosition) {
- default:
- case View.SCROLLBAR_POSITION_RIGHT:
- mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH);
- break;
- case View.SCROLLBAR_POSITION_LEFT:
- mThumbDrawable.setBounds(0, 0, mThumbW, mThumbH);
- break;
- }
- }
- if (mOverlayPosition == OVERLAY_FLOATING) {
- final RectF pos = mOverlayPos;
- pos.left = (w - mOverlaySize) / 2;
- pos.right = pos.left + mOverlaySize;
- pos.top = h / 10; // 10% from top
- pos.bottom = pos.top + mOverlaySize;
- if (mOverlayDrawable != null) {
- mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
- (int) pos.right, (int) pos.bottom);
- }
+ /**
+ * Shows the thumb and track.
+ */
+ private void transitionToVisible() {
+ if (mDecorAnimation != null) {
+ mDecorAnimation.cancel();
}
+
+ final Animator fadeIn = groupAnimatorOfFloat(View.ALPHA, 1f, mThumbImage, mTrackImage)
+ .setDuration(DURATION_FADE_IN);
+ final Animator fadeOut = groupAnimatorOfFloat(
+ View.ALPHA, 0f, mPreviewImage, mPrimaryText, mSecondaryText)
+ .setDuration(DURATION_FADE_OUT);
+ final Animator slideIn = groupAnimatorOfFloat(
+ View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
+
+ mDecorAnimation = new AnimatorSet();
+ mDecorAnimation.playTogether(fadeIn, fadeOut, slideIn);
+ mDecorAnimation.start();
}
- void onItemCountChanged(int oldCount, int newCount) {
- if (mAlwaysShow) {
- mLongList = true;
+ /**
+ * Shows the thumb, preview, and track.
+ */
+ private void transitionToDragging() {
+ if (mDecorAnimation != null) {
+ mDecorAnimation.cancel();
}
+
+ final Animator fadeIn = groupAnimatorOfFloat(
+ View.ALPHA, 1f, mThumbImage, mTrackImage, mPreviewImage)
+ .setDuration(DURATION_FADE_IN);
+ final Animator slideIn = groupAnimatorOfFloat(
+ View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
+
+ mDecorAnimation = new AnimatorSet();
+ mDecorAnimation.playTogether(fadeIn, slideIn);
+ mDecorAnimation.start();
+
+ // Ensure the preview text is correct.
+ final String previewText = getPreviewText();
+ transitionPreviewLayout(previewText);
}
- void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- // Are there enough pages to require fast scroll? Recompute only if total count changes
+ private boolean isLongList(int visibleItemCount, int totalItemCount) {
+ // Are there enough pages to require fast scroll? Recompute only if
+ // total count changes.
if (mItemCount != totalItemCount && visibleItemCount > 0) {
mItemCount = totalItemCount;
mLongList = mItemCount / visibleItemCount >= MIN_PAGES;
}
- if (mAlwaysShow) {
- mLongList = true;
- }
- if (!mLongList) {
- if (mState != STATE_NONE) {
- setState(STATE_NONE);
- }
+
+ return mLongList;
+ }
+
+ public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ if (!mAlwaysShow && !isLongList(visibleItemCount, totalItemCount)) {
+ setState(STATE_NONE);
return;
}
- if (totalItemCount - visibleItemCount > 0 && mState != STATE_DRAGGING) {
- mThumbY = getThumbPositionForListPosition(firstVisibleItem, visibleItemCount,
- totalItemCount);
- if (mChangedBounds) {
- resetThumbPos();
- mChangedBounds = false;
- }
+
+ final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
+ if (hasMoreItems && mState != STATE_DRAGGING) {
+ setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
}
+
mScrollCompleted = true;
- if (firstVisibleItem == mVisibleItem) {
- return;
- }
- mVisibleItem = firstVisibleItem;
- if (mState != STATE_DRAGGING) {
- setState(STATE_VISIBLE);
- if (!mAlwaysShow) {
- mHandler.postDelayed(mScrollFade, FADE_TIMEOUT);
- }
- }
- }
- SectionIndexer getSectionIndexer() {
- return mSectionIndexer;
- }
+ if (mFirstVisibleItem != firstVisibleItem) {
+ mFirstVisibleItem = firstVisibleItem;
- Object[] getSections() {
- if (mListAdapter == null && mList != null) {
- getSectionsFromIndexer();
+ // Show the thumb, if necessary, and set up auto-fade.
+ if (mState != STATE_DRAGGING) {
+ setState(STATE_VISIBLE);
+ mList.postDelayed(mDeferHide, FADE_TIMEOUT);
+ }
}
- return mSections;
}
- void getSectionsFromIndexer() {
- Adapter adapter = mList.getAdapter();
+ private void getSectionsFromIndexer() {
mSectionIndexer = null;
+
+ Adapter adapter = mList.getAdapter();
if (adapter instanceof HeaderViewListAdapter) {
- mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
- adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
+ mHeaderCount = ((HeaderViewListAdapter) adapter).getHeadersCount();
+ adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter();
}
+
if (adapter instanceof ExpandableListConnector) {
- ExpandableListAdapter expAdapter = ((ExpandableListConnector)adapter).getAdapter();
+ final ExpandableListAdapter expAdapter = ((ExpandableListConnector) adapter)
+ .getAdapter();
if (expAdapter instanceof SectionIndexer) {
mSectionIndexer = (SectionIndexer) expAdapter;
mListAdapter = (BaseAdapter) adapter;
mSections = mSectionIndexer.getSections();
}
+ } else if (adapter instanceof SectionIndexer) {
+ mListAdapter = (BaseAdapter) adapter;
+ mSectionIndexer = (SectionIndexer) adapter;
+ mSections = mSectionIndexer.getSections();
} else {
- if (adapter instanceof SectionIndexer) {
- mListAdapter = (BaseAdapter) adapter;
- mSectionIndexer = (SectionIndexer) adapter;
- mSections = mSectionIndexer.getSections();
- if (mSections == null) {
- mSections = new String[] { " " };
- }
- } else {
- mListAdapter = (BaseAdapter) adapter;
- mSections = new String[] { " " };
- }
+ mListAdapter = (BaseAdapter) adapter;
+ mSections = null;
}
}
@@ -564,21 +745,24 @@ class FastScroller {
mListAdapter = null;
}
- void scrollTo(float position) {
- int count = mList.getCount();
+ /**
+ * Scrolls to a specific position within the section
+ * @param position
+ */
+ private void scrollTo(float position) {
mScrollCompleted = false;
- float fThreshold = (1.0f / count) / 8;
+
+ final int count = mList.getCount();
final Object[] sections = mSections;
+ final int sectionCount = sections == null ? 0 : sections.length;
int sectionIndex;
- if (sections != null && sections.length > 1) {
- final int nSections = sections.length;
- int section = (int) (position * nSections);
- if (section >= nSections) {
- section = nSections - 1;
- }
- int exactSection = section;
- sectionIndex = section;
- int index = mSectionIndexer.getPositionForSection(section);
+ if (sections != null && sectionCount > 1) {
+ final int exactSection = MathUtils.constrain(
+ (int) (position * sectionCount), 0, sectionCount - 1);
+ int targetSection = exactSection;
+ int targetIndex = mSectionIndexer.getPositionForSection(targetSection);
+ sectionIndex = targetSection;
+
// Given the expected section and index, the following code will
// try to account for missing sections (no names starting with..)
// It will compute the scroll space of surrounding empty sections
@@ -586,25 +770,26 @@ class FastScroller {
// available space, so that there is always some list movement while
// the user moves the thumb.
int nextIndex = count;
- int prevIndex = index;
- int prevSection = section;
- int nextSection = section + 1;
+ int prevIndex = targetIndex;
+ int prevSection = targetSection;
+ int nextSection = targetSection + 1;
+
// Assume the next section is unique
- if (section < nSections - 1) {
- nextIndex = mSectionIndexer.getPositionForSection(section + 1);
+ if (targetSection < sectionCount - 1) {
+ nextIndex = mSectionIndexer.getPositionForSection(targetSection + 1);
}
// Find the previous index if we're slicing the previous section
- if (nextIndex == index) {
+ if (nextIndex == targetIndex) {
// Non-existent letter
- while (section > 0) {
- section--;
- prevIndex = mSectionIndexer.getPositionForSection(section);
- if (prevIndex != index) {
- prevSection = section;
- sectionIndex = section;
+ while (targetSection > 0) {
+ targetSection--;
+ prevIndex = mSectionIndexer.getPositionForSection(targetSection);
+ if (prevIndex != targetIndex) {
+ prevSection = targetSection;
+ sectionIndex = targetSection;
break;
- } else if (section == 0) {
+ } else if (targetSection == 0) {
// When section reaches 0 here, sectionIndex must follow it.
// Assuming mSectionIndexer.getPositionForSection(0) == 0.
sectionIndex = 0;
@@ -612,131 +797,281 @@ class FastScroller {
}
}
}
+
// Find the next index, in case the assumed next index is not
// unique. For instance, if there is no P, then request for P's
// position actually returns Q's. So we need to look ahead to make
// sure that there is really a Q at Q's position. If not, move
// further down...
int nextNextSection = nextSection + 1;
- while (nextNextSection < nSections &&
+ while (nextNextSection < sectionCount &&
mSectionIndexer.getPositionForSection(nextNextSection) == nextIndex) {
nextNextSection++;
nextSection++;
}
+
// Compute the beginning and ending scroll range percentage of the
- // currently visible letter. This could be equal to or greater than
- // (1 / nSections).
- float fPrev = (float) prevSection / nSections;
- float fNext = (float) nextSection / nSections;
- if (prevSection == exactSection && position - fPrev < fThreshold) {
- index = prevIndex;
+ // currently visible section. This could be equal to or greater than
+ // (1 / nSections). If the target position is near the previous
+ // position, snap to the previous position.
+ final float prevPosition = (float) prevSection / sectionCount;
+ final float nextPosition = (float) nextSection / sectionCount;
+ final float snapThreshold = (count == 0) ? Float.MAX_VALUE : .125f / count;
+ if (prevSection == exactSection && position - prevPosition < snapThreshold) {
+ targetIndex = prevIndex;
} else {
- index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev)
- / (fNext - fPrev));
+ targetIndex = prevIndex + (int) ((nextIndex - prevIndex) * (position - prevPosition)
+ / (nextPosition - prevPosition));
}
- // Don't overflow
- if (index > count - 1) index = count - 1;
+
+ // Clamp to valid positions.
+ targetIndex = MathUtils.constrain(targetIndex, 0, count - 1);
if (mList instanceof ExpandableListView) {
- ExpandableListView expList = (ExpandableListView) mList;
+ final ExpandableListView expList = (ExpandableListView) mList;
expList.setSelectionFromTop(expList.getFlatListPosition(
- ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0);
+ ExpandableListView.getPackedPositionForGroup(targetIndex + mHeaderCount)),
+ 0);
} else if (mList instanceof ListView) {
- ((ListView)mList).setSelectionFromTop(index + mListOffset, 0);
+ ((ListView) mList).setSelectionFromTop(targetIndex + mHeaderCount, 0);
} else {
- mList.setSelection(index + mListOffset);
+ mList.setSelection(targetIndex + mHeaderCount);
}
} else {
- int index = (int) (position * count);
- // Don't overflow
- if (index > count - 1) index = count - 1;
+ final int index = MathUtils.constrain((int) (position * count), 0, count - 1);
if (mList instanceof ExpandableListView) {
ExpandableListView expList = (ExpandableListView) mList;
expList.setSelectionFromTop(expList.getFlatListPosition(
- ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0);
+ ExpandableListView.getPackedPositionForGroup(index + mHeaderCount)), 0);
} else if (mList instanceof ListView) {
- ((ListView)mList).setSelectionFromTop(index + mListOffset, 0);
+ ((ListView)mList).setSelectionFromTop(index + mHeaderCount, 0);
} else {
- mList.setSelection(index + mListOffset);
+ mList.setSelection(index + mHeaderCount);
}
+
sectionIndex = -1;
}
- if (sectionIndex >= 0) {
- String text = mSectionText = sections[sectionIndex].toString();
- mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
- sectionIndex < sections.length;
+ if (sectionIndex >= 0 && sectionIndex < sections.length) {
+ // If we moved sections, display section.
+ if (mCurrentSection != sectionIndex) {
+ mCurrentSection = sectionIndex;
+ final String section = sections[sectionIndex].toString();
+ transitionToDragging();
+ transitionPreviewLayout(section);
+ }
} else {
- mDrawOverlay = false;
+ // No current section, transition out of preview.
+ transitionPreviewLayout(null);
+ transitionToVisible();
}
}
- private int getThumbPositionForListPosition(int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
+ private String getPreviewText() {
+ final Object[] sections = mSections;
+ if (sections == null) {
+ return null;
+ }
+
+ final int sectionIndex = mCurrentSection;
+ if (sectionIndex < 0 || sectionIndex >= sections.length) {
+ return null;
+ }
+
+ return sections[sectionIndex].toString();
+ }
+
+ /**
+ * Transitions the preview text to a new value. Handles animation,
+ * measurement, and layout.
+ *
+ * @param text The preview text to transition to.
+ */
+ private void transitionPreviewLayout(CharSequence text) {
+ final Rect bounds = mTempBounds;
+ final ImageView preview = mPreviewImage;
+ final TextView showing;
+ final TextView target;
+ if (mShowingPrimary) {
+ showing = mPrimaryText;
+ target = mSecondaryText;
+ } else {
+ showing = mSecondaryText;
+ target = mPrimaryText;
+ }
+
+ // Set and layout target immediately.
+ target.setText(text);
+ measurePreview(target, bounds);
+ applyLayout(target, bounds);
+
+ if (mPreviewAnimation != null) {
+ mPreviewAnimation.cancel();
+ }
+
+ // Cross-fade preview text.
+ final Animator showTarget = animateAlpha(target, 1f).setDuration(DURATION_CROSS_FADE);
+ final Animator hideShowing = animateAlpha(showing, 0f).setDuration(DURATION_CROSS_FADE);
+ hideShowing.addListener(mSwitchPrimaryListener);
+
+ // Apply preview image padding and animate bounds, if necessary.
+ bounds.left -= mPreviewImage.getPaddingLeft();
+ bounds.top -= mPreviewImage.getPaddingTop();
+ bounds.right += mPreviewImage.getPaddingRight();
+ bounds.bottom += mPreviewImage.getPaddingBottom();
+ final Animator resizePreview = animateBounds(preview, bounds);
+ resizePreview.setDuration(DURATION_RESIZE);
+
+ mPreviewAnimation = new AnimatorSet();
+ final AnimatorSet.Builder builder = mPreviewAnimation.play(hideShowing).with(showTarget);
+ builder.with(resizePreview);
+
+ // The current preview size is unaffected by hidden or showing. It's
+ // used to set starting scales for things that need to be scaled down.
+ final int previewWidth = preview.getWidth() - preview.getPaddingLeft()
+ - preview.getPaddingRight();
+
+ // If target is too large, shrink it immediately to fit and expand to
+ // target size. Otherwise, start at target size.
+ final int targetWidth = target.getWidth();
+ if (targetWidth > previewWidth) {
+ target.setScaleX((float) previewWidth / targetWidth);
+ final Animator scaleAnim = animateScaleX(target, 1f).setDuration(DURATION_RESIZE);
+ builder.with(scaleAnim);
+ } else {
+ target.setScaleX(1f);
+ }
+
+ // If showing is larger than target, shrink to target size.
+ final int showingWidth = showing.getWidth();
+ if (showingWidth > targetWidth) {
+ final float scale = (float) targetWidth / showingWidth;
+ final Animator scaleAnim = animateScaleX(showing, scale).setDuration(DURATION_RESIZE);
+ builder.with(scaleAnim);
+ }
+
+ mPreviewAnimation.start();
+ }
+
+ /**
+ * Positions the thumb and preview widgets.
+ *
+ * @param position The position, between 0 and 1, along the track at which
+ * to place the thumb.
+ */
+ private void setThumbPos(float position) {
+ final int top = 0;
+ final int bottom = mList.getHeight();
+
+ final float thumbHalfHeight = mThumbImage.getHeight() / 2f;
+ final float min = top + thumbHalfHeight;
+ final float max = bottom - thumbHalfHeight;
+ final float offset = min;
+ final float range = max - min;
+ final float thumbMiddle = position * range + offset;
+ mThumbImage.setTranslationY(thumbMiddle - thumbHalfHeight);
+
+ // Center the preview on the thumb, constrained to the list bounds.
+ final float previewHalfHeight = mPreviewImage.getHeight() / 2f;
+ final float minP = top + previewHalfHeight;
+ final float maxP = bottom - previewHalfHeight;
+ final float previewMiddle = MathUtils.constrain(thumbMiddle, minP, maxP);
+ final float previewTop = previewMiddle - previewHalfHeight;
+
+ mPreviewImage.setTranslationY(previewTop);
+ mPrimaryText.setTranslationY(previewTop);
+ mSecondaryText.setTranslationY(previewTop);
+ }
+
+ private float getPosFromMotionEvent(float y) {
+ final int top = 0;
+ final int bottom = mList.getHeight();
+
+ final float thumbHalfHeight = mThumbImage.getHeight() / 2f;
+ final float min = top + thumbHalfHeight;
+ final float max = bottom - thumbHalfHeight;
+ final float offset = min;
+ final float range = max - min;
+
+ // If the list is the same height as the thumbnail or shorter,
+ // effectively disable scrolling.
+ if (range <= 0) {
+ return 0f;
+ }
+
+ return MathUtils.constrain((y - offset) / range, 0f, 1f);
+ }
+
+ private float getPosFromItemCount(
+ int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mSectionIndexer == null || mListAdapter == null) {
getSectionsFromIndexer();
}
- if (mSectionIndexer == null || !mMatchDragPosition) {
- return ((mList.getHeight() - mThumbH) * firstVisibleItem)
- / (totalItemCount - visibleItemCount);
+
+ final boolean hasSections = mSectionIndexer != null && mSections != null
+ && mSections.length > 0;
+ if (!hasSections || !mMatchDragPosition) {
+ return firstVisibleItem / (totalItemCount - visibleItemCount);
}
- firstVisibleItem -= mListOffset;
+ firstVisibleItem -= mHeaderCount;
if (firstVisibleItem < 0) {
return 0;
}
- totalItemCount -= mListOffset;
- final int trackHeight = mList.getHeight() - mThumbH;
+ totalItemCount -= mHeaderCount;
final int section = mSectionIndexer.getSectionForPosition(firstVisibleItem);
final int sectionPos = mSectionIndexer.getPositionForSection(section);
final int nextSectionPos = mSectionIndexer.getPositionForSection(section + 1);
final int sectionCount = mSections.length;
- final int positionsInSection = nextSectionPos - sectionPos;
+ final int positionsInSection = Math.max(1, nextSectionPos - sectionPos);
final View child = mList.getChildAt(0);
final float incrementalPos = child == null ? 0 : firstVisibleItem +
- (float) (mList.getPaddingTop() - child.getTop()) / child.getHeight();
+ (float) (mList.getPaddingTop() - child.getTop()) / Math.max(1, child.getHeight());
final float posWithinSection = (incrementalPos - sectionPos) / positionsInSection;
- int result = (int) ((section + posWithinSection) / sectionCount * trackHeight);
-
- // Fake out the scrollbar for the last item. Since the section indexer won't
- // ever actually move the list in this end space, make scrolling across the last item
- // account for whatever space is remaining.
- if (firstVisibleItem > 0 && firstVisibleItem + visibleItemCount == totalItemCount) {
- final View lastChild = mList.getChildAt(visibleItemCount - 1);
- final float lastItemVisible = (float) (mList.getHeight() - mList.getPaddingBottom()
- - lastChild.getTop()) / lastChild.getHeight();
- result += (trackHeight - result) * lastItemVisible;
- }
-
- return result;
+ return (section + posWithinSection) / sectionCount;
}
+ /**
+ * Cancels an ongoing fling event by injecting a
+ * {@link MotionEvent#ACTION_CANCEL} into the host view.
+ */
private void cancelFling() {
- // Cancel the list fling
- MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ final MotionEvent cancelFling = MotionEvent.obtain(
+ 0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mList.onTouchEvent(cancelFling);
cancelFling.recycle();
}
- void cancelPendingDrag() {
+ /**
+ * Cancels a pending drag.
+ *
+ * @see #startPendingDrag()
+ */
+ private void cancelPendingDrag() {
mList.removeCallbacks(mDeferStartDrag);
- mPendingDrag = false;
+ mHasPendingDrag = false;
}
- void startPendingDrag() {
- mPendingDrag = true;
- mList.postDelayed(mDeferStartDrag, PENDING_DRAG_DELAY);
+ /**
+ * Delays dragging until after the framework has determined that the user is
+ * scrolling, rather than tapping.
+ */
+ private void startPendingDrag() {
+ mHasPendingDrag = true;
+ mList.postDelayed(mDeferStartDrag, TAP_TIMEOUT);
}
- void beginDrag() {
+ private void beginDrag() {
setState(STATE_DRAGGING);
+
if (mListAdapter == null && mList != null) {
getSectionsFromIndexer();
}
+
if (mList != null) {
mList.requestDisallowInterceptTouchEvent(true);
mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
@@ -745,16 +1080,23 @@ class FastScroller {
cancelFling();
}
- boolean onInterceptTouchEvent(MotionEvent ev) {
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- if (mState > STATE_NONE && isPointInside(ev.getX(), ev.getY())) {
- if (!mList.isInScrollingContainer()) {
- beginDrag();
- return true;
+ if (isPointInside(ev.getX(), ev.getY())) {
+ // If the parent has requested that its children delay
+ // pressed state (e.g. is a scrolling container) then we
+ // need to allow the parent time to decide whether it wants
+ // to intercept events. If it does, we will receive a CANCEL
+ // event.
+ if (mList.isInScrollingContainer()) {
+ mInitialTouchY = ev.getY();
+ startPendingDrag();
+ return false;
}
- mInitialTouchY = ev.getY();
- startPendingDrag();
+
+ beginDrag();
+ return true;
}
break;
case MotionEvent.ACTION_UP:
@@ -762,70 +1104,56 @@ class FastScroller {
cancelPendingDrag();
break;
}
+
return false;
}
- boolean onTouchEvent(MotionEvent me) {
- if (mState == STATE_NONE) {
- return false;
- }
-
- final int action = me.getAction();
-
- if (action == MotionEvent.ACTION_DOWN) {
- if (isPointInside(me.getX(), me.getY())) {
- if (!mList.isInScrollingContainer()) {
+ public boolean onTouchEvent(MotionEvent me) {
+ switch (me.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ if (isPointInside(me.getX(), me.getY())) {
beginDrag();
return true;
}
- mInitialTouchY = me.getY();
- startPendingDrag();
- }
- } else if (action == MotionEvent.ACTION_UP) { // don't add ACTION_CANCEL here
- if (mPendingDrag) {
- // Allow a tap to scroll.
- beginDrag();
+ } break;
- final int viewHeight = mList.getHeight();
- // Jitter
- int newThumbY = (int) me.getY() - mThumbH + 10;
- if (newThumbY < 0) {
- newThumbY = 0;
- } else if (newThumbY + mThumbH > viewHeight) {
- newThumbY = viewHeight - mThumbH;
- }
- mThumbY = newThumbY;
- scrollTo((float) mThumbY / (viewHeight - mThumbH));
+ case MotionEvent.ACTION_UP: {
+ if (mHasPendingDrag) {
+ // Allow a tap to scroll.
+ beginDrag();
- cancelPendingDrag();
- // Will hit the STATE_DRAGGING check below
- }
- if (mState == STATE_DRAGGING) {
- if (mList != null) {
- // ViewGroup does the right thing already, but there might
- // be other classes that don't properly reset on touch-up,
- // so do this explicitly just in case.
- mList.requestDisallowInterceptTouchEvent(false);
- mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ final float pos = getPosFromMotionEvent(me.getY());
+ setThumbPos(pos);
+ scrollTo(pos);
+
+ cancelPendingDrag();
+ // Will hit the STATE_DRAGGING check below
}
- setState(STATE_VISIBLE);
- final Handler handler = mHandler;
- handler.removeCallbacks(mScrollFade);
- if (!mAlwaysShow) {
- handler.postDelayed(mScrollFade, 1000);
+
+ if (mState == STATE_DRAGGING) {
+ if (mList != null) {
+ // ViewGroup does the right thing already, but there might
+ // be other classes that don't properly reset on touch-up,
+ // so do this explicitly just in case.
+ mList.requestDisallowInterceptTouchEvent(false);
+ mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ }
+
+ setState(STATE_VISIBLE);
+ mList.postDelayed(mDeferHide, FADE_TIMEOUT);
+
+ return true;
}
+ } break;
- mList.invalidate();
- return true;
- }
- } else if (action == MotionEvent.ACTION_MOVE) {
- if (mPendingDrag) {
- final float y = me.getY();
- if (Math.abs(y - mInitialTouchY) > mScaledTouchSlop) {
+ case MotionEvent.ACTION_MOVE: {
+ if (mHasPendingDrag && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) {
setState(STATE_DRAGGING);
+
if (mListAdapter == null && mList != null) {
getSectionsFromIndexer();
}
+
if (mList != null) {
mList.requestDisallowInterceptTouchEvent(true);
mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
@@ -835,87 +1163,168 @@ class FastScroller {
cancelPendingDrag();
// Will hit the STATE_DRAGGING check below
}
- }
- if (mState == STATE_DRAGGING) {
- final int viewHeight = mList.getHeight();
- // Jitter
- int newThumbY = (int) me.getY() - mThumbH + 10;
- if (newThumbY < 0) {
- newThumbY = 0;
- } else if (newThumbY + mThumbH > viewHeight) {
- newThumbY = viewHeight - mThumbH;
- }
- if (Math.abs(mThumbY - newThumbY) < 2) {
+
+ if (mState == STATE_DRAGGING) {
+ // TODO: Ignore jitter.
+ final float pos = getPosFromMotionEvent(me.getY());
+ setThumbPos(pos);
+
+ // If the previous scrollTo is still pending
+ if (mScrollCompleted) {
+ scrollTo(pos);
+ }
+
return true;
}
- mThumbY = newThumbY;
- // If the previous scrollTo is still pending
- if (mScrollCompleted) {
- scrollTo((float) mThumbY / (viewHeight - mThumbH));
- }
- return true;
- }
- } else if (action == MotionEvent.ACTION_CANCEL) {
- cancelPendingDrag();
+ } break;
+
+ case MotionEvent.ACTION_CANCEL: {
+ cancelPendingDrag();
+ } break;
}
+
return false;
}
- boolean isPointInside(float x, float y) {
- boolean inTrack = false;
- switch (mPosition) {
- default:
- case View.SCROLLBAR_POSITION_RIGHT:
- inTrack = x > mList.getWidth() - mThumbW;
- break;
- case View.SCROLLBAR_POSITION_LEFT:
- inTrack = x < mThumbW;
- break;
+ /**
+ * Returns whether a coordinate is inside the scroller's activation area. If
+ * there is a track image, touching anywhere within the thumb-width of the
+ * track activates scrolling. Otherwise, the user has to touch inside thumb
+ * itself.
+ *
+ * @param x The x-coordinate.
+ * @param y The y-coordinate.
+ * @return Whether the coordinate is inside the scroller's activation area.
+ */
+ private boolean isPointInside(float x, float y) {
+ return isPointInsideX(x) && (mHasTrackImage || isPointInsideY(y));
+ }
+
+ private boolean isPointInsideX(float x) {
+ if (mLayoutFromRight) {
+ return x >= mThumbImage.getLeft();
+ } else {
+ return x <= mThumbImage.getRight();
+ }
+ }
+
+ private boolean isPointInsideY(float y) {
+ return y >= mThumbImage.getTop() && y <= mThumbImage.getBottom();
+ }
+
+ /**
+ * Constructs an animator for the specified property on a group of views.
+ * See {@link ObjectAnimator#ofFloat(Object, String, float...)} for
+ * implementation details.
+ *
+ * @param property The property being animated.
+ * @param value The value to which that property should animate.
+ * @param views The target views to animate.
+ * @return An animator for all the specified views.
+ */
+ private static Animator groupAnimatorOfFloat(
+ Property<View, Float> property, float value, View... views) {
+ AnimatorSet animSet = new AnimatorSet();
+ AnimatorSet.Builder builder = null;
+
+ for (int i = views.length - 1; i >= 0; i--) {
+ final Animator anim = ObjectAnimator.ofFloat(views[i], property, value);
+ if (builder == null) {
+ builder = animSet.play(anim);
+ } else {
+ builder.with(anim);
+ }
}
- // Allow taps in the track to start moving.
- return inTrack && (mTrackDrawable != null || y >= mThumbY && y <= mThumbY + mThumbH);
+ return animSet;
}
- public class ScrollFade implements Runnable {
+ /**
+ * Returns an animator for the view's scaleX value.
+ */
+ private static Animator animateScaleX(View v, float target) {
+ return ObjectAnimator.ofFloat(v, View.SCALE_X, target);
+ }
- long mStartTime;
- long mFadeDuration;
- static final int ALPHA_MAX = 208;
- static final long FADE_DURATION = 200;
+ /**
+ * Returns an animator for the view's alpha value.
+ */
+ private static Animator animateAlpha(View v, float alpha) {
+ return ObjectAnimator.ofFloat(v, View.ALPHA, alpha);
+ }
- void startFade() {
- mFadeDuration = FADE_DURATION;
- mStartTime = SystemClock.uptimeMillis();
- setState(STATE_EXIT);
+ /**
+ * A Property wrapper around the <code>left</code> functionality handled by the
+ * {@link View#setLeft(int)} and {@link View#getLeft()} methods.
+ */
+ private static Property<View, Integer> LEFT = new IntProperty<View>("left") {
+ @Override
+ public void setValue(View object, int value) {
+ object.setLeft(value);
}
- int getAlpha() {
- if (getState() != STATE_EXIT) {
- return ALPHA_MAX;
- }
- int alpha;
- long now = SystemClock.uptimeMillis();
- if (now > mStartTime + mFadeDuration) {
- alpha = 0;
- } else {
- alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration);
- }
- return alpha;
+ @Override
+ public Integer get(View object) {
+ return object.getLeft();
}
+ };
+ /**
+ * A Property wrapper around the <code>top</code> functionality handled by the
+ * {@link View#setTop(int)} and {@link View#getTop()} methods.
+ */
+ private static Property<View, Integer> TOP = new IntProperty<View>("top") {
@Override
- public void run() {
- if (getState() != STATE_EXIT) {
- startFade();
- return;
- }
+ public void setValue(View object, int value) {
+ object.setTop(value);
+ }
- if (getAlpha() > 0) {
- mList.invalidate();
- } else {
- setState(STATE_NONE);
- }
+ @Override
+ public Integer get(View object) {
+ return object.getTop();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>right</code> functionality handled by the
+ * {@link View#setRight(int)} and {@link View#getRight()} methods.
+ */
+ private static Property<View, Integer> RIGHT = new IntProperty<View>("right") {
+ @Override
+ public void setValue(View object, int value) {
+ object.setRight(value);
+ }
+
+ @Override
+ public Integer get(View object) {
+ return object.getRight();
}
+ };
+
+ /**
+ * A Property wrapper around the <code>bottom</code> functionality handled by the
+ * {@link View#setBottom(int)} and {@link View#getBottom()} methods.
+ */
+ private static Property<View, Integer> BOTTOM = new IntProperty<View>("bottom") {
+ @Override
+ public void setValue(View object, int value) {
+ object.setBottom(value);
+ }
+
+ @Override
+ public Integer get(View object) {
+ return object.getBottom();
+ }
+ };
+
+ /**
+ * Returns an animator for the view's bounds.
+ */
+ private static Animator animateBounds(View v, Rect bounds) {
+ final PropertyValuesHolder left = PropertyValuesHolder.ofInt(LEFT, bounds.left);
+ final PropertyValuesHolder top = PropertyValuesHolder.ofInt(TOP, bounds.top);
+ final PropertyValuesHolder right = PropertyValuesHolder.ofInt(RIGHT, bounds.right);
+ final PropertyValuesHolder bottom = PropertyValuesHolder.ofInt(BOTTOM, bounds.bottom);
+ return ObjectAnimator.ofPropertyValuesHolder(v, left, top, right, bottom);
}
}
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ccca2d8..00caac9 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -52,6 +52,8 @@
<!-- Minimum size of the fastscroll overlay -->
<dimen name="fastscroll_overlay_size">104dp</dimen>
+ <!-- Text size of the fastscroll overlay -->
+ <dimen name="fastscroll_overlay_text_size">24sp</dimen>
<!-- Padding of the fastscroll overlay -->
<dimen name="fastscroll_overlay_padding">16dp</dimen>
<!-- Width of the fastscroll thumb -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cb8d144..7f39364 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -309,6 +309,7 @@
<java-symbol type="dimen" name="dropdownitem_icon_width" />
<java-symbol type="dimen" name="dropdownitem_text_padding_left" />
<java-symbol type="dimen" name="fastscroll_overlay_size" />
+ <java-symbol type="dimen" name="fastscroll_overlay_text_size" />
<java-symbol type="dimen" name="fastscroll_overlay_padding" />
<java-symbol type="dimen" name="fastscroll_thumb_height" />
<java-symbol type="dimen" name="fastscroll_thumb_width" />
diff --git a/docs/html/distribute/distribute_toc.cs b/docs/html/distribute/distribute_toc.cs
index 907d267..75cf9f9 100644
--- a/docs/html/distribute/distribute_toc.cs
+++ b/docs/html/distribute/distribute_toc.cs
@@ -88,6 +88,7 @@
<ul>
<li><a href="<?cs var:toroot ?>distribute/googleplay/edu/about.html">About</a></li>
<li><a href="<?cs var:toroot ?>distribute/googleplay/edu/start.html">Get Started</a></li>
+ <li><a href="<?cs var:toroot ?>distribute/googleplay/edu/guidelines.html">Guidelines</a></li>
<li><a href="<?cs var:toroot ?>distribute/googleplay/edu/contact.html">Sign Up</a></li>
</ul>
</li>
diff --git a/docs/html/distribute/googleplay/edu/contact.jd b/docs/html/distribute/googleplay/edu/contact.jd
index a302bc9..804d925 100644
--- a/docs/html/distribute/googleplay/edu/contact.jd
+++ b/docs/html/distribute/googleplay/edu/contact.jd
@@ -24,7 +24,7 @@ teachers and administrators buy, deploy, and use apps. </p>
Whether you have an existing educational app or are developing a fresh idea that
will unlock learning in the classroom &mdash; sign up to receive information about
the upcoming launch of Google Play for Education. To get your apps ready, read our
-<a href="{@docRoot}distribute/googleplay/edu/start.html">guidelines</a> for building
+<a href="{@docRoot}distribute/googleplay/edu/guidelines.html">guidelines</a> for building
educational apps.</p>
</p><a href="http://developer.android.com/edu/signup">Developer Sign Up »</a>
</div>
diff --git a/docs/html/distribute/googleplay/edu/guidelines.jd b/docs/html/distribute/googleplay/edu/guidelines.jd
new file mode 100644
index 0000000..c1d3065
--- /dev/null
+++ b/docs/html/distribute/googleplay/edu/guidelines.jd
@@ -0,0 +1,252 @@
+page.title=Guidelines for Apps
+page.metaDescription=Get your apps ready for Google Play for Education.
+excludeFromSuggestions=true
+@jd:body
+
+<div style="position:absolute;margin-left: 636px;
+ margin-top:-76px;color:#777;">If you're interested<br>
+ <a href="{@docRoot}distribute/googleplay/edu/contact.html"
+ class="go-link"
+ style="display: block;text-align: right;">SIGN UP</a></div>
+
+<div
+style="background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">You
+can now include your apps in the Google Play for Education <a
+href="{@docRoot}distribute/googleplay/edu/start.html#program">pilot program</a>,
+getting it into the hands of participating schools and key influencers in the
+education technology community. See <a href="start.html">Get Started</a> to
+learn how to participate. </div>
+
+<p>The sections below list the guidelines and requirements for apps
+participating in Google Play for Education.
+
+<p>Before you include your app in Google Play for Education, set up a <a
+href="#test-environment">test environment</a> and make sure your app meets all
+of the safety, usability, and quality guidelines given here. You can use the
+linked resources to help
+you develop a great app for students that offers compelling content and an
+intuitive user experience on Android tablets.</p>
+
+<p>In addition, ensure that your app complies with the terms of a <a
+href="https://play.google.com/about/developer-distribution-agreement-addendum.
+html" target="_policies">Google Play for Education Addendum</a>, as well as
+the standard <a
+href="http://play.google.com/about/developer-content-policy.html"
+target="_policies">Google Play Developer Program Policies</a> and <a
+href="http://play.google.com/about/developer-distribution-agreement.html"
+target="_policies">Developer Distribution Agreement</a>.</p>
+
+
+<h2 id="requirements">Safety First</h2>
+
+<p>To participate, your apps must be designed to be appropriate for
+the K-12 market. The basic requirements that your apps must meet are:</p>
+
+<ol>
+ <li>Apps and the ads they contain must not collect personally identifiable
+information other than user credentials or data required to operate and improve
+the app.</li>
+ <li>Apps must not use student data for purposes unrelated to its educational
+function.</li>
+ <li>Apps must have a content rating of "Everyone" or "Low Maturity" (apps with
+a "Medium Maturity" rating are allowed, if they have that rating solely because
+they allow communication between students).</li>
+ <li>App content, including ads displayed by the app, must be consistent with
+the app's maturity rating. The app must not display any “offensive” content, as
+described in the <a
+href="http://play.google.com/about/developer-content-policy.html">Google Play
+Developer Program Policies</a> and <a
+href="https://support.google.com/googleplay/android-developer/answer/188189">
+content-rating guidelines</a>.</p></li>
+<li>Apps must comply with the Children’s Online Privacy Protection Act
+and all other applicable laws and regulations.</li>
+</ol>
+
+
+<h2 id="inapp">Monetizing and Ads</h2>
+
+<p>Google Play for Education provides a simple and secure environment for students
+and teachers. To support that environment, priced or free apps that do not use in-app
+purchases are preferred, as are apps that do not display ads. Apps that use in-app
+payments or ads are acceptable, but you must declare those behaviors when opting-in
+to Google Play for Education. Your app's use of in-app purchases or ads will be
+disclosed to educators when they are browsing for content.</p>
+
+<p>Follow the guidelines below to help your app receive the
+ highest ratings and offer the best possible user-experience.</p>
+
+<p>If your app is priced or sells in-app products, you must:</p>
+
+<ul>
+ <li>Sell all content and services through Google Play for Education</li>
+ <li>Allow Google Play to offer teachers limited free trials before purchase
+(through business terms only, no development work is needed)</li>
+<li>Disable in-app purchases if possible, or ensure that:
+
+<ul>
+<li>Users can access your app's core functionality for a classroom setting without
+an in-app purchase.</li>
+<li>In-app purchases are clearly identifiable in your UI.</li>
+<li>You declare the use of in-app purchases at <a href="{@docRoot}distribute/googleplay/edu/start.html#opt-in">opt-in</a>.</li>
+</ul>
+</li>
+</ul>
+
+<p class="note"><strong>Note</strong>: In-app
+purchases are blocked on Google Play for Education tablets at this time.</p>
+
+<p>If your app displays ads, you should:
+ <ul>
+ <li>Disable the display of ads if possible, or ensure that:
+ <ul>
+ <li>Ads are not distracting for students or teachers</li>
+ <li>Ads do not occupy a significant portion of the screen</li>
+ <li>Ads content does not exceed the maturity rating of the app.</li>
+ <li>You declare the use of ads at <a href="{@docRoot}distribute/googleplay/edu/start.html#opt-in">opt-in</a>.</li>
+ </ul>
+ </li>
+</ul>
+
+
+<h2 id="approved">Educational Value</h2>
+
+<p>Apps submitted to Google Play for Education will be evaluated by a
+third-party educator network, which will review them based on alignment with <a
+href="http://www.corestandards.org/" class="external-link"
+target="_android">Common Core Standards</a> and other factors. This will help
+make your content more discoverable for teachers and administrators as they
+browse by grade level, subject, core curriculum, and other parameters. </p>
+
+<p>Apps with highest educational value will have these characteristics:</p>
+<ul>
+ <li>Designed for use in K-12 classrooms.</li>
+ <li>Aligned with a common core standard or support common-core learning.</li>
+ <li>Simple, easy to use, and intuitive for the grade levels the app is targeting.
+ App is relatively easy to navigate without teacher guidance. Not distracting
+ or overwhelming to students.</li>
+ <li>Enjoyable and interactive. App is engaging to students and lets them control
+ their experience.</li>
+ <li>Versatile. App has features make the it useful for more than one classroom
+ function or lesson throughout the school year.</li>
+ <li>Supports the "4Cs":
+ <ul>
+ <li><em>Creativity</em> &mdash; Allows students to create in order to express
+ understanding of the learning objectives, and try new approaches, innovation
+ and invention to get things done.</li>
+ <li><em>Critical thinking</em> &mdash; Allows students to look at problems in
+ a new way, linking learning across subjects and disciplines.</li>
+ <li><em>Collaboration</em> &mdash; Allows students and (if appropriate) educators
+ to work together to reach a goal.</li>
+ <li><em>Communication</em> &mdash; Allows students to comprehend, critique and
+ share thoughts, questions, ideas and solutions.</li>
+ </ul>
+ </li>
+</ul>
+
+<p>As you design and develop your app, make sure it offers high educational value
+by addressing as many of those characteristics as possible.</p>
+
+
+<h2 id="quality">App Quality</h2>
+
+<p>Google Play for Education brings educational content to students and teachers
+on Android tablets. Your apps should be designed to perform well and look great
+on Android tablets, and they should offer the best user experience possible.
+</p>
+
+<p>High quality apps are engaging, intuitive, and offer compelling content.
+Google Play for Education will highlight high-quality apps for easy discovery in
+the store. Here are some recommendations for making your app easy for students
+and teachers to enjoy.</p>
+
+<ul>
+ <li>Meet Core app quality guidelines
+ <ul>
+ <li>Follow <a
+ href="{@docRoot}design/index.html">Android Design Guidelines</a>. Pay special
+ attention to the sections on <a href="{@docRoot}design/patterns/actionbar.html">Action
+ Bar</a>, <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> and <a
+ href="{@docRoot}design/patterns/pure-android.html">Pure Android</a>.</li>
+ <li>Test your apps against the <a href="{@docRoot}distribute/googleplay/quality/core.html">Core
+ App Quality Guidelines</a>.</li>
+ </ul>
+ </li>
+<li>Meet tablet app quality guidelines
+ <ul>
+ <li>Follow our best practices for tablet app development</li>
+ <li>Review the <a href="{@docRoot}distribute/googleplay/quality/tablet.html">Tablet App
+ Quality Checklist</a> and <a
+ href="http://android-developers.blogspot.com/2012/11/designing-for-tablets-were-here-to-help.html"
+ target="_android">blog post on designing for tablets</a></li>
+ <li>Check your Optimization Tips in the Google Play Developer Console (if you've
+ already uploaded your app)</li>
+ </ul>
+<li>Strive for simplicity and highest usability for students
+ <ul>
+ <li>Design your app so that teachers and students can use all capabilities of
+ your app without having to sign-in to multiple accounts and remember
+ multiple passwords.</li>
+ <li>Every student or teacher using a Google Play for Education tablet will already be
+ signed in with a Google account on the device. You can take advantage of that to provide a
+ simple, seamless sign-in experience in your app. A recommended approach is to use
+ <a href="{@docRoot}google/play-services/auth.html">Google OAuth 2 authorization</a>
+ through Google Play Services.</li>
+ </ul>
+</li>
+</ul>
+
+
+<h2 id="test-environment">Test Environment</h2>
+
+<p>To test your app and assess it against the guidelines in this document, it's
+recommended that you set up a test environment that replicates the actual
+environment in which students and teachers will run your app.</p>
+
+<p>In general, you should use the test environment described in <a
+href="{@docRoot}distribute/googleplay/quality/tablet.html#test-environment">
+Setting Up a Test Environment for Tablets</a>, including a small number of
+actual hardware devices that replicate the tablet form factors used in the
+Google Play for Education.</p>
+
+<h3 id="devices">Android tablets</h3>
+
+<p>Google Play for Education uses primarily Nexus 7 devices, so
+your testing can focus on that specific hardware device. You can purchase the
+device from <a href="https://play.google.com/store/devices/details?id=nexus_7_16gb"
+target="_android">Google Play</a> and other stores. Although testing on Nexus
+devices is preferred, you can test on other 7-inch (or 10-inch) tablets or virtual
+devices if you don't have access to Nexus devices.</p>
+
+<h3 id="conditions">Test conditions</h3>
+
+<p>Once you've set up a suitable hardware environment, make sure to test your
+apps under conditions that simulate those of schools. For example, Google Play
+for Education lets administrators control or disable certain capabilities for
+students, so it's good to test your app with those capabilities disabled. Below
+are some conditions to test your app in, to ensure best results in the Google
+Play for Education environment:</p>
+
+<ul>
+<li><em>Android version</em> &mdash; Test the app on devices running Android
+4.2. Google Play for Education devices will be running Android 4.2 or higher
+(API level 17).</li>
+<li><em>Proxy server</em> &mdash; Test the app in network environment that uses
+proxies. Many schools use proxies.</li>
+<li><em>Secondary user account</em> &mdash; Test the app using a secondary user
+account. Most Google Play for Education users will not be using the primary <a
+href="{@docRoot}about/versions/jelly-bean.html#42-multiuser">multiuser</a>
+account on their devices. For testing, create a secondary multiuser account on
+your tablet.</li>
+<li><em>No location services</em> &mdash; Test the app to make sure it works
+properly with location services disabled. Many schools will disable location
+services for student devices.</li>
+<li><em>No In-app Billing</em> &mdash; Test the app to make sure it works
+properly without access to In-app Billing. In-app purchases are blocked on
+Google Play for Education devices at this time.</li>
+<li><em>No Bluetooth</em> &mdash; Test the app to make sure it works properly
+when Bluetooth is disabled. Many schools will disable Bluetooth on student
+devices.</li>
+<li><em>No access to network</em> &mdash; Test the app to make sure it works
+properly when the device cannot connect to the internet. </li>
+</ul>
+
diff --git a/docs/html/distribute/googleplay/edu/start.jd b/docs/html/distribute/googleplay/edu/start.jd
index e740cce..419d5ea 100644
--- a/docs/html/distribute/googleplay/edu/start.jd
+++ b/docs/html/distribute/googleplay/edu/start.jd
@@ -1,5 +1,5 @@
page.title=Get Started
-page.metaDescription=Get your apps ready for Google Play for Education.
+page.metaDescription=Get Started with Google Play for Education
excludeFromSuggestions=true
@jd:body
@@ -9,126 +9,256 @@ excludeFromSuggestions=true
class="go-link"
style="display: block;text-align: right;">SIGN UP</a></div>
+<div style="background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">You
+can now include your apps in the Google Play for Education <a href="#program">pilot program</a>,
+getting it into the hands of participating schools and key influencers in the education technology
+community. See the sections below to learn more.</div>
+
<p>If you've got a great app for education or just an idea for one, plan to be a
part of Google Play for Education to reach even more teachers and students. It's
-easy to participate, and you will be able to offer new or existing Android apps
-using the familiar tools in Google Play.</p>
+easy to participate, and you'll be able to offer new or existing Android apps
+using familiar tools and processes in Google Play.</p>
+
+<p>To get started, review the sections in this document and learn how to make
+your apps available through Google Play for Education. Also make sure to read <a
+href="{@docRoot}distribute/googleplay/edu/guidelines.html">Guidelines for
+Apps</a> for information on the safety, usability, and quality standards that
+your apps should meet. When your app is ready, you can opt-in to Google Play for
+Education from the Developer Console.</p>
+
+<p>Note that the initial launch of Google Play for Education is planned for Fall
+2013 and will include schools in the United States only, with support for other
+countries to follow. At this time, please include your app in Google Play for
+Education only if it is targeting the <strong>US K-12 market</strong>. </p>
+
+
+<h2 id="participate">How to Participate</h2>
+
+<div style="float:right; padding-top:2em;"><img
+src="{@docRoot}images/gp-edu-process.png"></div>
+
+<p>Google Play for Education lets you put your educational apps in front of a
+new audience of teachers and students. You can develop and publish using
+familiar tools and processes, such as your existing Developer Console account
+and your current distribution and pricing settings. It's easy to participate
+&mdash; the sections below outline the process.</p>
+
+<h3 id="basic-info">1. Understand guidelines and policies</h3>
+
+<p>To prepare for a successful launch on Google Play for Education, start by
+reviewing the guidelines for educational apps in Google Play and the policies
+that apply to your apps. See <a
+href="{@docRoot}distribute/googleplay/edu/guidelines.html">Guidelines for
+Apps</a> for details.</p>
+
+<p>Also, make sure that your are familiar with the policies that your app must
+comply with, including
+<a href="http://play.google.com/about/developer-content-policy.html" target="_policies">content
+policies</a>, the <a
+href="http://play.google.com/about/developer-distribution-agreement.html"
+target="_policies">developer agreement</a>, and <a
+href="https://play.google.com/about/developer-distribution-agreement-addendum.
+html" target="_policies">Google Play for Education Addendum</a>.</p>
+
+<h3 id="developing">2. Design and develop a great app for education</h3>
+
+<p>A great app for educators and students is designed for classroom use, looks
+great on tablets, and delivers a compelling feature set for teachers and
+students. If you are developing an app for education, make sure that it is
+appropriate for K-12 classrooms, offers educational value, and is refined to
+offer a polished, high-quality tablet experience.</p>
+
+<p>Assess your app against the criteria listed in <a
+href="{@docRoot}distribute/googleplay/edu/guidelines.html">Guidelines for
+Apps</a> and plan on supporting them to the greatest extent possible. In some
+cases you might need to modify your features or UI to support the requirements
+of the classroom use-case. It's a good idea to identify those areas early in
+development so that you are able address them properly. </p>
+
+<p>With Google Play for Education, optimizing your app for tablets is a crucial
+part of getting your app ready for distribution to educators. A variety of
+resources are available to help you understand what you need to optimize for
+tablets &mdash; a good starting point is the <a
+href="{@docRoot}distribute/googleplay/quality/tablet.html">Tablet App Quality
+Guidelines</a>. </p>
+
+<p>Throughout design and development, it's important to have a suitable device
+on which to prototype and test your user experience. It's highly recommended
+that you acquire one or more tablet devices and set up your testing environment
+as early as possible. The recommended hardware device that replicates the Google
+Play for Education environment is the Nexus 7, which is available from <a href="https://play.google.com/store/devices/details?id=nexus_7_16gb" target="_android">Google Play</a> and other stores.</p>
+
+<p>Proper testing and quality assurance are key aspects of delivering a great
+app for teachers and students. Make sure you set up a <a
+href="{@docRoot}distribute/googleplay/edu/guidelines.html#test-environment">
+proper test environment</a> to ensure that your app meets guidelines under
+realistic conditions.</p>
+
+<h3 id="opt-in">3. Opt-in to Google Play for Education and publish</h3>
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h2>Before you opt-in</h2>
+<p>To participate in Google Play for Education, you must agree to a <a
+href="https://play.google.com/about/developer-distribution-agreement-addendum.html"
+target="_policies">Google Play for Education Addendum</a>
+to the standard Developer Distribution Agreement.</p>
-<p>To get started, review the sections in this document and learn about the
-safety, usability, and quality guidelines that apps should meet. Assess your
-apps against these guidelines and make any adjustments needed. You can use the
-linked resources to help you develop a great app for students that offers
-compelling content and an intuitive user experience on Android tablets.</p>
+<p>Before you opt-in, review the Addendum completely and make any necessary
+modifications to your app.</p>
+</div>
+</div>
-<h2 id="requirements">Safety First</h2>
+<p>When you've built your release-ready APK and tested to ensure that it meets
+the <a href="{@docRoot}distribute/googleplay/edu/guidelines.html">app guidelines</a>,
+upload it to the Developer Console, create your store listing, and set
+distribution options. If you aren't familiar with how to prepare for launch on
+Google Play, see the <a
+href="{@docRoot}distribute/googleplay/publish/preparing.html">Launch Checklist</a>. </p>
-<p>To participate, your apps must be designed to be usable and appropriate for
-the K-12 market. The basic requirements that your apps must meet are:</p>
+<p>When your app is ready to publish, you can <em>opt-in</em> to Google Play for
+Education from the Developer Console. Opt-in means that you want your app to be
+made available to educators through Google Play for Education, including review,
+classification, and approval by our third-party educator network. Note that
+opt-in does not affect the availability of your app in Google Play Store.</p>
+
+<p>Opt-in also confirms that your app complies with <a
+href="http://play.google.com/about/developer-content-policy.html"
+target="_policies">Google Play Developer Program
+Policies</a> and the <a
+href="http://play.google.com/about/developer-distribution-agreement.html"
+target="_policies">Developer Distribution Agreement</a>,
+including a <a
+href="https://play.google.com/about/developer-distribution-agreement-addendum.
+html" target="_policies">Google Play for Education
+Addendum</a>. If you are not familiar with these policy documents or the
+Addendum, make sure to read them before opting-in. </p>
+
+<p>Here's how to opt-in to Google Play for Education for a specific app:</p>
<ol>
- <li>Apps and the ads they contain must not collect personally identifiable
-information other than user credentials or data required to operate and improve
-the app.</li>
- <li>Apps must not use student data for purposes unrelated to its educational
-function.</li>
- <li>Apps must have a content rating of "Everyone" or "Low Maturity" (apps with
-a "Medium Maturity" rating are allowed, if they have that rating solely because
-they allow communication between students).</li>
- <li>App content, including ads displayed by the app, must be consistent with
-the app's maturity rating. The app must not display any “offensive” content, as
-described in the <a
-href="http://play.google.com/about/developer-content-policy.html">Google Play
-Developer Program Policies</a> and <a
-href="https://support.google.com/googleplay/android-developer/answer/188189">
-content-rating guidelines</a>.</p></li>
+ <li>In the Developer Console All Applications page, click the app you want to
+opt-in. </li>
+ <li>Under Pricing and Distribution, scroll down to find "Google Play for
+Education" and the opt-in checkbox. </li>
+ <li>Click the checkbox next to "Include my app in Google Play for
+Education..."</li>
+ <li>After you've opted-in, find the "Ads" and "In-app purchases" checkboxes below.
+Check each checkbox that applies. Your app's use of ads or in-app purchases will
+be shown to educators when they are browsing your app. </li>
+ <li>Click "Save" to save your Pricing and Distribution changes.</li>
</ol>
+<div style="clear:both;margin-top:1.5em;margin-bottom:1.5em;width:660px;">
+<img src="{@docRoot}images/gp-edu-optin.png" style="border:2px solid #ddd;width:660px;">
+<p class="image-caption"><span style="font-weight:500;">Opt-in for apps</span>:
+Include your app in Google Play for Education by opting-in from the Developer Console.</p>
+</div>
-<h2 id="approved">With the Help of Educators</h2>
+<p>Once you save changes and publish your app, the app will be submitted to our
+third-party educator network for review and approval. If the app is already
+published, it will be submitted for review as soon as you opt-in and save your
+changes. </p>
-<p>App content submitted to Google Play for Education will be reviewed by
-educators who will categorize the apps and align them with <a
-href="http://www.corestandards.org/" class="external-link"
-target="_android">Common Core Standards</a>. This will help make your content
-discoverable in a way that is easy for teachers and administrators. </p>
+<p class="note"><strong>Note</strong>: Google Play for Education is part of
+Google Play. When you publish an app that's opted-in to Google Play for
+Education, the app becomes available to users in Google Play right away. After
+the app is reviewed and approved, it then becomes available to educators in
+Google Play for Education.</p>
+<h3 id="review">4. Track your review and approval</h3>
-<h2 id="secure">Sold Simply</h2>
+<p>Google Play for Education provides content to educators in a way that's
+properly organized by subject, grade level, and common core standards (where
+applicable). To ensure high educational value and proper classification, we work
+with a third-party educator network to review and approve apps before making
+them discoverable through the Google Play for Education browsing tools. </p>
-<p>Google Play for Education provides a simple and secure environment in which
-educators can buy apps in a way that's easy for schools &mdash; through purchase
-orders. Your apps must support this environment by ensuring that they:</p>
+<p>Our third-party educator network will evaluate apps according to educational
+value and alignment with K-12 core standards, then assign the metadata for
+subject, grade level, and core curriculum that makes them easily browseable for
+educators. To understand how your apps will be evaluated, please see the <a
+href="{@docRoot}distribute/googleplay/edu/guidelines.html">Guidelines for
+Apps</a> document.</p>
-<ul>
- <li>Sell all content and services through Google Play for Education</li>
- <li>Permit Google Play to offer teachers limited free trials before purchase
-(through business terms only, no development work is needed)</li>
-</ul>
+<p>As soon as you opt-in to Google Play for Education and publish, your app is
+queued for review by our third-party educator network. The review and approval
+process can take <strong>3-4 weeks or more</strong>. You'll receive notification
+by email (to your developer account address) when the review is complete, with a
+summary of the review results. </p>
+
+<p class="note"><strong>Note</strong>: Until the full product launch in Fall
+2013, please expect the initial review of your app to take longer than usual.
+</p>
+
+<p>At any time, you can check the review and approval status of your app in the
+Developer Console, under "Google Play for Education" in the app's Pricing and
+Distribution page. There are three approval states:</p>
-<p>In addition, it's highly recommended that your apps:</p>
<ul>
- <li>Disable in-app purchase in any UI accessible to students.</li>
+<li><em>Pending</em> &mdash; Your app was sent for review and the review
+is not yet complete.</li>
+<li><em>Approved</em> &mdash; Your app was reviewed and approved. The app
+will be made available directly to educators through Google Play for Education.
+Until the full product launch later this year, your app will be available to a
+limited number of educators through the <a
+href="{@docRoot}distribute/googleplay/edu/start.html#program">pilot program</a>.
+Once your app is approved, you can update it at your convenience without needing
+another full review. </li>
+<li><em>Not approved</em> &mdash; Your app was reviewed and not approved.
+Check the notification email for information about why the app was not approved.
+You can address any issues and opt-in again for another review. </li>
</ul>
-<h2 id="quality">High Quality and Usability</h2>
+<p>If you have questions about the review status of your app, follow the process
+discussed in the next section. </p>
-<p>Google Play for Education brings educational content to students and teachers
-on Android tablets. Your apps should be designed to perform well and look great
-on Android tablets, and they should offer the best user experience possible.
-</p>
+<h3 id="appeal">5. Get support or appeal your review results</h3>
-<p>High quality apps are engaging, intuitive, and offer compelling content.
-Google Play for Education will highlight high-quality apps for easy discovery in
-the store. Here are some recommendations for making your app easy for students
-and teachers to enjoy.</p>
+<p>After your app is reviewed you'll receive an email giving you the review
+results, including whether the app was approved, how the app was classified, and
+what issues may need to be addressed. You'll receive the email at the address
+you specified for your developer account. </p>
-<ul>
- <li>Meet Core app quality guidelines
- <ul>
- <li>Follow <a href="{@docRoot}design/index.html">Android Design Guidelines</a>.
- Pay special attention to the sections on <a href="{@docRoot}design/patterns/actionbar.html">Action
- Bar</a>, <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> and <a
- href="{@docRoot}design/patterns/pure-android.html">Pure Android</a>.</li>
- <li>Test your apps against the <a href="{@docRoot}distribute/googleplay/quality/core.html">Core
- App Quality Guidelines</a>.</li>
- </ul>
- </li>
-<li>Meet tablet app quality guidelines
- <ul>
- <li>Follow our best practices for tablet app development</li>
- <li>Review the <a href="{@docRoot}distribute/googleplay/quality/tablet.html">Tablet App
- Quality Checklist</a> and <a href="http://android-developers.blogspot.com/2012/11/designing-for-tablets-were-here-to-help.html"
- class="external-link;" target="_android">blog post on designing for tablets</a></li>
- <li>Check your Optimization Tips in the Google Play Developer Console (if you've already uploaded your app)</li>
- </ul>
-<li>Strive for simplicity and highest usability for students
- <ul>
- <li>Design your app so that teachers and students can use all capabilities of your app without
- having to sign-in to multiple accounts and remember multiple passwords. </li>
- <li>For best experience, use Google sign-in in your apps, which provides seamless authentication
- across apps. <!--Google Account login service and integrate with Google Drive where appropriate. --></li>
- </ul>
-</li>
-</ul>
-<p>In addition, it's highly recommended that your apps:</p>
-<ul>
- <li>Disable advertisements in any UI accessible to students.</li>
-</ul>
+<p>If you believe your app was reviewed or classified incorrectly, you will be
+able to appeal and request reconsideration. Watch for more information on the
+appeal process and links in the weeks to come.</p>
+
+<p class="note"><strong>Note</strong>: Support and appeal forms are not yet
+available, but will be available soon.</p>
+
+
+<h2 id="program">Including Your Apps in the Pilot Program</h2>
+
+<p>Leading up to the Fall 2013 launch, the Google Play for Education team is
+conducting an extensive series of pilots that include schools and students across
+the United States. Educators in participating schools can browse for apps and
+purchase them in bulk, then deploy them instantly to teacher and student
+devices. </p>
+
+<h3 id="pilot">Early opt-in and publishing</h3>
+<p>As an app developer, you can take part in the pilot program, getting your app
+into the hands of schools and key influencers in the education technology
+community. It's a great way to get early feedback on your educational app. </p>
+<p>To offer your app in the pilot program, prepare the app and ensure that it meets
+the <a href="{@docRoot}distribute/googleplay/edu/guidelines.html">Guidelines
+for Apps</a>. Then opt-in to Google Play for Education and publish as soon
+as you are ready. Once your app is approved during review by our third-party
+educator network, it will be made available to educators in the pilot program
+right away. Note that during the pilot program, the review and approval process
+may take longer than usual.</p>
-<h2 id="strategies">Strategies for Development</h2>
+<h3 id="launch">Full launch to US schools</h3>
+<p>The initial launch of Google Play for Education is planned for Fall 2013. The
+pilot program and full launch will include schools in the United States only,
+with support for schools in other countries to follow. </p>
- <p>If you have an existing educational app in Google Play, the classroom
-environment offered by Google Play for Education presents a slightly different
-set of needs, requirements, and also opportunities.</p>
+<p>At this time, you should include your app in Google Play for Education only
+if it is targeting the US K-12 market. </p>
- <p>We're working to give you the tools you need to build for the classroom
-environment from a single APK, delivered as a single product to all of your
-users in Google Play. Our goal is to let you customize your app's UI and
-features as minimally or deeply as you need, to provide a simple, intuitive, and
-beautiful learning experience. </p>
+<h3 id="more">More information</h3>
- <p>Watch for more information on developer tools coming in the weeks ahead.
-We'll update this page as we roll out tools for you to use. As a starting point,
-we recommend planning your app's design and ensuring its optimization on Nexus
-tablets.</p>
+<p>If you'd like to be notified by email of the latest information about Google Play
+for Education, visit the <a href="{@docRoot}distribute/googleplay/edu/contact.html">
+Sign Up</a> page and fill out the form. </p> \ No newline at end of file
diff --git a/docs/html/distribute/googleplay/publish/preparing.jd b/docs/html/distribute/googleplay/publish/preparing.jd
index 0cbc270..5593f4f 100644
--- a/docs/html/distribute/googleplay/publish/preparing.jd
+++ b/docs/html/distribute/googleplay/publish/preparing.jd
@@ -81,6 +81,10 @@ violations, termination of your developer account. </p>
<tr>
<td><p>Related resources:</p>
<ul style="margin-top:-.5em;">
+
+<li><strong><a href="{@docRoot}distribute/googleplay/policies/index.html">Google Play Policies and Guidelines</a></strong> &mdash; An overview of Google Play policies for spam, intellectual property, and ads, with examples of common problems. </li>
+</a></strong> &mdash; Help Center document describing various content policies and processes.</li>
+
<li><strong><a href="http://support.google.com/googleplay/android-developer/bin/topic.py?hl=en&topic=2364761&parent=2365624&ctx=topic">Policy and Best Practices
</a></strong> &mdash; Help Center document describing various content policies and processes.</li>
diff --git a/docs/html/images/gp-edu-optin.png b/docs/html/images/gp-edu-optin.png
new file mode 100644
index 0000000..91e4e0d
--- /dev/null
+++ b/docs/html/images/gp-edu-optin.png
Binary files differ
diff --git a/docs/html/images/gp-edu-process.png b/docs/html/images/gp-edu-process.png
new file mode 100644
index 0000000..febf007
--- /dev/null
+++ b/docs/html/images/gp-edu-process.png
Binary files differ
diff --git a/opengl/java/android/opengl/EGL14.java b/opengl/java/android/opengl/EGL14.java
index cd53c17..b93557d 100644
--- a/opengl/java/android/opengl/EGL14.java
+++ b/opengl/java/android/opengl/EGL14.java
@@ -1,5 +1,4 @@
/*
-**
** Copyright 2012, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/opengl/java/android/opengl/EGLConfig.java b/opengl/java/android/opengl/EGLConfig.java
index d457c9f..a7a6bbb 100644
--- a/opengl/java/android/opengl/EGLConfig.java
+++ b/opengl/java/android/opengl/EGLConfig.java
@@ -29,7 +29,7 @@ public class EGLConfig extends EGLObjectHandle {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (!(o instanceof EGLConfig)) return false;
EGLConfig that = (EGLConfig) o;
return getHandle() == that.getHandle();
diff --git a/opengl/java/android/opengl/EGLContext.java b/opengl/java/android/opengl/EGLContext.java
index 41b8ef1..c93bd6e 100644
--- a/opengl/java/android/opengl/EGLContext.java
+++ b/opengl/java/android/opengl/EGLContext.java
@@ -29,7 +29,7 @@ public class EGLContext extends EGLObjectHandle {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (!(o instanceof EGLContext)) return false;
EGLContext that = (EGLContext) o;
return getHandle() == that.getHandle();
diff --git a/opengl/java/android/opengl/EGLDisplay.java b/opengl/java/android/opengl/EGLDisplay.java
index 17d1a64..5b8043a 100644
--- a/opengl/java/android/opengl/EGLDisplay.java
+++ b/opengl/java/android/opengl/EGLDisplay.java
@@ -29,7 +29,7 @@ public class EGLDisplay extends EGLObjectHandle {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (!(o instanceof EGLDisplay)) return false;
EGLDisplay that = (EGLDisplay) o;
return getHandle() == that.getHandle();
diff --git a/opengl/java/android/opengl/EGLExt.java b/opengl/java/android/opengl/EGLExt.java
index 2e0363d..b74b5fb 100644
--- a/opengl/java/android/opengl/EGLExt.java
+++ b/opengl/java/android/opengl/EGLExt.java
@@ -1,5 +1,4 @@
/*
-**
** Copyright 2013, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/opengl/java/android/opengl/EGLSurface.java b/opengl/java/android/opengl/EGLSurface.java
index 65bec4f..c379dc9 100644
--- a/opengl/java/android/opengl/EGLSurface.java
+++ b/opengl/java/android/opengl/EGLSurface.java
@@ -29,7 +29,7 @@ public class EGLSurface extends EGLObjectHandle {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (!(o instanceof EGLSurface)) return false;
EGLSurface that = (EGLSurface) o;
return getHandle() == that.getHandle();
diff --git a/opengl/java/android/opengl/Matrix.java b/opengl/java/android/opengl/Matrix.java
index 72128ac..ce3f57e 100644
--- a/opengl/java/android/opengl/Matrix.java
+++ b/opengl/java/android/opengl/Matrix.java
@@ -19,24 +19,21 @@ package android.opengl;
/**
* Matrix math utilities. These methods operate on OpenGL ES format
* matrices and vectors stored in float arrays.
- *
+ * <p>
* Matrices are 4 x 4 column-vector matrices stored in column-major
* order:
* <pre>
* m[offset + 0] m[offset + 4] m[offset + 8] m[offset + 12]
* m[offset + 1] m[offset + 5] m[offset + 9] m[offset + 13]
* m[offset + 2] m[offset + 6] m[offset + 10] m[offset + 14]
- * m[offset + 3] m[offset + 7] m[offset + 11] m[offset + 15]
- * </pre>
+ * m[offset + 3] m[offset + 7] m[offset + 11] m[offset + 15]</pre>
*
- * Vectors are 4 row x 1 column column-vectors stored in order:
+ * Vectors are 4 x 1 column vectors stored in order:
* <pre>
* v[offset + 0]
* v[offset + 1]
* v[offset + 2]
- * v[offset + 3]
- * </pre>
- *
+ * v[offset + 3]</pre>
*/
public class Matrix {
@@ -44,12 +41,18 @@ public class Matrix {
private final static float[] sTemp = new float[32];
/**
- * Multiply two 4x4 matrices together and store the result in a third 4x4
+ * @deprecated All methods are static, do not instantiate this class.
+ */
+ @Deprecated
+ public Matrix() {}
+
+ /**
+ * Multiplies two 4x4 matrices together and stores the result in a third 4x4
* matrix. In matrix notation: result = lhs x rhs. Due to the way
* matrix multiplication works, the result matrix will have the same
* effect as first multiplying by the rhs matrix, then multiplying by
* the lhs matrix. This is the opposite of what you might expect.
- *
+ * <p>
* The same float array may be passed for result, lhs, and/or rhs. However,
* the result element values are undefined if the result elements overlap
* either the lhs or rhs elements.
@@ -70,9 +73,9 @@ public class Matrix {
float[] lhs, int lhsOffset, float[] rhs, int rhsOffset);
/**
- * Multiply a 4 element vector by a 4x4 matrix and store the result in a 4
- * element column vector. In matrix notation: result = lhs x rhs
- *
+ * Multiplies a 4 element vector by a 4x4 matrix and stores the result in a
+ * 4-element column vector. In matrix notation: result = lhs x rhs
+ * <p>
* The same float array may be passed for resultVec, lhsMat, and/or rhsVec.
* However, the resultVec element values are undefined if the resultVec
* elements overlap either the lhsMat or rhsVec elements.
@@ -97,12 +100,14 @@ public class Matrix {
/**
* Transposes a 4 x 4 matrix.
+ * <p>
+ * mTrans and m must not overlap.
*
- * @param mTrans the array that holds the output inverted matrix
- * @param mTransOffset an offset into mInv where the inverted matrix is
+ * @param mTrans the array that holds the output transposed matrix
+ * @param mTransOffset an offset into mTrans where the transposed matrix is
* stored.
* @param m the input array
- * @param mOffset an offset into m where the matrix is stored.
+ * @param mOffset an offset into m where the input matrix is stored.
*/
public static void transposeM(float[] mTrans, int mTransOffset, float[] m,
int mOffset) {
@@ -117,12 +122,14 @@ public class Matrix {
/**
* Inverts a 4 x 4 matrix.
+ * <p>
+ * mInv and m must not overlap.
*
* @param mInv the array that holds the output inverted matrix
* @param mInvOffset an offset into mInv where the inverted matrix is
* stored.
* @param m the input array
- * @param mOffset an offset into m where the matrix is stored.
+ * @param mOffset an offset into m where the input matrix is stored.
* @return true if the matrix could be inverted, false if it could not.
*/
public static boolean invertM(float[] mInv, int mInvOffset, float[] m,
@@ -301,10 +308,11 @@ public class Matrix {
/**
- * Define a projection matrix in terms of six clip planes
- * @param m the float array that holds the perspective matrix
+ * Defines a projection matrix in terms of six clip planes.
+ *
+ * @param m the float array that holds the output perspective matrix
* @param offset the offset into float array m where the perspective
- * matrix data is written
+ * matrix data is written
* @param left
* @param right
* @param bottom
@@ -358,11 +366,12 @@ public class Matrix {
}
/**
- * Define a projection matrix in terms of a field of view angle, an
- * aspect ratio, and z clip planes
+ * Defines a projection matrix in terms of a field of view angle, an
+ * aspect ratio, and z clip planes.
+ *
* @param m the float array that holds the perspective matrix
* @param offset the offset into float array m where the perspective
- * matrix data is written
+ * matrix data is written
* @param fovy field of view in y direction, in degrees
* @param aspect width to height aspect ratio of the viewport
* @param zNear
@@ -395,7 +404,7 @@ public class Matrix {
}
/**
- * Computes the length of a vector
+ * Computes the length of a vector.
*
* @param x x coordinate of a vector
* @param y y coordinate of a vector
@@ -408,6 +417,7 @@ public class Matrix {
/**
* Sets matrix m to the identity matrix.
+ *
* @param sm returns the result
* @param smOffset index into sm where the result matrix starts
*/
@@ -421,7 +431,10 @@ public class Matrix {
}
/**
- * Scales matrix m by x, y, and z, putting the result in sm
+ * Scales matrix m by x, y, and z, putting the result in sm.
+ * <p>
+ * m and sm must not overlap.
+ *
* @param sm returns the result
* @param smOffset index into sm where the result matrix starts
* @param m source matrix
@@ -444,7 +457,8 @@ public class Matrix {
}
/**
- * Scales matrix m in place by sx, sy, and sz
+ * Scales matrix m in place by sx, sy, and sz.
+ *
* @param m matrix to scale
* @param mOffset index into m where the matrix starts
* @param x scale factor x
@@ -462,7 +476,10 @@ public class Matrix {
}
/**
- * Translates matrix m by x, y, and z, putting the result in tm
+ * Translates matrix m by x, y, and z, putting the result in tm.
+ * <p>
+ * m and tm must not overlap.
+ *
* @param tm returns the result
* @param tmOffset index into sm where the result matrix starts
* @param m source matrix
@@ -487,6 +504,7 @@ public class Matrix {
/**
* Translates matrix m by x, y, and z in place.
+ *
* @param m matrix
* @param mOffset index into m where the matrix starts
* @param x translation factor x
@@ -503,15 +521,18 @@ public class Matrix {
}
/**
- * Rotates matrix m by angle a (in degrees) around the axis (x, y, z)
+ * Rotates matrix m by angle a (in degrees) around the axis (x, y, z).
+ * <p>
+ * m and rm must not overlap.
+ *
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param m source matrix
* @param mOffset index into m where the source matrix starts
* @param a angle to rotate in degrees
- * @param x scale factor x
- * @param y scale factor y
- * @param z scale factor z
+ * @param x X axis component
+ * @param y Y axis component
+ * @param z Z axis component
*/
public static void rotateM(float[] rm, int rmOffset,
float[] m, int mOffset,
@@ -524,13 +545,14 @@ public class Matrix {
/**
* Rotates matrix m in place by angle a (in degrees)
- * around the axis (x, y, z)
+ * around the axis (x, y, z).
+ *
* @param m source matrix
* @param mOffset index into m where the matrix starts
* @param a angle to rotate in degrees
- * @param x scale factor x
- * @param y scale factor y
- * @param z scale factor z
+ * @param x X axis component
+ * @param y Y axis component
+ * @param z Z axis component
*/
public static void rotateM(float[] m, int mOffset,
float a, float x, float y, float z) {
@@ -542,13 +564,18 @@ public class Matrix {
}
/**
- * Rotates matrix m by angle a (in degrees) around the axis (x, y, z)
+ * Creates a matrix for rotation by angle a (in degrees)
+ * around the axis (x, y, z).
+ * <p>
+ * An optimized path will be used for rotation about a major axis
+ * (e.g. x=1.0f y=0.0f z=0.0f).
+ *
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param a angle to rotate in degrees
- * @param x scale factor x
- * @param y scale factor y
- * @param z scale factor z
+ * @param x X axis component
+ * @param y Y axis component
+ * @param z Z axis component
*/
public static void setRotateM(float[] rm, int rmOffset,
float a, float x, float y, float z) {
@@ -608,7 +635,8 @@ public class Matrix {
}
/**
- * Converts Euler angles to a rotation matrix
+ * Converts Euler angles to a rotation matrix.
+ *
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param x angle of rotation, in degrees
@@ -651,7 +679,7 @@ public class Matrix {
}
/**
- * Define a viewing transformation in terms of an eye point, a center of
+ * Defines a viewing transformation in terms of an eye point, a center of
* view, and an up vector.
*
* @param rm returns the result
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 2d7a171..5e6bdb1 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -2857,6 +2857,8 @@ final class ActivityStack {
return;
}
+ mStackSupervisor.moveHomeStack(isHomeStack());
+
// Shift all activities with this task up to the top
// of the stack, keeping them in the same internal order.
mTaskHistory.remove(tr);
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index fd06535..ab0590e 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -97,6 +97,7 @@ import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.Choreographer;
import android.view.Display;
+import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IApplicationToken;
@@ -6609,8 +6610,9 @@ public class WindowManagerService extends IWindowManager.Stub
displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
displayInfo.appWidth = appWidth;
displayInfo.appHeight = appHeight;
- displayInfo.getLogicalMetrics(mRealDisplayMetrics, null);
- displayInfo.getAppMetrics(mDisplayMetrics, null);
+ displayInfo.getLogicalMetrics(mRealDisplayMetrics,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ displayInfo.getAppMetrics(mDisplayMetrics);
mDisplayManagerService.setDisplayInfoOverrideFromWindowManager(
displayContent.getDisplayId(), displayInfo);
}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 29d6e4d..44077ae 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -39,7 +39,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
import android.view.Display;
import java.io.File;
@@ -574,7 +574,7 @@ public class MockContext extends Context {
/** @hide */
@Override
- public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
+ public DisplayAdjustments getDisplayAdjustments(int displayId) {
throw new UnsupportedOperationException();
}
}