Merge "Don't require apps to call showLights to use default lights."
diff --git a/Android.mk b/Android.mk
index ad164e20..d68da97 100644
--- a/Android.mk
+++ b/Android.mk
@@ -136,6 +136,7 @@
 	core/java/android/content/ISyncStatusObserver.aidl \
 	core/java/android/content/pm/ILauncherApps.aidl \
 	core/java/android/content/pm/IOnAppsChangedListener.aidl \
+	core/java/android/content/pm/IOtaDexopt.aidl \
 	core/java/android/content/pm/IPackageDataObserver.aidl \
 	core/java/android/content/pm/IPackageDeleteObserver.aidl \
 	core/java/android/content/pm/IPackageDeleteObserver2.aidl \
diff --git a/core/java/android/content/pm/IOtaDexopt.aidl b/core/java/android/content/pm/IOtaDexopt.aidl
new file mode 100644
index 0000000..8f38d6f
--- /dev/null
+++ b/core/java/android/content/pm/IOtaDexopt.aidl
@@ -0,0 +1,49 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content.pm;
+
+/**
+ * A/B OTA dexopting service.
+ *
+ * {@hide}
+ */
+interface IOtaDexopt {
+    /**
+     * Prepare for A/B OTA dexopt. Initialize internal structures.
+     *
+     * Calls to the other methods are only valid after a call to prepare. You may not call
+     * prepare twice without a cleanup call.
+     */
+    void prepare();
+
+    /**
+     * Clean up all internal state.
+     */
+    void cleanup();
+
+    /**
+     * Check whether all updates have been performed.
+     */
+    boolean isDone();
+
+    /**
+     * Optimize the next package. Note: this command is synchronous, that is, only returns after
+     * the package has been dexopted (or dexopting failed).
+     */
+    void dexoptNextPackage();
+}
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 5997515..4b70649 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -438,8 +438,11 @@
         final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
         intent.setData(uri);
+
+        // note that docsui treats this as *force* show advanced. So sending
+        // false permits advanced to be shown based on user preferences.
+        intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
         intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true);
-        intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
         return intent;
     }
 
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 8892e34..e9c196d 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -27,6 +27,7 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -872,6 +873,23 @@
             mPackageName = null;
         }
 
+        /**
+         * Get the Id of all predefined media sizes beside the {@link #UNKNOWN_PORTRAIT} and
+         * {@link #UNKNOWN_LANDSCAPE}.
+         *
+         * @return List of all predefined media sizes
+         *
+         * @hide
+         */
+        public static @NonNull ArraySet<MediaSize> getAllPredefinedSizes() {
+            ArraySet<MediaSize> definedMediaSizes = new ArraySet<>(sIdToMediaSizeMap.values());
+
+            definedMediaSizes.remove(UNKNOWN_PORTRAIT);
+            definedMediaSizes.remove(UNKNOWN_LANDSCAPE);
+
+            return definedMediaSizes;
+        }
+
         /** @hide */
         public MediaSize(String id, String label, String packageName,
                 int widthMils, int heightMils, int labelResId) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 2733391..cf13a13 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -25,6 +25,7 @@
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
 import android.content.ServiceConnection;
+import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.LabeledIntent;
 import android.content.pm.PackageManager;
@@ -35,6 +36,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -68,6 +70,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.MetricsProto.MetricsEvent;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -91,6 +94,11 @@
     private ChooserListAdapter mChooserListAdapter;
     private ChooserRowAdapter mChooserRowAdapter;
 
+    private SharedPreferences mPinnedSharedPrefs;
+    private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
+    private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
+    private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
+
     private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
 
     private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
@@ -207,12 +215,30 @@
         mRefinementIntentSender = intent.getParcelableExtra(
                 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
         setSafeForwardingMode(true);
+
+        mPinnedSharedPrefs = getPinnedSharedPrefs(this);
         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                 null, false);
 
         MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
     }
 
+    static SharedPreferences getPinnedSharedPrefs(Context context) {
+        // The code below is because in the android:ui process, no one can hear you scream.
+        // The package info in the context isn't initialized in the way it is for normal apps,
+        // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+        // build the path manually below using the same policy that appears in ContextImpl.
+        // This fails silently under the hood if there's a problem, so if we find ourselves in
+        // the case where we don't have access to credential encrypted storage we just won't
+        // have our pinned target info.
+        final File prefsFile = new File(new File(
+                Environment.getDataUserCredentialEncryptedPackageDirectory(null,
+                        context.getUserId(), context.getPackageName()),
+                "shared_prefs"),
+                PINNED_SHARED_PREFS_NAME + ".xml");
+        return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
+    }
+
     @Override
     protected void onDestroy() {
         super.onDestroy();
@@ -243,7 +269,7 @@
     }
 
     @Override
-    void onActivityStarted(TargetInfo cti) {
+    public void onActivityStarted(TargetInfo cti) {
         if (mChosenComponentSender != null) {
             final ComponentName target = cti.getResolvedComponentName();
             if (target != null) {
@@ -259,7 +285,7 @@
     }
 
     @Override
-    void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
+    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
             boolean alwaysUseOption) {
         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
         mChooserListAdapter = (ChooserListAdapter) adapter;
@@ -272,17 +298,17 @@
     }
 
     @Override
-    int getLayoutResource() {
+    public int getLayoutResource() {
         return R.layout.chooser_grid;
     }
 
     @Override
-    boolean shouldGetActivityMetadata() {
+    public boolean shouldGetActivityMetadata() {
         return true;
     }
 
     @Override
-    boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
         final Intent intent = target.getResolvedIntent();
         final ResolveInfo resolve = target.getResolveInfo();
 
@@ -299,6 +325,16 @@
         return false;
     }
 
+    @Override
+    public void showTargetDetails(ResolveInfo ri) {
+        ComponentName name = ri.activityInfo.getComponentName();
+        boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+        ResolverTargetActionsDialogFragment f =
+                new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
+                        name, pinned);
+        f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+    }
+
     private void modifyTargetIntent(Intent in) {
         final String action = in.getAction();
         if (Intent.ACTION_SEND.equals(action) ||
@@ -340,7 +376,7 @@
     }
 
     @Override
-    void startSelected(int which, boolean always, boolean filtered) {
+    public void startSelected(int which, boolean always, boolean filtered) {
         super.startSelected(which, always, filtered);
 
         if (mChooserListAdapter != null) {
@@ -471,7 +507,7 @@
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
     }
 
-    void onSetupVoiceInteraction() {
+    public void onSetupVoiceInteraction() {
         // Do nothing. We'll send the voice stuff ourselves.
     }
 
@@ -543,7 +579,7 @@
     }
 
     @Override
-    ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
             Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
             boolean filterLastUsed) {
         final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
@@ -711,6 +747,11 @@
             }
             return results;
         }
+
+        @Override
+        public boolean isPinned() {
+            return mSourceInfo != null ? mSourceInfo.isPinned() : false;
+        }
     }
 
     public class ChooserListAdapter extends ResolveListAdapter {
@@ -777,6 +818,20 @@
         }
 
         @Override
+        public boolean isComponentPinned(ComponentName name) {
+            return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+        }
+
+        @Override
+        public float getScore(DisplayResolveInfo target) {
+            float score = super.getScore(target);
+            if (target.isPinned()) {
+                score += PINNED_TARGET_SCORE_BOOST;
+            }
+            return score;
+        }
+
+        @Override
         public View onCreateView(ViewGroup parent) {
             return mInflater.inflate(
                     com.android.internal.R.layout.resolve_grid_item, parent, false);
@@ -1121,7 +1176,7 @@
                 v.setOnLongClickListener(new OnLongClickListener() {
                     @Override
                     public boolean onLongClick(View v) {
-                        showAppDetails(
+                        showTargetDetails(
                                 mChooserListAdapter.resolveInfoForPosition(
                                         holder.itemIndices[column], true));
                         return true;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ec148c55..53e329d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -22,6 +22,7 @@
 import android.app.VoiceInteractor.PickOptionRequest;
 import android.app.VoiceInteractor.PickOptionRequest.Option;
 import android.app.VoiceInteractor.Prompt;
+import android.content.pm.ComponentInfo;
 import android.os.AsyncTask;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -336,12 +337,12 @@
     /**
      * Perform any initialization needed for voice interaction.
      */
-    void onSetupVoiceInteraction() {
+    public void onSetupVoiceInteraction() {
         // Do it right now. Subclasses may delay this and send it later.
         sendVoiceChoicesIfNeeded();
     }
 
-    void sendVoiceChoicesIfNeeded() {
+    public void sendVoiceChoicesIfNeeded() {
         if (!isVoiceInteraction()) {
             // Clearly not needed.
             return;
@@ -382,7 +383,7 @@
         return null;
     }
 
-    int getLayoutResource() {
+    public int getLayoutResource() {
         return R.layout.resolver_list;
     }
 
@@ -591,7 +592,7 @@
                 mAlwaysUseOption);
     }
 
-    void startSelected(int which, boolean always, boolean filtered) {
+    public void startSelected(int which, boolean always, boolean filtered) {
         if (isFinishing()) {
             return;
         }
@@ -761,7 +762,7 @@
         return true;
     }
 
-    void safelyStartActivity(TargetInfo cti) {
+    public void safelyStartActivity(TargetInfo cti) {
         // If needed, show that intent is forwarded
         // from managed profile to owner or other way around.
         if (mProfileSwitchMessageId != -1) {
@@ -791,26 +792,26 @@
         }
     }
 
-    void onActivityStarted(TargetInfo cti) {
+    public void onActivityStarted(TargetInfo cti) {
         // Do nothing
     }
 
-    boolean shouldGetActivityMetadata() {
+    public boolean shouldGetActivityMetadata() {
         return false;
     }
 
-    boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
         return true;
     }
 
-    void showAppDetails(ResolveInfo ri) {
+    public void showTargetDetails(ResolveInfo ri) {
         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
         startActivity(in);
     }
 
-    ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
             Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
             boolean filterLastUsed) {
         return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
@@ -820,7 +821,7 @@
     /**
      * Returns true if the activity is finishing and creation should halt
      */
-    boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
+    public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
             List<ResolveInfo> rList, boolean alwaysUseOption) {
         // The last argument of createAdapter is whether to do special handling
         // of the last used choice to highlight it in the list.  We need to always
@@ -867,7 +868,7 @@
         return false;
     }
 
-    void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
+    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
             boolean alwaysUseOption) {
         final boolean useHeader = adapter.hasFilteredItem();
         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -898,7 +899,7 @@
                 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
     }
 
-    final class DisplayResolveInfo implements TargetInfo {
+    public final class DisplayResolveInfo implements TargetInfo {
         private final ResolveInfo mResolveInfo;
         private final CharSequence mDisplayLabel;
         private Drawable mDisplayIcon;
@@ -906,8 +907,9 @@
         private final CharSequence mExtendedInfo;
         private final Intent mResolvedIntent;
         private final List<Intent> mSourceIntents = new ArrayList<>();
+        private boolean mPinned;
 
-        DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
+        public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
                 CharSequence pInfo, Intent pOrigIntent) {
             mSourceIntents.add(originalIntent);
             mResolveInfo = pri;
@@ -932,6 +934,7 @@
             mExtendedInfo = other.mExtendedInfo;
             mResolvedIntent = new Intent(other.mResolvedIntent);
             mResolvedIntent.fillIn(fillInIntent, flags);
+            mPinned = other.mPinned;
         }
 
         public ResolveInfo getResolveInfo() {
@@ -1026,6 +1029,15 @@
             activity.startActivityAsUser(mResolvedIntent, options, user);
             return false;
         }
+
+        @Override
+        public boolean isPinned() {
+            return mPinned;
+        }
+
+        public void setPinned(boolean pinned) {
+            mPinned = pinned;
+        }
     }
 
     /**
@@ -1039,7 +1051,7 @@
          *
          * @return the resolved intent for this target
          */
-        public Intent getResolvedIntent();
+        Intent getResolvedIntent();
 
         /**
          * Get the resolved component name that represents this target. Note that this may not
@@ -1048,7 +1060,7 @@
          *
          * @return the resolved ComponentName for this target
          */
-        public ComponentName getResolvedComponentName();
+        ComponentName getResolvedComponentName();
 
         /**
          * Start the activity referenced by this target.
@@ -1057,7 +1069,7 @@
          * @param options ActivityOptions bundle
          * @return true if the start completed successfully
          */
-        public boolean start(Activity activity, Bundle options);
+        boolean start(Activity activity, Bundle options);
 
         /**
          * Start the activity referenced by this target as if the ResolverActivity's caller
@@ -1068,7 +1080,7 @@
          * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
          * @return true if the start completed successfully
          */
-        public boolean startAsCaller(Activity activity, Bundle options, int userId);
+        boolean startAsCaller(Activity activity, Bundle options, int userId);
 
         /**
          * Start the activity referenced by this target as a given user.
@@ -1078,7 +1090,7 @@
          * @param user handle for the user to start the activity as
          * @return true if the start completed successfully
          */
-        public boolean startAsUser(Activity activity, Bundle options, UserHandle user);
+        boolean startAsUser(Activity activity, Bundle options, UserHandle user);
 
         /**
          * Return the ResolveInfo about how and why this target matched the original query
@@ -1086,14 +1098,14 @@
          *
          * @return ResolveInfo representing this target's match
          */
-        public ResolveInfo getResolveInfo();
+        ResolveInfo getResolveInfo();
 
         /**
          * Return the human-readable text label for this target.
          *
          * @return user-visible target label
          */
-        public CharSequence getDisplayLabel();
+        CharSequence getDisplayLabel();
 
         /**
          * Return any extended info for this target. This may be used to disambiguate
@@ -1101,35 +1113,40 @@
          *
          * @return human-readable disambig string or null if none present
          */
-        public CharSequence getExtendedInfo();
+        CharSequence getExtendedInfo();
 
         /**
          * @return The drawable that should be used to represent this target
          */
-        public Drawable getDisplayIcon();
+        Drawable getDisplayIcon();
 
         /**
          * @return The (small) icon to badge the target with
          */
-        public Drawable getBadgeIcon();
+        Drawable getBadgeIcon();
 
         /**
          * @return The content description for the badge icon
          */
-        public CharSequence getBadgeContentDescription();
+        CharSequence getBadgeContentDescription();
 
         /**
          * Clone this target with the given fill-in information.
          */
-        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
+        TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
 
         /**
          * @return the list of supported source intents deduped against this single target
          */
-        public List<Intent> getAllSourceIntents();
+        List<Intent> getAllSourceIntents();
+
+        /**
+         * @return true if this target should be pinned to the front by the request of the user
+         */
+        boolean isPinned();
     }
 
-    class ResolveListAdapter extends BaseAdapter {
+    public class ResolveListAdapter extends BaseAdapter {
         private final List<Intent> mIntents;
         private final Intent[] mInitialIntents;
         private final List<ResolveInfo> mBaseResolveList;
@@ -1376,9 +1393,12 @@
                     }
                 }
                 if (!found) {
-                    into.add(new ResolvedComponentInfo(new ComponentName(
-                            newInfo.activityInfo.packageName, newInfo.activityInfo.name),
-                            intent, newInfo));
+                    final ComponentName name = new ComponentName(
+                            newInfo.activityInfo.packageName, newInfo.activityInfo.name);
+                    final ResolvedComponentInfo rci = new ResolvedComponentInfo(name,
+                            intent, newInfo);
+                    rci.setPinned(isComponentPinned(name));
+                    into.add(rci);
                 }
             }
         }
@@ -1454,6 +1474,7 @@
             final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
             final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
                     extraInfo, replaceIntent);
+            dri.setPinned(rci.isPinned());
             addResolveInfo(dri);
             if (replaceIntent == intent) {
                 // Only add alternates if we didn't get a specific replacement from
@@ -1537,11 +1558,11 @@
             return false;
         }
 
-        protected int getDisplayResolveInfoCount() {
+        public int getDisplayResolveInfoCount() {
             return mDisplayList.size();
         }
 
-        protected DisplayResolveInfo getDisplayResolveInfo(int index) {
+        public DisplayResolveInfo getDisplayResolveInfo(int index) {
             // Used to query services. We only query services for primary targets, not alternates.
             return mDisplayList.get(index);
         }
@@ -1571,6 +1592,10 @@
             return !TextUtils.isEmpty(info.getExtendedInfo());
         }
 
+        public boolean isComponentPinned(ComponentName name) {
+            return false;
+        }
+
         public final void bindView(int position, View view) {
             onBindView(view, getItem(position));
         }
@@ -1607,6 +1632,7 @@
 
     static final class ResolvedComponentInfo {
         public final ComponentName name;
+        private boolean mPinned;
         private final List<Intent> mIntents = new ArrayList<>();
         private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
 
@@ -1649,6 +1675,14 @@
             }
             return -1;
         }
+
+        public boolean isPinned() {
+            return mPinned;
+        }
+
+        public void setPinned(boolean pinned) {
+            mPinned = pinned;
+        }
     }
 
     static class ViewHolder {
@@ -1702,7 +1736,7 @@
                 return false;
             }
             ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
-            showAppDetails(ri);
+            showTargetDetails(ri);
             return true;
         }
 
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 31556e2..0ae21c6 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -47,8 +47,8 @@
 
     private static final boolean DEBUG = false;
 
-    // Two weeks
-    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
+    // One week
+    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;
 
     private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;
 
@@ -171,15 +171,27 @@
             }
         }
 
-        if (mStats != null) {
-            final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
-                    lhs.activityInfo.packageName, lhs.activityInfo.name));
-            final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
-                    rhs.activityInfo.packageName, rhs.activityInfo.name));
-            final float diff = rhsTarget.score - lhsTarget.score;
+        final boolean lPinned = lhsp.isPinned();
+        final boolean rPinned = rhsp.isPinned();
 
