diff options
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 — 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> — 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> — Allows students to look at problems in + a new way, linking learning across subjects and disciplines.</li> + <li><em>Collaboration</em> — Allows students and (if appropriate) educators + to work together to reach a goal.</li> + <li><em>Communication</em> — 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> — 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> — Test the app in network environment that uses +proxies. Many schools use proxies.</li> +<li><em>Secondary user account</em> — 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> — 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> — 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> — 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> — 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 +— 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 — 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 — 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> — Your app was sent for review and the review +is not yet complete.</li> +<li><em>Approved</em> — 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> — 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> — An overview of Google Play policies for spam, intellectual property, and ads, with examples of common problems. </li> +</a></strong> — 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> — 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 Binary files differnew file mode 100644 index 0000000..91e4e0d --- /dev/null +++ b/docs/html/images/gp-edu-optin.png diff --git a/docs/html/images/gp-edu-process.png b/docs/html/images/gp-edu-process.png Binary files differnew file mode 100644 index 0000000..febf007 --- /dev/null +++ b/docs/html/images/gp-edu-process.png 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(); } } |