-            if (diff != 0) {
-                return diff > 0 ? 1 : -1;
+        if (lPinned && !rPinned) {
+            return -1;
+        } else if (!lPinned && rPinned) {
+            return 1;
+        }
+
+        // Pinned items stay stable within a normal lexical sort and ignore scoring.
+        if (!lPinned && !rPinned) {
+            if (mStats != null) {
+                final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
+                        lhs.activityInfo.packageName, lhs.activityInfo.name));
+                final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
+                        rhs.activityInfo.packageName, rhs.activityInfo.name));
+                final float diff = rhsTarget.score - lhsTarget.score;
+
+                if (diff != 0) {
+                    return diff > 0 ? 1 : -1;
+                }
             }
         }
 
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
new file mode 100644
index 0000000..8156f79
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.internal.app;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import com.android.internal.R;
+
+/**
+ * Shows a dialog with actions to take on a chooser target
+ */
+public class ResolverTargetActionsDialogFragment extends DialogFragment
+        implements DialogInterface.OnClickListener {
+    private static final String NAME_KEY = "componentName";
+    private static final String PINNED_KEY = "pinned";
+    private static final String TITLE_KEY = "title";
+
+    // Sync with R.array.resolver_target_actions_* resources
+    private static final int TOGGLE_PIN_INDEX = 0;
+    private static final int APP_INFO_INDEX = 1;
+
+    public ResolverTargetActionsDialogFragment() {
+    }
+
+    public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
+            boolean pinned) {
+        Bundle args = new Bundle();
+        args.putCharSequence(TITLE_KEY, title);
+        args.putParcelable(NAME_KEY, name);
+        args.putBoolean(PINNED_KEY, pinned);
+        setArguments(args);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final Bundle args = getArguments();
+        final int itemRes = args.getBoolean(PINNED_KEY, false)
+                ? R.array.resolver_target_actions_unpin
+                : R.array.resolver_target_actions_pin;
+        return new Builder(getContext())
+                .setCancelable(true)
+                .setItems(itemRes, this)
+                .setTitle(args.getCharSequence(TITLE_KEY))
+                .create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        final Bundle args = getArguments();
+        ComponentName name = args.getParcelable(NAME_KEY);
+        switch (which) {
+            case TOGGLE_PIN_INDEX:
+                SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
+                final String key = name.flattenToString();
+                boolean currentVal = sp.getBoolean(name.flattenToString(), false);
+                if (currentVal) {
+                    sp.edit().remove(key).apply();
+                } else {
+                    sp.edit().putBoolean(key, true).apply();
+                }
+
+                // Force the chooser to requery and resort things
+                getActivity().recreate();
+                break;
+            case APP_INFO_INDEX:
+                Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+                        .setData(Uri.fromParts("package", name.getPackageName(), null))
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+                startActivity(in);
+                break;
+        }
+        dismiss();
+    }
+}
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index a6a4564..c083a5e 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -402,4 +402,15 @@
         <item>@color/Red_700</item>
     </array>
 
+    <!-- Used in ResolverTargetActionsDialogFragment -->
+    <string-array name="resolver_target_actions_pin">
+        <item>@string/pin_target</item>
+        <item>@string/app_info</item>
+    </string-array>
+
+    <string-array name="resolver_target_actions_unpin">
+        <item>@string/unpin_target</item>
+        <item>@string/app_info</item>
+    </string-array>
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 523826b..b2f5f8a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4206,4 +4206,15 @@
     <string name="usb_mtp_launch_notification_title">Connected to <xliff:g id="product_name">%1$s</xliff:g></string>
     <!-- Description of notification shown after a MTP device is connected to Android. -->
     <string name="usb_mtp_launch_notification_description">Tap to view files</string>
+
+    <!-- Resolver target actions strings -->
+
+    <!-- Pin (as in to a bulletin board with a pushpin) a resolver
+         target to the front of the list. -->
+    <string name="pin_target">Pin</string>
+    <!-- Unpin a resolver target such that it sorts normally. -->
+    <string name="unpin_target">Unpin</string>
+    <!-- View application info for a target. -->
+    <string name="app_info">App info</string>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1f0e21e..bc609c6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2525,4 +2525,9 @@
 
   <java-symbol type="string" name="usb_mtp_launch_notification_title" />
   <java-symbol type="string" name="usb_mtp_launch_notification_description" />
+
+  <!-- Resolver target actions -->
+  <java-symbol type="array" name="resolver_target_actions_pin" />
+  <java-symbol type="array" name="resolver_target_actions_unpin" />
+
 </resources>
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index c219418..62a01dc 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -30,7 +30,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 
 /**
  * The contract between the TV provider and applications. Contains definitions for the supported
@@ -1331,13 +1330,7 @@
              * @return canonical genre strings.
              */
             public static String[] decode(String genres) {
-                StringTokenizer st = new StringTokenizer(genres, "\\s*,\\s*");
-                String[] result = new String[st.countTokens()];
-                int i = 0;
-                while (st.hasMoreTokens()){
-                    result[i++] = st.nextToken();
-                }
-                return result;
+                return genres.split("\\s*,\\s*");
             }
 
             /**
diff --git a/packages/DocumentsUI/res/drawable/ic_sd_storage.xml b/packages/DocumentsUI/res/drawable/ic_sd_storage.xml
new file mode 100644
index 0000000..b0f3cc3
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/ic_sd_storage.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18 2h-8L4.02 8 4 20c0 1.1.9 2 2 2h12c1.1 0 2,-.9 2,-2V4c0,-1.1,-.9,-2,-2,-2zm-6 6h-2V4h2v4zm3 0h-2V4h2v4zm3 0h-2V4h2v4z"/>
+</vector>
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index a3cfde8..ac668fd 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -88,5 +88,6 @@
     <item
         android:id="@+id/menu_settings"
         android:title="@string/menu_settings"
-        android:showAsAction="never" />
+        android:showAsAction="never"
+        android:visible="false" />
 </menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index afe9336..3c49f16 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -39,8 +39,8 @@
     <string name="menu_sort">Sort by</string>
     <!-- Menu item that enters a mode to search for documents [CHAR LIMIT=24] -->
     <string name="menu_search">Search</string>
-    <!-- Menu item that enters activity to change settings [CHAR LIMIT=24] -->
-    <string name="menu_settings">Settings</string>
+    <!-- Menu item that enters activity to change settings for current root [CHAR LIMIT=24] -->
+    <string name="menu_settings">Storage settings</string>
 
     <!-- Menu item title that opens the selected documents [CHAR LIMIT=24] -->
     <string name="menu_open">Open</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 1474aa6..6fd29a4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -150,7 +150,6 @@
         final MenuItem list = menu.findItem(R.id.menu_list);
         final MenuItem advanced = menu.findItem(R.id.menu_advanced);
         final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
-        final MenuItem settings = menu.findItem(R.id.menu_settings);
         final MenuItem search = menu.findItem(R.id.menu_search);
 
         // I'm thinkin' this isn't necesary here. If it is...'cuz of a bug....
@@ -167,7 +166,6 @@
         sortSize.setVisible(mState.showSize); // Only sort by size when visible
         fileSize.setVisible(!mState.forceSize);
         advanced.setVisible(!mState.forceAdvanced);
-        settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0);
         search.setVisible(canSearchRoot());
 
         advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index c3395ae..e7347a3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -297,7 +297,6 @@
         final MenuItem grid = menu.findItem(R.id.menu_grid);
         final MenuItem list = menu.findItem(R.id.menu_list);
         final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
-        final MenuItem settings = menu.findItem(R.id.menu_settings);
 
         boolean recents = cwd == null;
         boolean picking = mState.action == ACTION_CREATE
@@ -314,7 +313,6 @@
         }
 
         fileSize.setVisible(fileSize.isVisible() && !picking);
-        settings.setVisible(false);
 
         if (mState.action == ACTION_CREATE) {
             final FragmentManager fm = getFragmentManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index b490c00..3968cae 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -245,16 +245,21 @@
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
+        final RootInfo root = getCurrentRoot();
 
         final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
-        final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
         final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
+        final MenuItem settings = menu.findItem(R.id.menu_settings);
 
         createDir.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
         createDir.setVisible(true);
         createDir.setEnabled(canCreateDirectory());
-
         pasteFromCb.setEnabled(mClipper.hasItemsToPaste());
+        settings.setVisible(root.hasSettings());
+
+        // TODO: For some reason menu is ignoring this being set
+        // to never in activity.xml. File a bug.
+        settings.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
 
         Menus.disableHiddenItems(menu, pasteFromCb);
         return true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 12c0b8f..3f14a55 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -176,6 +176,7 @@
         } else if (isExternalStorage()) {
             derivedIcon = R.drawable.ic_root_smartphone;
             derivedType = TYPE_LOCAL;
+            // TODO: Apply SD card icon to SD devices.
         } else if (isDownloads()) {
             derivedIcon = R.drawable.ic_root_download;
             derivedType = TYPE_DOWNLOADS;
@@ -244,6 +245,10 @@
                 || derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS;
     }
 
+    public boolean hasSettings() {
+        return (flags & Root.FLAG_HAS_SETTINGS) != 0;
+    }
+
     public Drawable loadIcon(Context context) {
         if (derivedIcon != 0) {
             return context.getDrawable(derivedIcon);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 56e5a9b..f89934d 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -134,9 +134,6 @@
             final String rootId;
             final String title;
             if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
-                // save off the primary volume for subsequent "Home" dir initialization.
-                primaryVolume = volume;
-
                 // We currently only support a single emulated volume mounted at
                 // a time, and it's always considered the primary
                 rootId = ROOT_ID_PRIMARY_EMULATED;
@@ -167,9 +164,14 @@
             mRoots.put(rootId, root);
 
             root.rootId = rootId;
-            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
+            root.flags = Root.FLAG_LOCAL_ONLY
                     | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
 
+            if (volume.isPrimary()) {
+                // save off the primary volume for subsequent "Home" dir initialization.
+                primaryVolume = volume;
+                root.flags |= Root.FLAG_ADVANCED;
+            }
             // Dunno when this would NOT be the case, but never hurts to be correct.
             if (volume.isMountedWritable()) {
                 root.flags |= Root.FLAG_SUPPORTS_CREATE;
diff --git a/packages/PrintSpooler/res/values-ca/arrays.xml b/packages/PrintSpooler/res/values-ca/arrays.xml
deleted file mode 100644
index c1b149c..0000000
--- a/packages/PrintSpooler/res/values-ca/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-<resources>
-
-    <string-array name="pdf_printer_media_sizes" translatable="false">
-        <item>NA_LETTER</item>
-        <item>NA_GOVT_LETTER</item>
-        <item>NA_LEGAL</item>
-        <item>NA_JUNIOR_LEGAL</item>
-        <item>NA_LEDGER</item>
-        <item>NA_TABLOID</item>
-        <item>NA_INDEX_3X5</item>
-        <item>NA_INDEX_4X6</item>
-        <item>NA_INDEX_5X8</item>
-        <item>NA_MONARCH</item>
-        <item>NA_QUARTO</item>
-        <item>NA_FOOLSCAP</item>
-    </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-en-rCA/arrays.xml b/packages/PrintSpooler/res/values-en-rCA/arrays.xml
deleted file mode 100644
index d40278c..0000000
--- a/packages/PrintSpooler/res/values-en-rCA/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<resources>
-
-    <string-array name="pdf_printer_media_sizes" translatable="false">
-        <item>NA_LETTER</item>
-        <item>NA_GOVT_LETTER</item>
-        <item>NA_LEGAL</item>
-        <item>NA_JUNIOR_LEGAL</item>
-        <item>NA_LEDGER</item>
-        <item>NA_TABLOID</item>
-        <item>NA_INDEX_3X5</item>
-        <item>NA_INDEX_4X6</item>
-        <item>NA_INDEX_5X8</item>
-        <item>NA_MONARCH</item>
-        <item>NA_QUARTO</item>
-        <item>NA_FOOLSCAP</item>
-    </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-en-rUS/arrays.xml b/packages/PrintSpooler/res/values-en-rUS/arrays.xml
deleted file mode 100644
index d40278c..0000000
--- a/packages/PrintSpooler/res/values-en-rUS/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<resources>
-
-    <string-array name="pdf_printer_media_sizes" translatable="false">
-        <item>NA_LETTER</item>
-        <item>NA_GOVT_LETTER</item>
-        <item>NA_LEGAL</item>
-        <item>NA_JUNIOR_LEGAL</item>
-        <item>NA_LEDGER</item>
-        <item>NA_TABLOID</item>
-        <item>NA_INDEX_3X5</item>
-        <item>NA_INDEX_4X6</item>
-        <item>NA_INDEX_5X8</item>
-        <item>NA_MONARCH</item>
-        <item>NA_QUARTO</item>
-        <item>NA_FOOLSCAP</item>
-    </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-es-rUS/arrays.xml b/packages/PrintSpooler/res/values-es-rUS/arrays.xml
deleted file mode 100644
index c1b149c..0000000
--- a/packages/PrintSpooler/res/values-es-rUS/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-<resources>
-
-    <string-array name="pdf_printer_media_sizes" translatable="false">
-        <item>NA_LETTER</item>
-        <item>NA_GOVT_LETTER</item>
-        <item>NA_LEGAL</item>
-        <item>NA_JUNIOR_LEGAL</item>
-        <item>NA_LEDGER</item>
-        <item>NA_TABLOID</item>
-        <item>NA_INDEX_3X5</item>
-        <item>NA_INDEX_4X6</item>
-        <item>NA_INDEX_5X8</item>
-        <item>NA_MONARCH</item>
-        <item>NA_QUARTO</item>
-        <item>NA_FOOLSCAP</item>
-    </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml
deleted file mode 100644
index 3187cbe..0000000
--- a/packages/PrintSpooler/res/values-ja/arrays.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<resources>
-
-    <string-array name="pdf_printer_media_sizes" translatable="false">
-        <item>JIS_B10</item>
-        <item>JIS_B9</item>
-        <item>JIS_B8</item>
-        <item>JIS_B7</item>
-        <item>JIS_B6</item>
-        <item>JIS_B5</item>
-        <item>JIS_B4</item>
-        <item>JIS_B3</item>
-        <item>JIS_B2</item>
-        <item>JIS_B1</item>
-        <item>JIS_B0</item>
-        <item>JIS_EXEC</item>
-        <item>JPN_CHOU4</item>
-        <item>JPN_CHOU3</item>
-        <item>JPN_CHOU2</item>
-        <item>JPN_HAGAKI</item>
-        <item>JPN_OUFUKU</item>
-        <item>JPN_KAHU</item>
-        <item>JPN_KAKU2</item>
-        <item>JPN_YOU4</item>
-    </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values/arrays.xml b/packages/PrintSpooler/res/values/arrays.xml
index afe3c71..8658be4 100644
--- a/packages/PrintSpooler/res/values/arrays.xml
+++ b/packages/PrintSpooler/res/values/arrays.xml
@@ -121,38 +121,4 @@
         <!-- Everything else is ISO -->
     </string-array>
 
-    <string-array name="pdf_printer_media_sizes" translatable="false">
-        <item>ISO_A0</item>
-        <item>ISO_A1</item>
-        <item>ISO_A2</item>
-        <item>ISO_A3</item>
-        <item>ISO_A4</item>
-        <item>ISO_A5</item>
-        <item>ISO_A6</item>
-        <item>ISO_A7</item>
-        <item>ISO_A8</item>
-        <item>ISO_A9</item>
-        <item>ISO_A10</item>
-        <item>ISO_B1</item>
-        <item>ISO_B2</item>
-        <item>ISO_B3</item>
-        <item>ISO_B4</item>
-        <item>ISO_B5</item>
-        <item>ISO_B6</item>
-        <item>ISO_B7</item>
-        <item>ISO_B8</item>
-        <item>ISO_B9</item>
-        <item>ISO_B10</item>
-        <item>ISO_C1</item>
-        <item>ISO_C2</item>
-        <item>ISO_C3</item>
-        <item>ISO_C4</item>
-        <item>ISO_C5</item>
-        <item>ISO_C6</item>
-        <item>ISO_C7</item>
-        <item>ISO_C8</item>
-        <item>ISO_C9</item>
-        <item>ISO_C10</item>
-    </string-array>
-
 </resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 1e79121..a1ea658 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -61,6 +61,7 @@
 import android.text.TextUtils.SimpleStringSplitter;
 import android.text.TextWatcher;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.KeyEvent;
@@ -2361,6 +2362,7 @@
         }
 
         private PrinterInfo createFakePdfPrinter() {
+            ArraySet<MediaSize> allMediaSizes = MediaSize.getAllPredefinedSizes();
             MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
 
             PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
@@ -2368,11 +2370,9 @@
             PrinterCapabilitiesInfo.Builder builder =
                     new PrinterCapabilitiesInfo.Builder(printerId);
 
-            String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes);
-            final int mediaSizeIdCount = mediaSizeIds.length;
-            for (int i = 0; i < mediaSizeIdCount; i++) {
-                String id = mediaSizeIds[i];
-                MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
+            final int mediaSizeCount = allMediaSizes.size();
+            for (int i = 0; i < mediaSizeCount; i++) {
+                MediaSize mediaSize = allMediaSizes.valueAt(i);
                 builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
             }
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 4e3932f..4f7624a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -546,7 +546,10 @@
                         final int printerCount = mPrinters.size();
                         for (int i = 0; i < printerCount; i++) {
                             PrinterInfo printer = mPrinters.get(i);
-                            if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
+                            String description = printer.getDescription();
+                            if (printer.getName().toLowerCase().contains(constraintLowerCase)
+                                    || description != null && description.toLowerCase()
+                                            .contains(constraintLowerCase)) {
                                 filteredPrinters.add(printer);
                             }
                         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 56c4edb..5ffa581 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -141,6 +141,16 @@
         mCategoryListeners.remove(listener);
     }
 
+    public void setIsDrawerPresent(boolean isPresent) {
+        if (isPresent) {
+            mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+            updateDrawer();
+        } else {
+            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+            mDrawerLayout = null;
+        }
+    }
+
     public void openDrawer() {
         if (mDrawerLayout != null) {
             mDrawerLayout.openDrawer(Gravity.START);
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ebe0d97..e8df01b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -159,18 +159,6 @@
          in from the bottom of the screen. -->
     <integer name="recents_enter_from_home_transition_duration">100</integer>
 
-    <!-- The duration for animating the task from the bottom of the screen when transitioning
-     from home. -->
-    <integer name="recents_task_enter_from_home_duration">225</integer>
-
-    <!-- The stagger for each task when animating the task from the bottom of the screen when 
-     transitioning from home. -->
-    <integer name="recents_task_enter_from_home_stagger_delay">12</integer>
-
-    <!-- The duration of the animation of the tasks to the bottom of the screen when leaving
-     Recents to go back to the Launcher. -->
-    <integer name="recents_task_exit_to_home_duration">225</integer>
-
     <!-- The min animation duration for animating the nav bar scrim in. -->
     <integer name="recents_nav_bar_scrim_enter_duration">400</integer>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index e288878..ee3eb02 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -39,7 +39,7 @@
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
 
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -224,7 +224,7 @@
             if (row.getViewType() == TASK_ROW_VIEW_TYPE) {
                 TaskRow taskRow = (TaskRow) row;
                 Task task = taskRow.task;
-                mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
+                mStack.removeTask(task, AnimationProps.IMMEDIATE);
                 EventBus.getDefault().send(new DeleteTaskDataEvent(task));
                 i = removeTaskRow(i);
             }
@@ -304,7 +304,7 @@
     public void onTaskRemoved(Task task, int position) {
         // Since this is removed from the history, we need to update the stack as well to ensure
         // that the model is correct. Since the stack is hidden, we can update it immediately.
-        mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
+        mStack.removeTask(task, AnimationProps.IMMEDIATE);
         removeTaskRow(position);
         if (mRows.isEmpty()) {
             dismissHistory();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index aa8efa7..1f91dce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -43,7 +43,7 @@
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.views.DropTarget;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -228,12 +228,12 @@
          * Notifies when a task has been removed from the stack.
          */
         void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
-            Task newFrontMostTask, TaskViewAnimation animation);
+            Task newFrontMostTask, AnimationProps animation);
 
         /**
          * Notifies when a task has been removed from the history.
          */
-        void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation);
+        void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation);
     }
 
     /**
@@ -531,7 +531,7 @@
      * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
      * how they should update themselves.
      */
-    public void removeTask(Task t, TaskViewAnimation animation) {
+    public void removeTask(Task t, AnimationProps animation) {
         if (mStackTaskList.contains(t)) {
             boolean wasFrontMostTask = (getStackFrontMostTask(false /* includeFreeform */) == t);
             removeTaskImpl(mStackTaskList, t);
@@ -575,7 +575,7 @@
             if (!newTasksMap.containsKey(task.key)) {
                 if (notifyStackChanges) {
                     mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null,
-                            TaskViewAnimation.IMMEDIATE);
+                            AnimationProps.IMMEDIATE);
                 }
             }
             task.setGroup(null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
index 2d41742..58ec852 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -28,7 +28,7 @@
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.recents.model.TaskStack.TaskStackCallbacks;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -137,7 +137,7 @@
 
     @Override
     public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
-            Task newFrontMostTask, TaskViewAnimation animation) {
+            Task newFrontMostTask, AnimationProps animation) {
         getAdapter().notifyItemRemoved(stack.getStackTasks().indexOf(removedTask));
         if (mFocusedTask == removedTask) {
             resetFocusedTask(removedTask);
@@ -152,7 +152,7 @@
     }
 
     @Override
-    public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation) {
+    public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation) {
         //No history task on tv
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
new file mode 100644
index 0000000..93878c52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.annotation.IntDef;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * The generic set of animation properties to animate a {@link View}. The animation can have
+ * different interpolators, start delays and durations for each of the different properties.
+ */
+public class AnimationProps {
+
+    public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR);
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
+    public @interface PropType {}
+
+    public static final int ALL = 0;
+    public static final int TRANSLATION_X = 1;
+    public static final int TRANSLATION_Y = 2;
+    public static final int TRANSLATION_Z = 3;
+    public static final int ALPHA = 4;
+    public static final int SCALE = 5;
+    public static final int BOUNDS = 6;
+
+    private SparseLongArray mPropStartDelay;
+    private SparseLongArray mPropDuration;
+    private SparseArray<Interpolator> mPropInterpolators;
+    private Animator.AnimatorListener mListener;
+
+    /**
+     * The builder constructor.
+     */
+    public AnimationProps() {}
+
+    /**
+     * Creates an animation with a default {@param duration} and {@param interpolator} for all
+     * properties in this animation.
+     */
+    public AnimationProps(int duration, Interpolator interpolator) {
+        this(0, duration, interpolator, null);
+    }
+
+    /**
+     * Creates an animation with a default {@param duration} and {@param interpolator} for all
+     * properties in this animation.
+     */
+    public AnimationProps(int duration, Interpolator interpolator,
+            Animator.AnimatorListener listener) {
+        this(0, duration, interpolator, listener);
+    }
+
+    /**
+     * Creates an animation with a default {@param startDelay}, {@param duration} and
+     * {@param interpolator} for all properties in this animation.
+     */
+    public AnimationProps(int startDelay, int duration, Interpolator interpolator) {
+        this(startDelay, duration, interpolator, null);
+    }
+
+    /**
+     * Creates an animation with a default {@param startDelay}, {@param duration} and
+     * {@param interpolator} for all properties in this animation.
+     */
+    public AnimationProps(int startDelay, int duration, Interpolator interpolator,
+            Animator.AnimatorListener listener) {
+        setStartDelay(ALL, startDelay);
+        setDuration(ALL, duration);
+        setInterpolator(ALL, interpolator);
+        setListener(listener);
+    }
+
+    /**
+     * Creates a new {@link AnimatorSet} that will animate the given animators.  Callers need to
+     * manually apply the individual animation properties for each of the animators respectively.
+     */
+    public AnimatorSet createAnimator(List<Animator> animators) {
+        AnimatorSet anim = new AnimatorSet();
+        if (mListener != null) {
+            anim.addListener(mListener);
+        }
+        anim.playTogether(animators);
+        return anim;
+    }
+
+    /**
+     * Applies the specific start delay, duration and interpolator to the given {@param animator}
+     * for the specified {@param propertyType}.
+     */
+    public <T extends Animator> T apply(@PropType int propertyType, T animator) {
+        animator.setStartDelay(getStartDelay(propertyType));
+        animator.setDuration(getDuration(propertyType));
+        animator.setInterpolator(getInterpolator(propertyType));
+        return animator;
+    }
+
+    /**
+     * Sets a start delay for a specific property.
+     */
+    public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) {
+        if (mPropStartDelay == null) {
+            mPropStartDelay = new SparseLongArray();
+        }
+        mPropStartDelay.append(propertyType, startDelay);
+        return this;
+    }
+
+    /**
+     * Returns the start delay for a specific property.
+     */
+    public long getStartDelay(@PropType int propertyType) {
+        if (mPropStartDelay != null) {
+            long startDelay = mPropStartDelay.get(propertyType, -1);
+            if (startDelay != -1) {
+                return startDelay;
+            }
+            return mPropStartDelay.get(ALL, 0);
+        }
+        return 0;
+    }
+
+    /**
+     * Sets a duration for a specific property.
+     */
+    public AnimationProps setDuration(@PropType int propertyType, int duration) {
+        if (mPropDuration == null) {
+            mPropDuration = new SparseLongArray();
+        }
+        mPropDuration.append(propertyType, duration);
+        return this;
+    }
+
+    /**
+     * Returns the duration for a specific property.
+     */
+    public long getDuration(@PropType int propertyType) {
+        if (mPropDuration != null) {
+            long duration = mPropDuration.get(propertyType, -1);
+            if (duration != -1) {
+                return duration;
+            }
+            return mPropDuration.get(ALL, 0);
+        }
+        return 0;
+    }
+
+    /**
+     * Sets an interpolator for a specific property.
+     */
+    public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) {
+        if (mPropInterpolators == null) {
+            mPropInterpolators = new SparseArray<>();
+        }
+        mPropInterpolators.append(propertyType, interpolator);
+        return this;
+    }
+
+    /**
+     * Returns the interpolator for a specific property, falling back to the general interpolator
+     * if there is no specific property interpolator.
+     */
+    public Interpolator getInterpolator(@PropType int propertyType) {
+        if (mPropInterpolators != null) {
+            Interpolator interp = mPropInterpolators.get(propertyType);
+            if (interp != null) {
+                return interp;
+            }
+            return mPropInterpolators.get(ALL, Interpolators.LINEAR);
+        }
+        return Interpolators.LINEAR;
+    }
+
+    /**
+     * Sets an animator listener for this animation.
+     */
+    public AnimationProps setListener(Animator.AnimatorListener listener) {
+        mListener = listener;
+        return this;
+    }
+
+    /**
+     * Returns the animator listener for this animation.
+     */
+    public Animator.AnimatorListener getListener() {
+        return mListener;
+    }
+
+    /**
+     * Returns whether this animation has any duration.
+     */
+    public boolean isImmediate() {
+        int count = mPropDuration.size();
+        for (int i = 0; i < count; i++) {
+            if (mPropDuration.valueAt(i) > 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index e2ff52c..5e113b9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -497,8 +497,9 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        super.dispatchDraw(canvas);
+    public void onDrawForeground(Canvas canvas) {
+        super.onDrawForeground(canvas);
+
         ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
         for (int i = visDockStates.size() - 1; i >= 0; i--) {
             Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
@@ -530,8 +531,7 @@
 
     public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
         // Hide the history button
-        int taskViewExitToHomeDuration = getResources().getInteger(
-                R.integer.recents_task_exit_to_home_duration);
+        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
         hideHistoryButton(taskViewExitToHomeDuration, false /* translate */);
         animateBackgroundScrim(0f, taskViewExitToHomeDuration);
     }
@@ -588,7 +588,7 @@
             tmpTransform.scale = 1f;
             tmpTransform.rect.set(taskViewRect);
             mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform,
-                    new TaskViewAnimation(125, Interpolators.ALPHA_OUT,
+                    new AnimationProps(125, Interpolators.ALPHA_OUT,
                             new AnimatorListenerAdapter() {
                                 @Override
                                 public void onAnimationEnd(Animator animation) {
@@ -598,7 +598,7 @@
                                             event.task.key.id, dockState.createMode);
 
                                     // Animate the stack accordingly
-                                    TaskViewAnimation stackAnim = new TaskViewAnimation(
+                                    AnimationProps stackAnim = new AnimationProps(
                                             TaskStackView.DEFAULT_SYNC_STACK_DURATION,
                                             Interpolators.FAST_OUT_SLOW_IN);
                                     mTaskStackView.getStack().removeTask(event.task, stackAnim);
@@ -646,9 +646,8 @@
     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         if (!launchState.launchedFromAppWithThumbnail && mStack.getTaskCount() > 0) {
-            int taskViewEnterFromHomeDuration = getResources().getInteger(
-                    R.integer.recents_task_enter_from_home_duration);
-            animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, taskViewEnterFromHomeDuration);
+            animateBackgroundScrim(DEFAULT_SCRIM_ALPHA,
+                    TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 682c298..0eae183 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -20,8 +20,10 @@
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Path;
 import android.graphics.RectF;
 import android.view.View;
+import android.view.animation.PathInterpolator;
 
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -63,6 +65,22 @@
                 ReferenceCountedTrigger postAnimationTrigger);
     }
 
+    private static final int FRAME_OFFSET_MS = 16;
+
+    public static final int ENTER_FROM_HOME_ALPHA_DURATION = 100;
+    public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 333;
+    private static final PathInterpolator ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR =
+            new PathInterpolator(0, 0, 0, 1f);
+    private static final PathInterpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR =
+            new PathInterpolator(0, 0, 0.2f, 1f);
+
+    public static final int EXIT_TO_HOME_ALPHA_DURATION = 100;
+    public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 150;
+    private static final PathInterpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR =
+            new PathInterpolator(0.8f, 0, 0.6f, 1f);
+    private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR =
+            new PathInterpolator(0.4f, 0, 1f, 1f);
+
     private TaskStackView mStackView;
 
     private TaskViewTransform mTmpTransform = new TaskViewTransform();
@@ -157,15 +175,12 @@
                 R.integer.recents_task_enter_from_app_duration);
         int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
                 R.integer.recents_task_enter_from_affiliated_app_duration);
-        int taskViewEnterFromHomeDuration = res.getInteger(
-                R.integer.recents_task_enter_from_home_duration);
-        int taskViewEnterFromHomeStaggerDelay = res.getInteger(
-                R.integer.recents_task_enter_from_home_stagger_delay);
 
         // Create enter animations for each of the views from front to back
         List<TaskView> taskViews = mStackView.getTaskViews();
         int taskViewCount = taskViews.size();
         for (int i = taskViewCount - 1; i >= 0; i--) {
+            int taskIndexFromFront = taskViewCount - i - 1;
             final TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
             boolean currentTaskOccludesLaunchTarget = false;
@@ -186,7 +201,7 @@
                 } else {
                     // Animate the task up if it was occluding the launch target
                     if (currentTaskOccludesLaunchTarget) {
-                        TaskViewAnimation taskAnimation = new TaskViewAnimation(
+                        AnimationProps taskAnimation = new AnimationProps(
                                 taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
                                 new AnimatorListenerAdapter() {
                                     @Override
@@ -202,14 +217,16 @@
 
             } else if (launchState.launchedFromHome) {
                 // Animate the tasks up
-                int frontIndex = (taskViewCount - i - 1);
-                int delay = frontIndex * taskViewEnterFromHomeStaggerDelay;
-                int duration = taskViewEnterFromHomeDuration +
-                        frontIndex * taskViewEnterFromHomeStaggerDelay;
-
-                TaskViewAnimation taskAnimation = new TaskViewAnimation(delay,
-                        duration, Interpolators.DECELERATE_QUINT,
-                        postAnimationTrigger.decrementOnAnimationEnd());
+                AnimationProps taskAnimation = new AnimationProps()
+                        .setStartDelay(AnimationProps.ALPHA, taskIndexFromFront * FRAME_OFFSET_MS)
+                        .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION)
+                        .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION -
+                                (taskIndexFromFront * FRAME_OFFSET_MS))
+                        .setInterpolator(AnimationProps.BOUNDS,
+                                ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR)
+                        .setInterpolator(AnimationProps.ALPHA,
+                                ENTER_FROM_HOME_ALPHA_INTERPOLATOR)
+                        .setListener(postAnimationTrigger.decrementOnAnimationEnd());
                 postAnimationTrigger.increment();
                 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
             }
@@ -221,7 +238,6 @@
      */
     public void startExitToHomeAnimation(boolean animated,
             ReferenceCountedTrigger postAnimationTrigger) {
-        Resources res = mStackView.getResources();
         TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
         TaskStackViewScroller stackScroller = mStackView.getScroller();
         TaskStack stack = mStackView.getStack();
@@ -232,19 +248,32 @@
         }
 
         int offscreenY = stackLayout.mStackRect.bottom;
-        int taskViewExitToHomeDuration = res.getInteger(
-                R.integer.recents_task_exit_to_home_duration);
 
         // Create the animations for each of the tasks
         List<TaskView> taskViews = mStackView.getTaskViews();
         int taskViewCount = taskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
+            int taskIndexFromFront = taskViewCount - i - 1;
             TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            TaskViewAnimation taskAnimation = new TaskViewAnimation(
-                    animated ? taskViewExitToHomeDuration : 0, Interpolators.FAST_OUT_LINEAR_IN,
-                    postAnimationTrigger.decrementOnAnimationEnd());
-            postAnimationTrigger.increment();
+
+            // Animate the tasks down
+            AnimationProps taskAnimation;
+            if (animated) {
+                taskAnimation = new AnimationProps()
+                        .setStartDelay(AnimationProps.ALPHA, i * FRAME_OFFSET_MS)
+                        .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_ALPHA_DURATION)
+                        .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION +
+                                (taskIndexFromFront * FRAME_OFFSET_MS))
+                        .setInterpolator(AnimationProps.BOUNDS,
+                                EXIT_TO_HOME_TRANSLATION_INTERPOLATOR)
+                        .setInterpolator(AnimationProps.ALPHA,
+                                EXIT_TO_HOME_ALPHA_INTERPOLATOR)
+                        .setListener(postAnimationTrigger.decrementOnAnimationEnd());
+                postAnimationTrigger.increment();
+            } else {
+                taskAnimation = AnimationProps.IMMEDIATE;
+            }
 
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
                     null);
@@ -283,7 +312,7 @@
                         screenPinningRequested, postAnimationTrigger);
             } else if (currentTaskOccludesLaunchTarget) {
                 // Animate this task out of view
-                TaskViewAnimation taskAnimation = new TaskViewAnimation(
+                AnimationProps taskAnimation = new AnimationProps(
                         taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
                         postAnimationTrigger.decrementOnAnimationEnd());
                 postAnimationTrigger.increment();
@@ -315,7 +344,7 @@
         deleteTaskView.setClipViewInStack(false);
 
         // Compose the new animation and transform and star the animation
-        TaskViewAnimation taskAnimation = new TaskViewAnimation(taskViewRemoveAnimDuration,
+        AnimationProps taskAnimation = new AnimationProps(taskViewRemoveAnimDuration,
                 Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -352,7 +381,7 @@
         for (int i = taskViewCount - 1; i >= 0; i--) {
             TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i,
+            AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i,
                     historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN,
                     postAnimationTrigger.decrementOnAnimationEnd());
             postAnimationTrigger.increment();
@@ -381,7 +410,7 @@
         int taskViewCount = taskViews.size();
         for (int i = taskViewCount - 1; i >= 0; i--) {
             TaskView tv = taskViews.get(i);
-            TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i,
+            AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i,
                     historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN);
             stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(),
                     mTmpTransform, null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 232b416..1c97b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.recents.views;
 
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
@@ -34,9 +38,9 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
 import com.android.systemui.Interpolators;
@@ -85,10 +89,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 
 /* The visual representation of a task stack view */
 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
@@ -124,7 +124,7 @@
     ArrayList<TaskView> mTaskViews = new ArrayList<>();
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
     ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
-    TaskViewAnimation mDeferredTaskViewLayoutAnimation = null;
+    AnimationProps mDeferredTaskViewLayoutAnimation = null;
 
     DozeTrigger mUIDozeTrigger;
     Task mFocusedTask;
@@ -135,6 +135,7 @@
 
     boolean mTaskViewsClipDirty = true;
     boolean mAwaitingFirstLayout = true;
+    boolean mInMeasureLayout = false;
     boolean mEnterAnimationComplete = false;
     boolean mTouchExplorationEnabled;
     boolean mScreenPinningEnabled;
@@ -537,17 +538,17 @@
             if (tv == null) {
                 tv = mViewPool.pickUpViewFromPool(task, task);
                 if (task.isFreeformTask()) {
-                    tv.updateViewPropertiesToTaskTransform(transform, TaskViewAnimation.IMMEDIATE,
+                    tv.updateViewPropertiesToTaskTransform(transform, AnimationProps.IMMEDIATE,
                             mRequestUpdateClippingListener);
                 } else {
                     if (Float.compare(transform.p, 0f) <= 0) {
                         tv.updateViewPropertiesToTaskTransform(
                                 mLayoutAlgorithm.getBackOfStackTransform(),
-                                TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener);
+                                AnimationProps.IMMEDIATE, mRequestUpdateClippingListener);
                     } else {
                         tv.updateViewPropertiesToTaskTransform(
                                 mLayoutAlgorithm.getFrontOfStackTransform(),
-                                TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener);
+                                AnimationProps.IMMEDIATE, mRequestUpdateClippingListener);
                     }
                 }
             } else {
@@ -580,9 +581,9 @@
      * animations that are current running on those task views, and will ensure that the children
      * {@link TaskView}s will match the set of visible tasks in the stack.
      *
-     * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>)
+     * @see #relayoutTaskViews(AnimationProps, ArraySet<Task.TaskKey>)
      */
-    void relayoutTaskViews(TaskViewAnimation animation) {
+    void relayoutTaskViews(AnimationProps animation) {
         relayoutTaskViews(animation, mIgnoreTasks);
     }
 
@@ -594,7 +595,7 @@
      *
      * @param ignoreTasksSet the set of tasks to ignore in the relayout
      */
-    void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
+    void relayoutTaskViews(AnimationProps animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
         // If we had a deferred animation, cancel that
         mDeferredTaskViewLayoutAnimation = null;
 
@@ -623,17 +624,17 @@
     /**
      * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
      */
-    void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) {
+    void relayoutTaskViewsOnNextFrame(AnimationProps animation) {
         mDeferredTaskViewLayoutAnimation = animation;
         invalidate();
     }
 
     /**
      * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a
-     * given set of {@link TaskViewAnimation} properties.
+     * given set of {@link AnimationProps} properties.
      */
     public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform,
-            TaskViewAnimation animation) {
+            AnimationProps animation) {
         taskView.updateViewPropertiesToTaskTransform(transform, animation,
                 mRequestUpdateClippingListener);
     }
@@ -1137,6 +1138,7 @@
      */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mInMeasureLayout = true;
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
@@ -1159,22 +1161,29 @@
         mTmpTaskViews.addAll(mViewPool.getViews());
         int taskViewCount = mTmpTaskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = mTmpTaskViews.get(i);
-            if (tv.getBackground() != null) {
-                tv.getBackground().getPadding(mTmpRect);
-            } else {
-                mTmpRect.setEmpty();
-            }
-            tv.measure(
-                    MeasureSpec.makeMeasureSpec(
-                            mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
-                            MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(
-                            mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
-                            MeasureSpec.EXACTLY));
+            measureTaskView(mTmpTaskViews.get(i));
         }
 
         setMeasuredDimension(width, height);
+        mInMeasureLayout = false;
+    }
+
+    /**
+     * Measures a TaskView.
+     */
+    private void measureTaskView(TaskView tv) {
+        if (tv.getBackground() != null) {
+            tv.getBackground().getPadding(mTmpRect);
+        } else {
+            mTmpRect.setEmpty();
+        }
+        tv.measure(
+                MeasureSpec.makeMeasureSpec(
+                        mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
+                        MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(
+                        mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
+                        MeasureSpec.EXACTLY));
     }
 
     /**
@@ -1190,15 +1199,7 @@
         mTmpTaskViews.addAll(mViewPool.getViews());
         int taskViewCount = mTmpTaskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = mTmpTaskViews.get(i);
-            if (tv.getBackground() != null) {
-                tv.getBackground().getPadding(mTmpRect);
-            } else {
-                mTmpRect.setEmpty();
-            }
-            Rect taskRect = mLayoutAlgorithm.mTaskRect;
-            tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
-                    taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
+            layoutTaskView(mTmpTaskViews.get(i));
         }
 
         if (changed) {
@@ -1207,29 +1208,41 @@
             }
         }
         // Relayout all of the task views including the ignored ones
-        relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET);
+        relayoutTaskViews(AnimationProps.IMMEDIATE, EMPTY_TASK_SET);
         clipTaskViews();
 
         if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
             mAwaitingFirstLayout = false;
             onFirstLayout();
-            return;
         }
     }
 
+    /**
+     * Lays out a TaskView.
+     */
+    private void layoutTaskView(TaskView tv) {
+        if (tv.getBackground() != null) {
+            tv.getBackground().getPadding(mTmpRect);
+        } else {
+            mTmpRect.setEmpty();
+        }
+        Rect taskRect = mLayoutAlgorithm.mTaskRect;
+        tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
+                taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
+    }
+
     /** Handler for the first layout. */
     void onFirstLayout() {
         // Setup the view for the enter animation
         mAnimationHelper.prepareForEnterAnimation();
 
         // Animate in the freeform workspace
-        animateFreeformWorkspaceBackgroundAlpha(
-                mLayoutAlgorithm.getStackState().freeformBackgroundAlpha, 150,
-                Interpolators.FAST_OUT_SLOW_IN);
+        int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
+        animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
+                Interpolators.FAST_OUT_SLOW_IN));
 
         // Set the task focused state without requesting view focus, and leave the focus animations
         // until after the enter-animation
-        Task launchTask = mStack.getLaunchTarget();
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
@@ -1274,7 +1287,9 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
         // Draw the freeform workspace background
         SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.hasFreeformWorkspaceSupport()) {
@@ -1282,8 +1297,6 @@
                 mFreeformWorkspaceBackground.draw(canvas);
             }
         }
-
-        super.dispatchDraw(canvas);
     }
 
     @Override
@@ -1318,7 +1331,7 @@
         updateLayoutAlgorithm(true /* boundScroll */);
 
         // Animate all the tasks into place
-        relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+        relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
                 Interpolators.FAST_OUT_SLOW_IN));
     }
 
@@ -1327,7 +1340,7 @@
      */
     @Override
     public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
-            Task newFrontMostTask, TaskViewAnimation animation) {
+            Task newFrontMostTask, AnimationProps animation) {
         if (mFocusedTask == removedTask) {
             resetFocusedTask(removedTask);
         }
@@ -1364,7 +1377,7 @@
 
     @Override
     public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
-            TaskViewAnimation animation) {
+            AnimationProps animation) {
         // To be implemented
     }
 
@@ -1404,7 +1417,21 @@
 
         // Add/attach the view to the hierarchy
         if (isNewView) {
-            addView(tv, insertIndex);
+            if (mInMeasureLayout) {
+                // If we are measuring the layout, then just add the view normally as it will be
+                // laid out during the layout pass
+                addView(tv, insertIndex);
+            } else {
+                // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout
+                // pass, and we should layout the new child ourselves
+                ViewGroup.LayoutParams params = tv.getLayoutParams();
+                if (params == null) {
+                    params = generateDefaultLayoutParams();
+                }
+                addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */);
+                measureTaskView(tv);
+                layoutTaskView(tv);
+            }
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
         }
@@ -1463,14 +1490,14 @@
     public void onFocusStateChanged(float prevFocusState, float curFocusState) {
         if (mDeferredTaskViewLayoutAnimation == null) {
             mUIDozeTrigger.poke();
-            relayoutTaskViewsOnNextFrame(TaskViewAnimation.IMMEDIATE);
+            relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE);
         }
     }
 
     /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
 
     @Override
-    public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) {
+    public void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
         mUIDozeTrigger.poke();
         if (animation != null) {
             relayoutTaskViewsOnNextFrame(animation);
@@ -1506,7 +1533,7 @@
                     tv.dismissTask();
                 } else {
                     // Otherwise, remove the task from the stack immediately
-                    mStack.removeTask(t, TaskViewAnimation.IMMEDIATE);
+                    mStack.removeTask(t, AnimationProps.IMMEDIATE);
                 }
             }
         }
@@ -1531,10 +1558,9 @@
         mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
 
         // Dismiss the freeform workspace background
-        int taskViewExitToHomeDuration = getResources().getInteger(
-                R.integer.recents_task_exit_to_home_duration);
-        animateFreeformWorkspaceBackgroundAlpha(0, taskViewExitToHomeDuration,
-                Interpolators.FAST_OUT_SLOW_IN);
+        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
+        animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
+                Interpolators.FAST_OUT_SLOW_IN));
     }
 
     public final void onBusEvent(DismissFocusedTaskViewEvent event) {
@@ -1587,6 +1613,9 @@
     }
 
     public final void onBusEvent(DragStartEvent event) {
+        // Ensure that the drag task is not animated
+        addIgnoreTask(event.task);
+
         if (event.task.isFreeformTask()) {
             // Animate to the front of the stack
             mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
@@ -1599,7 +1628,7 @@
         mTmpTransform.scale = finalScale;
         mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
         updateTaskViewToTransform(event.taskView, mTmpTransform,
-                new TaskViewAnimation(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
+                new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
     }
 
     public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
@@ -1611,7 +1640,7 @@
     }
 
     public final void onBusEvent(DragDropTargetChangedEvent event) {
-        TaskViewAnimation animation = new TaskViewAnimation(250, Interpolators.FAST_OUT_SLOW_IN);
+        AnimationProps animation = new AnimationProps(250, Interpolators.FAST_OUT_SLOW_IN);
         if (event.dropTarget instanceof TaskStack.DockState) {
             // Calculate the new task stack bounds that matches the window size that Recents will
             // have after the drop
@@ -1683,10 +1712,10 @@
         mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
                 mTmpTransform, null);
         event.getAnimationTrigger().increment();
-        relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+        relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
                 Interpolators.FAST_OUT_SLOW_IN));
         updateTaskViewToTransform(event.taskView, mTmpTransform,
-                new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
+                new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
                         event.getAnimationTrigger().decrementOnAnimationEnd()));
         removeIgnoreTask(event.task);
     }
@@ -1797,15 +1826,15 @@
                 R.string.accessibility_recents_item_dismissed, task.title));
 
         // Remove the task from the stack
-        mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+        mStack.removeTask(task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
                 Interpolators.FAST_OUT_SLOW_IN));
     }
 
     /**
      * Starts an alpha animation on the freeform workspace background.
      */
-    private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, int duration,
-            Interpolator interpolator) {
+    private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
+            AnimationProps animation) {
         if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
             return;
         }
@@ -1813,8 +1842,12 @@
         Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
         mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
                 Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
-        mFreeformWorkspaceBackgroundAnimator.setDuration(duration);
-        mFreeformWorkspaceBackgroundAnimator.setInterpolator(interpolator);
+        mFreeformWorkspaceBackgroundAnimator.setStartDelay(
+                animation.getDuration(AnimationProps.ALPHA));
+        mFreeformWorkspaceBackgroundAnimator.setDuration(
+                animation.getDuration(AnimationProps.ALPHA));
+        mFreeformWorkspaceBackgroundAnimator.setInterpolator(
+                animation.getInterpolator(AnimationProps.ALPHA));
         mFreeformWorkspaceBackgroundAnimator.start();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index ced5d4b..c641d75 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -38,7 +38,7 @@
     private static final boolean DEBUG = false;
 
     public interface TaskStackViewScrollerCallbacks {
-        void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation);
+        void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation);
     }
 
     /**
@@ -93,14 +93,14 @@
      * Sets the current stack scroll immediately.
      */
     public void setStackScroll(float s) {
-        setStackScroll(s, TaskViewAnimation.IMMEDIATE);
+        setStackScroll(s, AnimationProps.IMMEDIATE);
     }
 
     /**
      * Sets the current stack scroll, but indicates to the callback the preferred animation to
      * update to this new scroll.
      */
-    public void setStackScroll(float s, TaskViewAnimation animation) {
+    public void setStackScroll(float s, AnimationProps animation) {
         float prevStackScroll = mStackScrollP;
         mStackScrollP = s;
         if (mCb != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index da99956..b8b5068 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -546,7 +546,7 @@
             mTmpTransform.translationZ = fromTransform.translationZ +
                     (toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
 
-            mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE);
+            mSv.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 853f868..703005f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -29,7 +29,6 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
-import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.IntProperty;
@@ -243,9 +242,9 @@
     }
 
     void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
-            TaskViewAnimation toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
+                                             AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
         RecentsConfiguration config = Recents.getConfiguration();
-        Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
+        cancelTransformAnimation();
 
         // Compose the animations for the transform
         mTmpAnimators.clear();
@@ -255,8 +254,8 @@
                 setTaskProgress(toTransform.p);
             }
             // Manually call back to the animator listener and update callback
-            if (toAnimation.listener != null) {
-                toAnimation.listener.onAnimationEnd(null);
+            if (toAnimation.getListener() != null) {
+                toAnimation.getListener().onAnimationEnd(null);
             }
             if (updateCallback != null) {
                 updateCallback.onAnimationUpdate(null);
@@ -280,7 +279,7 @@
 
     /** Resets this view's properties */
     void resetViewProperties() {
-        Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
+        cancelTransformAnimation();
         setDim(0);
         setVisibility(View.VISIBLE);
         getViewBounds().reset();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
deleted file mode 100644
index 5455042..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.view.animation.Interpolator;
-
-import com.android.systemui.Interpolators;
-
-import java.util.List;
-
-/**
- * The animation properties to animate a {@link TaskView} to a given {@link TaskViewTransform}.
- */
-public class TaskViewAnimation {
-
-    public static final TaskViewAnimation IMMEDIATE = new TaskViewAnimation(0,
-            Interpolators.LINEAR);
-
-    public final int startDelay;
-    public final int duration;
-    public final Interpolator interpolator;
-    public final Animator.AnimatorListener listener;
-
-    public TaskViewAnimation(int duration, Interpolator interpolator) {
-        this(0 /* startDelay */, duration, interpolator, null);
-    }
-
-    public TaskViewAnimation(int duration, Interpolator interpolator,
-            Animator.AnimatorListener listener) {
-        this(0 /* startDelay */, duration, interpolator, listener);
-    }
-
-    public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator) {
-        this(startDelay, duration, interpolator, null);
-    }
-
-    public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator,
-            Animator.AnimatorListener listener) {
-        this.startDelay = startDelay;
-        this.duration = duration;
-        this.interpolator = interpolator;
-        this.listener = listener;
-    }
-
-    /**
-     * Creates a new {@link AnimatorSet} that will animate the given animators with the current
-     * animation properties.
-     */
-    public AnimatorSet createAnimator(List<Animator> animators) {
-        AnimatorSet anim = new AnimatorSet();
-        anim.setStartDelay(startDelay);
-        anim.setDuration(duration);
-        anim.setInterpolator(interpolator);
-        if (listener != null) {
-            anim.addListener(listener);
-        }
-        anim.playTogether(animators);
-        return anim;
-    }
-
-    /**
-     * Returns whether this animation has any duration.
-     */
-    public boolean isImmediate() {
-        return duration <= 0;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 7cde463..c91a833 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -274,8 +274,8 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        super.dispatchDraw(canvas);
+    public void onDrawForeground(Canvas canvas) {
+        super.onDrawForeground(canvas);
 
         // Draw the dim layer with the rounded corners
         canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 85b7c82..32878b0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -154,13 +154,13 @@
      * Applies this transform to a view.
      */
     public void applyToTaskView(TaskView v, ArrayList<Animator> animators,
-            TaskViewAnimation taskAnimation, boolean allowShadows) {
+            AnimationProps animation, boolean allowShadows) {
         // Return early if not visible
         if (!visible) {
             return;
         }
 
-        if (taskAnimation.isImmediate()) {
+        if (animation.isImmediate()) {
             if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
                 v.setTranslationZ(translationZ);
             }
@@ -177,23 +177,27 @@
             }
         } else {
             if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
-                animators.add(ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, v.getTranslationZ(),
-                        translationZ));
+                ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.TRANSLATION_Z,
+                        v.getTranslationZ(), translationZ);
+                animators.add(animation.apply(AnimationProps.TRANSLATION_Z, anim));
             }
             if (hasScaleChangedFrom(v.getScaleX())) {
-                animators.add(ObjectAnimator.ofPropertyValuesHolder(v,
+                ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v,
                         PropertyValuesHolder.ofFloat(View.SCALE_X, v.getScaleX(), scale),
-                        PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale)));
+                        PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale));
+                animators.add(animation.apply(AnimationProps.SCALE, anim));
             }
             if (hasAlphaChangedFrom(v.getAlpha())) {
-                animators.add(ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha));
+                ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha);
+                animators.add(animation.apply(AnimationProps.ALPHA, anim));
             }
             if (hasRectChangedFrom(v)) {
-                animators.add(ObjectAnimator.ofPropertyValuesHolder(v,
+                ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v,
                         PropertyValuesHolder.ofInt(LEFT, v.getLeft(), (int) rect.left),
                         PropertyValuesHolder.ofInt(TOP, v.getTop(), (int) rect.top),
                         PropertyValuesHolder.ofInt(RIGHT, v.getRight(), (int) rect.right),
-                        PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom)));
+                        PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom));
+                animators.add(animation.apply(AnimationProps.BOUNDS, anim));
             }
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
index e3108015..912e8e2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -27,6 +27,8 @@
 import android.util.Slog;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
 
 import com.android.internal.R;
 
@@ -46,7 +48,9 @@
     public interface Listener {
         public void onDoubleTapAndHold(MotionEvent event, int policyFlags);
         public boolean onDoubleTap(MotionEvent event, int policyFlags);
-        public boolean onGesture(int gestureId);
+        public boolean onGestureCompleted(int gestureId);
+        public void onGestureStarted();
+        public void onGestureCancelled(MotionEvent event, int policyFlags);
     }
 
     private final Listener mListener;
@@ -64,6 +68,10 @@
     // Indicates that motion events are being collected to match a gesture.
     private boolean mRecognizingGesture;
 
+    // Indicates that we've collected enough data to be sure it could be a
+    // gesture.
+    private boolean mGestureConfirmed;
+
     // Indicates that motion events from the second pointer are being checked
     // for a double tap.
     private boolean mSecondFingerDoubleTap;
@@ -81,18 +89,41 @@
     // The Y of the previous event.
     private float mPreviousY;
 
+    // The X of the down event.
+    private float mBaseX;
+
+    // The Y of the down event.
+    private float mBaseY;
+
+    // Slop between the first and second tap to be a double tap.
+    private final int mDoubleTapSlop;
+
+    // The scaled velocity above which we detect gestures.
+    private final int mScaledGestureDetectionVelocity;
+
     // Buffer for storing points for gesture detection.
     private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
 
+    // Helper to track gesture velocity.
+    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+
     // The minimal delta between moves to add a gesture point.
     private static final int TOUCH_TOLERANCE = 3;
 
     // The minimal score for accepting a predicted gesture.
     private static final float MIN_PREDICTION_SCORE = 2.0f;
 
+    // The velocity above which we detect gestures.  Expressed in DIPs/Second.
+    private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
+
+    // Constant used to calculate velocity in seconds.
+    private static final int VELOCITY_UNITS_SECONDS = 1000;
+
     AccessibilityGestureDetector(Context context, Listener listener) {
         mListener = listener;
 
+        mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+
         mGestureDetector = new GestureDetector(context, this);
         mGestureDetector.setOnDoubleTapListener(this);
 
@@ -100,9 +131,14 @@
         mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */);
         mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
         mGestureLibrary.load();
+
+        final float density = context.getResources().getDisplayMetrics().density;
+        mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
     }
 
     public boolean onMotionEvent(MotionEvent event, int policyFlags) {
+        mVelocityTracker.addMovement(event);
+
         final float x = event.getX();
         final float y = event.getY();
 
@@ -112,14 +148,48 @@
                 mDoubleTapDetected = false;
                 mSecondFingerDoubleTap = false;
                 mRecognizingGesture = true;
+                mGestureConfirmed = false;
+                mBaseX = x;
+                mBaseY = y;
                 mPreviousX = x;
                 mPreviousY = y;
                 mStrokeBuffer.clear();
                 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+                mVelocityTracker.clear();
+                mVelocityTracker.addMovement(event);
                 break;
 
             case MotionEvent.ACTION_MOVE:
                 if (mRecognizingGesture) {
+                    if (!mGestureConfirmed) {
+                        mVelocityTracker.addMovement(event);
+                        // It is *important* to use the distance traveled by the pointers
+                        // on the screen which may or may not be magnified.
+                        final float deltaX = mBaseX - event.getX(0);
+                        final float deltaY = mBaseY - event.getY(0);
+                        final double moveDelta = Math.hypot(deltaX, deltaY);
+                        // The user has moved enough for us to decide.
+                        if (moveDelta > mDoubleTapSlop) {
+                            // Check whether the user is performing a gesture. We
+                            // detect gestures if the pointer is moving above a
+                            // given velocity.
+                            mVelocityTracker.computeCurrentVelocity(VELOCITY_UNITS_SECONDS);
+                            final float maxAbsVelocity = Math.max(
+                                    Math.abs(mVelocityTracker.getXVelocity(0)),
+                                    Math.abs(mVelocityTracker.getYVelocity(0)));
+                            if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
+                                // We have to perform gesture detection, so
+                                // notify the listener.
+                                mGestureConfirmed = true;
+                                mListener.onGestureStarted();
+                            } else {
+                                // This won't match any gesture, so notify the
+                                // listener.
+                                cancelGesture();
+                                mListener.onGestureCancelled(event, policyFlags);
+                            }
+                        }
+                    }
                     final float dX = Math.abs(x - mPreviousX);
                     final float dY = Math.abs(y - mPreviousY);
                     if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
@@ -134,12 +204,13 @@
                 if (maybeFinishDoubleTap(event, policyFlags)) {
                     return true;
                 }
-                if (mRecognizingGesture) {
+                if (mGestureConfirmed) {
                     mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
 
-                    if (recognizeGesture()) {
-                        return true;
+                    if (!recognizeGesture()) {
+                        mListener.onGestureCancelled(event, policyFlags);
                     }
+                    return true;
                 }
                 break;
 
@@ -167,6 +238,10 @@
                     return true;
                 }
                 break;
+
+            case MotionEvent.ACTION_CANCEL:
+                clear();
+                break;
         }
 
         // If we're detecting taps on the second finger, map events from the
@@ -194,18 +269,13 @@
         mDoubleTapDetected = false;
         mSecondFingerDoubleTap = false;
         cancelGesture();
-        mStrokeBuffer.clear();
+        mVelocityTracker.clear();
     }
 
     public boolean firstTapDetected() {
         return mFirstTapDetected;
     }
 
-    public void cancelGesture() {
-        mRecognizingGesture = false;
-        mStrokeBuffer.clear();
-    }
-
     @Override
     public void onLongPress(MotionEvent e) {
         maybeSendLongPress(e, mPolicyFlags);
@@ -251,6 +321,12 @@
         return mListener.onDoubleTap(event, policyFlags);
     }
 
+    private void cancelGesture() {
+        mRecognizingGesture = false;
+        mGestureConfirmed = false;
+        mStrokeBuffer.clear();
+    }
+
     private boolean recognizeGesture() {
         Gesture gesture = new Gesture();
         gesture.addStroke(new GestureStroke(mStrokeBuffer));
@@ -265,7 +341,7 @@
                 }
                 try {
                     final int gestureId = Integer.parseInt(bestPrediction.name);
-                    if (mListener.onGesture(gestureId)) {
+                    if (mListener.onGestureCompleted(gestureId)) {
                         return true;
                     }
                 } catch (NumberFormatException nfe) {
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index ca30349..9e6cd00 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -25,7 +25,6 @@
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
-import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
@@ -87,9 +86,6 @@
     // Invalid pointer ID.
     private static final int INVALID_POINTER_ID = -1;
 
-    // The velocity above which we detect gestures.
-    private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
-
     // The minimal distance before we take the middle of the distance between
     // the two dragging pointers as opposed to use the location of the primary one.
     private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
@@ -134,15 +130,9 @@
     // the two dragging pointers as opposed to use the location of the primary one.
     private final int mScaledMinPointerDistanceToUseMiddleLocation;
 
-    // The scaled velocity above which we detect gestures.
-    private final int mScaledGestureDetectionVelocity;
-
     // The handler to which to delegate events.
     private EventStreamTransformation mNext;
 
-    // Helper to track gesture velocity.
-    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-
     // Helper class to track received pointers.
     private final ReceivedPointerTracker mReceivedPointerTracker;
 
@@ -200,7 +190,6 @@
         final float density = context.getResources().getDisplayMetrics().density;
         mScaledMinPointerDistanceToUseMiddleLocation =
             (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
-        mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
     }
 
     @Override
@@ -289,11 +278,19 @@
 
         mReceivedPointerTracker.onMotionEvent(rawEvent);
 
-        if (mGestureDetector.onMotionEvent(event, policyFlags)) {
+        // The motion detector is interested in the movements in physical space,
+        // so it uses the rawEvent to ignore magnification and other
+        // transformations.
+        if (mGestureDetector.onMotionEvent(rawEvent, policyFlags)) {
             // Event was handled by the gesture detector.
             return;
         }
 
+        if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+            clear(event, policyFlags);
+            return;
+        }
+
         switch(mCurrentState) {
             case STATE_TOUCH_EXPLORING: {
                 handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
@@ -305,7 +302,7 @@
                 handleMotionEventStateDelegating(event, policyFlags);
             } break;
             case STATE_GESTURE_DETECTING: {
-                handleMotionEventGestureDetecting(rawEvent, policyFlags);
+                // Already handled.
             } break;
             default:
                 throw new IllegalStateException("Illegal state: " + mCurrentState);
@@ -440,26 +437,50 @@
     }
 
     @Override
-    public boolean onGesture(int gestureId) {
+    public boolean onGestureCompleted(int gestureId) {
         if (mCurrentState != STATE_GESTURE_DETECTING) {
             return false;
         }
 
-        mAms.onTouchInteractionEnd();
-
-        // Announce the end of the gesture recognition.
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
-        // Announce the end of a the touch interaction.
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+        endGestureDetection();
 
         mAms.onGesture(gestureId);
 
-        mExitGestureDetectionModeDelayed.cancel();
-        mCurrentState = STATE_TOUCH_EXPLORING;
-
         return true;
     }
 
+    @Override
+    public void onGestureStarted() {
+      // We have to perform gesture detection, so
+      // clear the current state and try to detect.
+      mCurrentState = STATE_GESTURE_DETECTING;
+      mSendHoverEnterAndMoveDelayed.cancel();
+      mSendHoverExitDelayed.cancel();
+      mExitGestureDetectionModeDelayed.post();
+      // Send accessibility event to announce the start
+      // of gesture recognition.
+      sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
+    }
+
+    @Override
+    public void onGestureCancelled(MotionEvent event, int policyFlags) {
+      if (mCurrentState == STATE_GESTURE_DETECTING) {
+          endGestureDetection();
+      } else if (mCurrentState == STATE_TOUCH_EXPLORING) {
+          final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+          final int pointerIdBits = (1 << pointerId);
+
+          // Cache the event until we discern exploration from gesturing.
+          mSendHoverEnterAndMoveDelayed.addEvent(event);
+
+          // We have just decided that the user is touch,
+          // exploring so start sending events.
+          mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
+          mSendHoverExitDelayed.cancel();
+          sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+      }
+    }
+
     /**
      * Handles a motion event in touch exploring state.
      *
@@ -471,8 +492,6 @@
             int policyFlags) {
         ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
 
-        mVelocityTracker.addMovement(rawEvent);
-
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 mAms.onTouchInteractionStart();
@@ -525,46 +544,6 @@
                         if (mSendHoverEnterAndMoveDelayed.isPending()) {
                             // Cache the event until we discern exploration from gesturing.
                             mSendHoverEnterAndMoveDelayed.addEvent(event);
-
-                            // It is *important* to use the distance traveled by the pointers
-                            // on the screen which may or may not be magnified.
-                            final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
-                                - rawEvent.getX(pointerIndex);
-                            final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
-                                - rawEvent.getY(pointerIndex);
-                            final double moveDelta = Math.hypot(deltaX, deltaY);
-                            // The user has moved enough for us to decide.
-                            if (moveDelta > mDoubleTapSlop) {
-                                // Check whether the user is performing a gesture. We
-                                // detect gestures if the pointer is moving above a
-                                // given velocity.
-                                mVelocityTracker.computeCurrentVelocity(1000);
-                                final float maxAbsVelocity = Math.max(
-                                        Math.abs(mVelocityTracker.getXVelocity(pointerId)),
-                                        Math.abs(mVelocityTracker.getYVelocity(pointerId)));
-                                if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
-                                    // We have to perform gesture detection, so
-                                    // clear the current state and try to detect.
-                                    mCurrentState = STATE_GESTURE_DETECTING;
-                                    mVelocityTracker.clear();
-                                    mSendHoverEnterAndMoveDelayed.cancel();
-                                    mSendHoverExitDelayed.cancel();
-                                    mExitGestureDetectionModeDelayed.post();
-                                    // Send accessibility event to announce the start
-                                    // of gesture recognition.
-                                    sendAccessibilityEvent(
-                                            AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
-                                } else {
-                                    // We have just decided that the user is touch,
-                                    // exploring so start sending events.
-                                    mGestureDetector.cancelGesture();
-                                    mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
-                                    mSendHoverExitDelayed.cancel();
-                                    sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
-                                            pointerIdBits, policyFlags);
-                                }
-                                break;
-                            }
                         } else {
                             if (mTouchExplorationInProgress) {
                                 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
@@ -602,11 +581,6 @@
                             }
                         }
 
-                        // We know that a new state transition is to happen and the
-                        // new state will not be gesture recognition, so cancel
-                        // the gesture.
-                        mGestureDetector.cancelGesture();
-
                         if (isDraggingGesture(event)) {
                             // Two pointers moving in the same direction within
                             // a given distance perform a drag.
@@ -620,7 +594,6 @@
                             mCurrentState = STATE_DELEGATING;
                             sendDownForAllNotInjectedPointers(event, policyFlags);
                         }
-                        mVelocityTracker.clear();
                     } break;
                     default: {
                         // More than one pointer so the user is not touch exploring
@@ -639,7 +612,6 @@
                         // More than two pointers are delegated to the view hierarchy.
                         mCurrentState = STATE_DELEGATING;
                         sendDownForAllNotInjectedPointers(event, policyFlags);
-                        mVelocityTracker.clear();
                     }
                 }
             } break;
@@ -648,8 +620,6 @@
                 final int pointerId = event.getPointerId(event.getActionIndex());
                 final int pointerIdBits = (1 << pointerId);
 
-                mVelocityTracker.clear();
-
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
                     // If we have not delivered the enter schedule an exit.
                     mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
@@ -663,9 +633,6 @@
                 }
 
             } break;
-            case MotionEvent.ACTION_CANCEL: {
-                clear(event, policyFlags);
-            } break;
         }
     }
 
@@ -756,9 +723,6 @@
                 }
                 mCurrentState = STATE_TOUCH_EXPLORING;
             } break;
-            case MotionEvent.ACTION_CANCEL: {
-                clear(event, policyFlags);
-            } break;
         }
     }
 
@@ -795,9 +759,6 @@
 
                 mCurrentState = STATE_TOUCH_EXPLORING;
             } break;
-            case MotionEvent.ACTION_CANCEL: {
-                clear(event, policyFlags);
-            } break;
             default: {
                 // Deliver the event.
                 sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
@@ -805,22 +766,16 @@
         }
     }
 
-    private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_UP: {
-                mAms.onTouchInteractionEnd();
-                // Announce the end of the gesture recognition.
-                sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
-                // Announce the end of a the touch interaction.
-                sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+    private void endGestureDetection() {
+        mAms.onTouchInteractionEnd();
 
-                mExitGestureDetectionModeDelayed.cancel();
-                mCurrentState = STATE_TOUCH_EXPLORING;
-            } break;
-            case MotionEvent.ACTION_CANCEL: {
-                clear(event, policyFlags);
-            } break;
-        }
+        // Announce the end of the gesture recognition.
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+        // Announce the end of a the touch interaction.
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+        mExitGestureDetectionModeDelayed.cancel();
+        mCurrentState = STATE_TOUCH_EXPLORING;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 5d97afa..dac89ec 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -49,6 +49,8 @@
     public static final int DEXOPT_BOOTCOMPLETE = 1 << 4;
     /** Do not compile, only extract bytecode into an OAT file */
     public static final int DEXOPT_EXTRACTONLY  = 1 << 5;
+    /** This is an OTA update dexopt */
+    public static final int DEXOPT_OTA          = 1 << 6;
 
     /** @hide */
     @IntDef(flag = true, value = {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
new file mode 100644
index 0000000..da62a2d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IOtaDexopt;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
+import android.content.pm.ResolveInfo;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import dalvik.system.DexFile;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static com.android.server.pm.Installer.DEXOPT_OTA;
+
+/**
+ * A service for A/B OTA dexopting.
+ *
+ * {@hide}
+ */
+public class OtaDexoptService extends IOtaDexopt.Stub {
+    private final static String TAG = "OTADexopt";
+    private final static boolean DEBUG_DEXOPT = true;
+    // Apps used in the last 7 days.
+    private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60;
+
+    private final Context mContext;
+    private final PackageDexOptimizer mPackageDexOptimizer;
+    private final PackageManagerService mPackageManagerService;
+
+    // TODO: Evaluate the need for WeakReferences here.
+    private List<PackageParser.Package> mDexoptPackages;
+
+    public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
+        this.mContext = context;
+        this.mPackageManagerService = packageManagerService;
+
+        // Use the package manager install and install lock here for the OTA dex optimizer.
+        mPackageDexOptimizer = new OTADexoptPackageDexOptimizer(packageManagerService.mInstaller,
+                packageManagerService.mInstallLock, context);
+    }
+
+    public static OtaDexoptService main(Context context,
+            PackageManagerService packageManagerService) {
+        OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
+        ServiceManager.addService("otadexopt", ota);
+
+        return ota;
+    }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) throws RemoteException {
+        (new OtaDexoptShellCommand(this)).exec(
+                this, in, out, err, args, resultReceiver);
+    }
+
+    @Override
+    public synchronized void prepare() throws RemoteException {
+        if (mDexoptPackages != null) {
+            throw new IllegalStateException("already called prepare()");
+        }
+
+        mDexoptPackages = new LinkedList<>();
+
+        ArrayList<PackageParser.Package> pkgs;
+        synchronized (mPackageManagerService.mPackages) {
+            pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values());
+        }
+
+        // Sort apps by importance for dexopt ordering. Important apps are given more priority
+        // in case the device runs out of space.
+
+        // Give priority to core apps.
+        for (PackageParser.Package pkg : pkgs) {
+            if (pkg.coreApp) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName);
+                }
+                mDexoptPackages.add(pkg);
+            }
+        }
+        pkgs.removeAll(mDexoptPackages);
+
+        // Give priority to system apps that listen for pre boot complete.
+        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+        ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
+        for (PackageParser.Package pkg : pkgs) {
+            if (pkgNames.contains(pkg.packageName)) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " +
+                            pkg.packageName);
+                }
+                mDexoptPackages.add(pkg);
+            }
+        }
+        pkgs.removeAll(mDexoptPackages);
+
+        // Filter out packages that aren't recently used, add all remaining apps.
+        // TODO: add a property to control this?
+        if (mPackageManagerService.isHistoricalPackageUsageAvailable()) {
+            filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000);
+        }
+        mDexoptPackages.addAll(pkgs);
+
+        // Now go ahead and also add the libraries required for these packages.
+        // TODO: Think about interleaving things.
+        Set<PackageParser.Package> dependencies = new HashSet<>();
+        for (PackageParser.Package p : mDexoptPackages) {
+            dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p));
+        }
+        if (!dependencies.isEmpty()) {
+            dependencies.removeAll(mDexoptPackages);
+        }
+        mDexoptPackages.addAll(dependencies);
+
+        if (DEBUG_DEXOPT) {
+            StringBuilder sb = new StringBuilder();
+            for (PackageParser.Package pkg : mDexoptPackages) {
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append(pkg.packageName);
+            }
+            Log.i(TAG, "Packages to be optimized: " + sb.toString());
+        }
+    }
+
+    @Override
+    public synchronized void cleanup() throws RemoteException {
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "Cleaning up OTA Dexopt state.");
+        }
+        mDexoptPackages = null;
+    }
+
+    @Override
+    public synchronized boolean isDone() throws RemoteException {
+        if (mDexoptPackages == null) {
+            throw new IllegalStateException("done() called before prepare()");
+        }
+
+        return mDexoptPackages.isEmpty();
+    }
+
+    @Override
+    public synchronized void dexoptNextPackage() throws RemoteException {
+        if (mDexoptPackages == null) {
+            throw new IllegalStateException("dexoptNextPackage() called before prepare()");
+        }
+        if (mDexoptPackages.isEmpty()) {
+            // Tolerate repeated calls.
+            return;
+        }
+
+        PackageParser.Package nextPackage = mDexoptPackages.remove(0);
+
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt.");
+        }
+
+        // Check for low space.
+        // TODO: If apps are not installed in the internal /data partition, we should compare
+        //       against that storage's free capacity.
+        File dataDir = Environment.getDataDirectory();
+        long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
+        if (lowThreshold == 0) {
+            throw new IllegalStateException("Invalid low memory threshold");
+        }
+        long usableSpace = dataDir.getUsableSpace();
+        if (usableSpace < lowThreshold) {
+            Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " +
+                    usableSpace);
+            return;
+        }
+
+        mPackageDexOptimizer.performDexOpt(nextPackage, null /* ISAs */, false /* useProfiles */,
+                false /* extractOnly */);
+    }
+
+    private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
+        List<ResolveInfo> ris = null;
+        try {
+            ris = AppGlobals.getPackageManager().queryIntentReceivers(
+                    intent, null, 0, userId);
+        } catch (RemoteException e) {
+        }
+        ArraySet<String> pkgNames = new ArraySet<String>(ris == null ? 0 : ris.size());
+        if (ris != null) {
+            for (ResolveInfo ri : ris) {
+                pkgNames.add(ri.activityInfo.packageName);
+            }
+        }
+        return pkgNames;
+    }
+
+    private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs,
+            long dexOptLRUThresholdInMills) {
+        // Filter out packages that aren't recently used.
+        int total = pkgs.size();
+        int skipped = 0;
+        long now = System.currentTimeMillis();
+        for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
+            PackageParser.Package pkg = i.next();
+            long then = pkg.mLastPackageUsageTimeInMills;
+            if (then + dexOptLRUThresholdInMills < now) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
+                          ((then == 0) ? "never" : new Date(then)));
+                }
+                i.remove();
+                skipped++;
+            }
+        }
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
+        }
+    }
+
+    private static class OTADexoptPackageDexOptimizer extends
+            PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
+
+        public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
+                Context context) {
+            super(installer, installLock, context, "*otadexopt*");
+        }
+
+        @Override
+        protected int adjustDexoptFlags(int dexoptFlags) {
+            // Add the OTA flag.
+            return dexoptFlags | DEXOPT_OTA;
+        }
+
+        @Override
+        protected void recordSuccessfulDexopt(Package pkg, String instructionSet) {
+            // Never record the dexopt, as it's in the B partition.
+        }
+
+    }
+}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
new file mode 100644
index 0000000..ea9cf17
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.IOtaDexopt;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+class OtaDexoptShellCommand extends ShellCommand {
+    final IOtaDexopt mInterface;
+
+    OtaDexoptShellCommand(OtaDexoptService service) {
+        mInterface = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(null);
+        }
+
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch(cmd) {
+                case "prepare":
+                    return runOtaPrepare();
+                case "cleanup":
+                    return runOtaCleanup();
+                case "done":
+                    return runOtaDone();
+                case "step":
+                    return runOtaStep();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private int runOtaPrepare() throws RemoteException {
+        mInterface.prepare();
+        getOutPrintWriter().println("Success");
+        return 0;
+    }
+
+    private int runOtaCleanup() throws RemoteException {
+        mInterface.cleanup();
+        return 0;
+    }
+
+    private int runOtaDone() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        if (mInterface.isDone()) {
+            pw.println("OTA complete.");
+        } else {
+            pw.println("OTA incomplete.");
+        }
+        return 0;
+    }
+
+    private int runOtaStep() throws RemoteException {
+        mInterface.dexoptNextPackage();
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("OTA Dexopt (ota) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("");
+        pw.println("  prepare");
+        pw.println("    Prepare an OTA dexopt pass, collecting all packages.");
+        pw.println("  done");
+        pw.println("    Replies whether the OTA is complete or not.");
+        pw.println("  step");
+        pw.println("    OTA dexopt the next package.");
+        pw.println("  cleanup");
+        pw.println("    Clean up internal states. Ends an OTA session.");
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fe0f141..64af213 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.WorkSource;
@@ -48,7 +49,7 @@
 /**
  * Helper class for running dexopt command on packages.
  */
-final class PackageDexOptimizer {
+class PackageDexOptimizer {
     private static final String TAG = "PackageManager.DexOptimizer";
     static final String OAT_DIR_NAME = "oat";
     // TODO b/19550105 Remove error codes and use exceptions
@@ -57,16 +58,28 @@
     static final int DEX_OPT_DEFERRED = 2;
     static final int DEX_OPT_FAILED = -1;
 
-    private final PackageManagerService mPackageManagerService;
+    private static final boolean DEBUG_DEXOPT = PackageManagerService.DEBUG_DEXOPT;
+
+    private final Installer mInstaller;
+    private final Object mInstallLock;
 
     private final PowerManager.WakeLock mDexoptWakeLock;
     private volatile boolean mSystemReady;
 
-    PackageDexOptimizer(PackageManagerService packageManagerService) {
-        this.mPackageManagerService = packageManagerService;
-        PowerManager powerManager = (PowerManager)packageManagerService.mContext.getSystemService(
-                Context.POWER_SERVICE);
-        mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*dexopt*");
+    PackageDexOptimizer(Installer installer, Object installLock, Context context,
+            String wakeLockTag) {
+        this.mInstaller = installer;
+        this.mInstallLock = installLock;
+
+        PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
+    }
+
+    protected PackageDexOptimizer(PackageDexOptimizer from) {
+        this.mInstaller = from.mInstaller;
+        this.mInstallLock = from.mInstallLock;
+        this.mDexoptWakeLock = from.mDexoptWakeLock;
+        this.mSystemReady = from.mSystemReady;
     }
 
     static boolean canOptimizePackage(PackageParser.Package pkg) {
@@ -77,27 +90,19 @@
      * Performs dexopt on all code paths and libraries of the specified package for specified
      * instruction sets.
      *
-     * <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on
-     * {@link PackageManagerService#mInstallLock}.
+     * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
+     * synchronized on {@link #mInstallLock}.
      */
-    int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
-            boolean inclDependencies, boolean useProfiles, boolean extractOnly, boolean force) {
-        ArraySet<String> done;
-        if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
-            done = new ArraySet<String>();
-            done.add(pkg.packageName);
-        } else {
-            done = null;
-        }
-        synchronized (mPackageManagerService.mInstallLock) {
+    int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean useProfiles,
+            boolean extractOnly) {
+        synchronized (mInstallLock) {
             final boolean useLock = mSystemReady;
             if (useLock) {
                 mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
                 mDexoptWakeLock.acquire();
             }
             try {
-                return performDexOptLI(pkg, instructionSets, done, useProfiles,
-                        extractOnly, force);
+                return performDexOptLI(pkg, instructionSets, useProfiles, extractOnly);
             } finally {
                 if (useLock) {
                     mDexoptWakeLock.release();
@@ -106,21 +111,42 @@
         }
     }
 
+    /**
+     * Determine whether the package should be skipped for the given instruction set. A return
+     * value of true means the package will be skipped. A return value of false means that the
+     * package will be further investigated, and potentially compiled.
+     */
+    protected boolean shouldSkipBasedOnISA(PackageParser.Package pkg, String instructionSet) {
+        return pkg.mDexOptPerformed.contains(instructionSet);
+    }
+
+    /**
+     * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
+     * optimize or not (and in what way).
+     */
+    protected int adjustDexoptNeeded(int dexoptNeeded) {
+        return dexoptNeeded;
+    }
+
+    /**
+     * Adjust the given dexopt flags that will be passed to the installer.
+     */
+    protected int adjustDexoptFlags(int dexoptFlags) {
+        return dexoptFlags;
+    }
+
+    /**
+     * Update the package status after a successful compilation.
+     */
+    protected void recordSuccessfulDexopt(PackageParser.Package pkg, String instructionSet) {
+        pkg.mDexOptPerformed.add(instructionSet);
+    }
+
     private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
-            ArraySet<String> done, boolean useProfiles, boolean extractOnly, boolean force) {
+            boolean useProfiles, boolean extractOnly) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
 
-        if (done != null) {
-            done.add(pkg.packageName);
-            if (pkg.usesLibraries != null) {
-                performDexOptLibsLI(pkg.usesLibraries, instructionSets, done);
-            }
-            if (pkg.usesOptionalLibraries != null) {
-                performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, done);
-            }
-        }
-
         if (!canOptimizePackage(pkg)) {
             return DEX_OPT_SKIPPED;
         }
@@ -128,34 +154,26 @@
         final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
 
-        if (useProfiles) {
-            // If we do a profile guided compilation then we might recompile
-            // the same package if more profile information is available.
-            force = true;
-        }
-
         final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
         boolean performedDexOpt = false;
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
         for (String dexCodeInstructionSet : dexCodeInstructionSets) {
-            if (!force && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+            if (!useProfiles && shouldSkipBasedOnISA(pkg, dexCodeInstructionSet)) {
+                // Skip only if we do not use profiles since they might trigger a recompilation.
                 continue;
             }
 
             for (String path : paths) {
                 int dexoptNeeded;
 
-                if (force) {
-                    dexoptNeeded = DexFile.DEX2OAT_NEEDED;
-                } else {
-                    try {
-                        dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
-                                dexCodeInstructionSet, /* defer */false);
-                    } catch (IOException ioe) {
-                        Slog.w(TAG, "IOException reading apk: " + path, ioe);
-                        return DEX_OPT_FAILED;
-                    }
+                try {
+                    dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
+                            dexCodeInstructionSet, /* defer */false);
+                } catch (IOException ioe) {
+                    Slog.w(TAG, "IOException reading apk: " + path, ioe);
+                    return DEX_OPT_FAILED;
                 }
+                dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
 
                 if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
                     // No dexopt needed and we don't use profiles. Nothing to do.
@@ -174,21 +192,22 @@
                     throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
                 }
 
+
                 Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
                         + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
                         + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
                         + " extractOnly=" + extractOnly + " oatDir = " + oatDir);
                 final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
-                final int dexFlags =
+                final int dexFlags = adjustDexoptFlags(
                         (!pkg.isForwardLocked() ? DEXOPT_PUBLIC : 0)
                         | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
                         | (debuggable ? DEXOPT_DEBUGGABLE : 0)
                         | (extractOnly ? DEXOPT_EXTRACTONLY : 0)
-                        | DEXOPT_BOOTCOMPLETE;
+                        | DEXOPT_BOOTCOMPLETE);
+
                 try {
-                    mPackageManagerService.mInstaller.dexopt(path, sharedGid,
-                            pkg.packageName, dexCodeInstructionSet, dexoptNeeded, oatDir,
-                            dexFlags, pkg.volumeUuid, useProfiles);
+                    mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
+                            dexoptNeeded, oatDir, dexFlags, pkg.volumeUuid, useProfiles);
                     performedDexOpt = true;
                 } catch (InstallerException e) {
                     Slog.w(TAG, "Failed to dexopt", e);
@@ -201,7 +220,7 @@
                 // it isn't required. We therefore mark that this package doesn't need dexopt unless
                 // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
                 // it.
-                pkg.mDexOptPerformed.add(dexCodeInstructionSet);
+                recordSuccessfulDexopt(pkg, dexCodeInstructionSet);
             }
         }
 
@@ -232,8 +251,7 @@
         if (codePath.isDirectory()) {
             File oatDir = getOatDir(codePath);
             try {
-                mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
-                        dexInstructionSet);
+                mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
             } catch (InstallerException e) {
                 Slog.w(TAG, "Failed to create oat dir", e);
                 return null;
@@ -247,21 +265,36 @@
         return new File(codePath, OAT_DIR_NAME);
     }
 
-    private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets,
-            ArraySet<String> done) {
-        for (String libName : libs) {
-            PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary(
-                    libName);
-            if (libPkg != null && !done.contains(libName)) {
-                // TODO: Analyze and investigate if we (should) profile libraries.
-                // Currently this will do a full compilation of the library.
-                performDexOptLI(libPkg, instructionSets, done, /*useProfiles*/ false,
-                        /* extractOnly */ false, /* force */ false);
-            }
-        }
-    }
-
     void systemReady() {
         mSystemReady = true;
     }
+
+    /**
+     * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a
+     * dexopt path.
+     */
+    public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer {
+
+        public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
+                Context context, String wakeLockTag) {
+            super(installer, installLock, context, wakeLockTag);
+        }
+
+        public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
+            super(from);
+        }
+
+        @Override
+        protected boolean shouldSkipBasedOnISA(Package pkg, String instructionSet) {
+            // Forced compilation, never skip.
+            return false;
+        }
+
+        @Override
+        protected int adjustDexoptNeeded(int dexoptNeeded) {
+            // Ensure compilation, no matter the current state.
+            // TODO: The return value is wrong when patchoat is needed.
+            return DexFile.DEX2OAT_NEEDED;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d3b9187..553a9a2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -139,6 +139,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.Package;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageStats;
@@ -263,6 +264,7 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
@@ -277,6 +279,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -313,7 +316,12 @@
     private static final boolean DEBUG_INTENT_MATCHING = false;
     private static final boolean DEBUG_PACKAGE_SCANNING = false;
     private static final boolean DEBUG_VERIFY = false;
-    private static final boolean DEBUG_DEXOPT = false;
+
+    // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
+    // and PackageDexOptimizer. All these classes have their own flag to allow switching a single
+    // user, but by default initialize to this.
+    static final boolean DEBUG_DEXOPT = false;
+
     private static final boolean DEBUG_ABI_SELECTION = false;
     private static final boolean DEBUG_EPHEMERAL = false;
     private static final boolean DEBUG_TRIAGED_MISSING = false;
@@ -1990,7 +1998,8 @@
         }
 
         mInstaller = installer;
-        mPackageDexOptimizer = new PackageDexOptimizer(this);
+        mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
+                "*dexopt*");
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -3377,19 +3386,6 @@
         return null;
     }
 
-    /**
-     * @hide
-     */
-    PackageParser.Package findSharedNonSystemLibrary(String libName) {
-        synchronized (mPackages) {
-            PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
-            if (lib != null && lib.apk != null) {
-                return mPackages.get(lib.apk);
-            }
-        }
-        return null;
-    }
-
     @Override
     public FeatureInfo[] getSystemAvailableFeatures() {
         Collection<FeatureInfo> featSet;
@@ -6718,8 +6714,8 @@
         try {
             synchronized (mInstallLock) {
                 final String[] instructionSets = new String[] { targetInstructionSet };
-                int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
-                        true /* inclDependencies */, useProfiles, extractOnly, force);
+                int result = performDexOptInternalWithDependenciesLI(p, instructionSets,
+                        useProfiles, extractOnly, force);
                 return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
             }
         } finally {
@@ -6739,6 +6735,80 @@
         return pkgs;
     }
 
+    private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
+            String instructionSets[], boolean useProfiles, boolean extractOnly, boolean force) {
+        // Select the dex optimizer based on the force parameter.
+        // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+        //       allocate an object here.
+        PackageDexOptimizer pdo = force
+                ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
+                : mPackageDexOptimizer;
+
+        // Optimize all dependencies first. Note: we ignore the return value and march on
+        // on errors.
+        Collection<PackageParser.Package> deps = findSharedNonSystemLibraries(p);
+        if (!deps.isEmpty()) {
+            for (PackageParser.Package depPackage : deps) {
+                // TODO: Analyze and investigate if we (should) profile libraries.
+                // Currently this will do a full compilation of the library.
+                pdo.performDexOpt(depPackage, instructionSets, false /* useProfiles */,
+                        false /* extractOnly */);
+            }
+        }
+
+        return pdo.performDexOpt(p, instructionSets, useProfiles, extractOnly);
+    }
+
+    Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
+        if (p.usesLibraries != null || p.usesOptionalLibraries != null) {
+            ArrayList<PackageParser.Package> retValue = new ArrayList<>();
+            Set<String> collectedNames = new HashSet<>();
+            findSharedNonSystemLibrariesRecursive(p, retValue, collectedNames);
+
+            retValue.remove(p);
+
+            return retValue;
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    private void findSharedNonSystemLibrariesRecursive(PackageParser.Package p,
+            Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+        if (!collectedNames.contains(p.packageName)) {
+            collectedNames.add(p.packageName);
+            collected.add(p);
+
+            if (p.usesLibraries != null) {
+                findSharedNonSystemLibrariesRecursive(p.usesLibraries, collected, collectedNames);
+            }
+            if (p.usesOptionalLibraries != null) {
+                findSharedNonSystemLibrariesRecursive(p.usesOptionalLibraries, collected,
+                        collectedNames);
+            }
+        }
+    }
+
+    private void findSharedNonSystemLibrariesRecursive(Collection<String> libs,
+            Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+        for (String libName : libs) {
+            PackageParser.Package libPkg = findSharedNonSystemLibrary(libName);
+            if (libPkg != null) {
+                findSharedNonSystemLibrariesRecursive(libPkg, collected, collectedNames);
+            }
+        }
+    }
+
+    private PackageParser.Package findSharedNonSystemLibrary(String libName) {
+        synchronized (mPackages) {
+            PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
+            if (lib != null && lib.apk != null) {
+                return mPackages.get(lib.apk);
+            }
+        }
+        return null;
+    }
+
     public void shutdown() {
         mPackageUsage.write(true);
     }
@@ -6763,9 +6833,8 @@
 
             // Whoever is calling forceDexOpt wants a fully compiled package.
             // Don't use profiles since that may cause compilation to be skipped.
-            final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
-                    true /* inclDependencies */, false /* useProfiles */,
-                    false /* extractOnly */, true /* force */);
+            final int res = performDexOptInternalWithDependenciesLI(pkg, instructionSets,
+                    false /* useProfiles */, false /* extractOnly */, true /* force */);
 
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -13042,8 +13111,7 @@
                 // Do not run PackageDexOptimizer through the local performDexOpt
                 // method because `pkg` is not in `mPackages` yet.
                 int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */,
-                        false /* inclDependencies */, false /* useProfiles */,
-                        true /* extractOnly */, false /* force */);
+                        false /* useProfiles */, true /* extractOnly */);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
                     String msg = "Extracking package failed for " + pkgName;
@@ -18043,4 +18111,8 @@
                     "Cannot call " + tag + " from UID " + callingUid);
         }
     }
+
+    boolean isHistoricalPackageUsageAvailable() {
+        return mPackageUsage.isHistoricalPackageUsageAvailable();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 47bdb5c..14e4fd5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7260,8 +7260,18 @@
         mTaskPositioner = new TaskPositioner(this);
         mTaskPositioner.register(display);
         mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+        // We need to grab the touch focus so that the touch events during the
+        // resizing/scrolling are not sent to the app. 'win' is the main window
+        // of the app, it may not have focus since there might be other windows
+        // on top (eg. a dialog window).
+        WindowState transferFocusFromWin = win;
+        if (mCurrentFocus != null && mCurrentFocus != win
+                && mCurrentFocus.mAppToken == win.mAppToken) {
+            transferFocusFromWin = mCurrentFocus;
+        }
         if (!mInputManager.transferTouchFocus(
-                win.mInputChannel, mTaskPositioner.mServerChannel)) {
+                transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
             Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
             mTaskPositioner.unregister();
             mTaskPositioner = null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e8b9b1f..572843e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1463,6 +1463,14 @@
         void securityLogSetLoggingEnabledProperty(boolean enabled) {
             SecurityLog.setLoggingEnabledProperty(enabled);
         }
+
+        boolean securityLogGetLoggingEnabledProperty() {
+            return SecurityLog.getLoggingEnabledProperty();
+        }
+
+        boolean securityLogIsLoggingEnabled() {
+            return SecurityLog.isLoggingEnabled();
+        }
     }
 
     /**
@@ -1607,7 +1615,7 @@
             if (mOwners.hasDeviceOwner()) {
                 mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true");
                 disableDeviceLoggingIfNotCompliant();
-                if (SecurityLog.getLoggingEnabledProperty()) {
+                if (mInjector.securityLogGetLoggingEnabledProperty()) {
                     mSecurityLogMonitor.start();
                 }
             } else {
@@ -4472,7 +4480,7 @@
             mInjector.binderRestoreCallingIdentity(ident);
         }
 
-        if (SecurityLog.isLoggingEnabled()) {
+        if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0);
         }
     }
@@ -4502,7 +4510,7 @@
             }
         }
 
-        if (SecurityLog.isLoggingEnabled()) {
+        if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 1);
         }
     }
@@ -4511,7 +4519,7 @@
     public void reportKeyguardDismissed() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        if (SecurityLog.isLoggingEnabled()) {
+        if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISSED);
         }
     }
@@ -4520,7 +4528,7 @@
     public void reportKeyguardSecured() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        if (SecurityLog.isLoggingEnabled()) {
+        if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_SECURED);
         }
     }
@@ -8352,7 +8360,7 @@
         Preconditions.checkNotNull(admin);
         synchronized (this) {
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-            return SecurityLog.getLoggingEnabledProperty();
+            return mInjector.securityLogGetLoggingEnabledProperty();
         }
     }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 254a37a..b64db57 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -80,6 +80,7 @@
 import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.Installer;
 import com.android.server.pm.LauncherAppsService;
+import com.android.server.pm.OtaDexoptService;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerService;
 import com.android.server.power.PowerManagerService;
@@ -1097,6 +1098,18 @@
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
+                // Manages A/B OTA dexopting.
+                boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
+                        false);
+                if (!disableOtaDexopt) {
+                    traceBeginAndSlog("StartOtaDexOptService");
+                    try {
+                        OtaDexoptService.main(mSystemContext, mPackageManagerService);
+                    } catch (Throwable e) {
+                        reportWtf("starting BackgroundDexOptService", e);
+                    }
+                    Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+                }
             }
 
             mSystemServiceManager.startService(LauncherAppsService.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index bd37f4a..db2a9ad 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -291,5 +291,15 @@
         void securityLogSetLoggingEnabledProperty(boolean enabled) {
             context.settings.securityLogSetLoggingEnabledProperty(enabled);
         }
+
+        @Override
+        boolean securityLogGetLoggingEnabledProperty() {
+            return context.settings.securityLogGetLoggingEnabledProperty();
+        }
+
+        @Override
+        boolean securityLogIsLoggingEnabled() {
+            return context.settings.securityLogIsLoggingEnabled();
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 6518732..7a00098 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -198,6 +198,14 @@
 
         void securityLogSetLoggingEnabledProperty(boolean enabled) {
         }
+
+        public boolean securityLogGetLoggingEnabledProperty() {
+            return false;
+        }
+
+        public boolean securityLogIsLoggingEnabled() {
+            return false;
+        }
     }
 
     public final Context realTestContext;
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index af36b3e..95c8db5 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -243,4 +243,9 @@
      * @see TelecomServiceImpl#setDefaultDialer
      */
     boolean setDefaultDialer(in String packageName);
+
+    /**
+    * @see TelecomServiceImpl#launchManageBlockedNumbersActivity
+    **/
+    void launchManageBlockedNumbersActivity(in String callingPackageName);
 }
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 8443490..9eb1304 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -187,6 +187,13 @@
      */
     public static final int CDMA_ALREADY_ACTIVATED         = 49;
 
+    /**
+     * The call was terminated because it is not possible to place a video call while TTY is
+     * enabled.
+     * {@hide}
+     */
+    public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50;
+
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Please assign the new type the next id value below.
@@ -202,7 +209,7 @@
     public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED;
 
     /** Largest valid value for call disconnect codes. */
-    public static final int MAXIMUM_VALID_VALUE = CDMA_ALREADY_ACTIVATED;
+    public static final int MAXIMUM_VALID_VALUE = VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED;
 
     /** Private constructor to avoid class instantiation. */
     private DisconnectCause() {
@@ -310,6 +317,8 @@
             return "IMS_MERGED_SUCCESSFULLY";
         case CDMA_ALREADY_ACTIVATED:
             return "CDMA_ALREADY_ACTIVATED";
+        case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
+                return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
         default:
             return "INVALID: " + cause;
         }