Import Android SDK Platform P [4344336]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4344336 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4344336.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: If482fcd4cfaf6c5e544e5574926be25a293e9a6d
diff --git a/android/app/FragmentManager.java b/android/app/FragmentManager.java
index ba5ea21..0d5cd02 100644
--- a/android/app/FragmentManager.java
+++ b/android/app/FragmentManager.java
@@ -1380,8 +1380,13 @@
                                     @Override
                                     public void onAnimationEnd(Animator anim) {
                                         container.endViewTransition(view);
-                                        if (fragment.getAnimatingAway() != null) {
-                                            fragment.setAnimatingAway(null);
+                                        Animator animator = f.getAnimatingAway();
+                                        f.setAnimatingAway(null);
+                                        // If the animation finished immediately, the fragment's
+                                        // view will still be there. If so, we can just pretend
+                                        // there was no animation and skip the moveToState()
+                                        if (container.indexOfChild(view) == -1
+                                                && animator != null) {
                                             moveToState(fragment, fragment.getStateAfterAnimating(),
                                                     0, 0, false);
                                         }
diff --git a/android/app/LoadedApk.java b/android/app/LoadedApk.java
index b38be66..f6d9710 100644
--- a/android/app/LoadedApk.java
+++ b/android/app/LoadedApk.java
@@ -625,17 +625,31 @@
         final List<String> zipPaths = new ArrayList<>(10);
         final List<String> libPaths = new ArrayList<>(10);
 
-        final boolean isBundledApp = mApplicationInfo.isSystemApp()
+        boolean isBundledApp = mApplicationInfo.isSystemApp()
                 && !mApplicationInfo.isUpdatedSystemApp();
 
+        // Vendor apks are treated as bundled only when /vendor/lib is in the default search
+        // paths. If not, they are treated as unbundled; access to system libs is limited.
+        // Having /vendor/lib in the default search paths means that all system processes
+        // are allowed to use any vendor library, which in turn means that system is dependent
+        // on vendor partition. In the contrary, not having /vendor/lib in the default search
+        // paths mean that the two partitions are separated and thus we can treat vendor apks
+        // as unbundled.
+        final String defaultSearchPaths = System.getProperty("java.library.path");
+        final boolean treatVendorApkAsUnbundled = !defaultSearchPaths.contains("/vendor/lib");
+        if (mApplicationInfo.getCodePath() != null
+                && mApplicationInfo.getCodePath().startsWith("/vendor/")
+                && treatVendorApkAsUnbundled) {
+            isBundledApp = false;
+        }
+
         makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
 
         String libraryPermittedPath = mDataDir;
         if (isBundledApp) {
             // This is necessary to grant bundled apps access to
             // libraries located in subdirectories of /system/lib
-            libraryPermittedPath += File.pathSeparator +
-                                    System.getProperty("java.library.path");
+            libraryPermittedPath += File.pathSeparator + defaultSearchPaths;
         }
 
         final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
diff --git a/android/app/Notification.java b/android/app/Notification.java
index 841b961..ee6c1cb 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -67,6 +67,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -3905,6 +3906,7 @@
             if (p.title != null) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
                 contentView.setTextViewText(R.id.title, processTextSpans(p.title));
+                updateTextSizePrimary(contentView, R.id.title);
                 if (!p.ambient) {
                     setTextViewColorPrimary(contentView, R.id.title);
                 }
@@ -3916,6 +3918,7 @@
                 int textId = showProgress ? com.android.internal.R.id.text_line_1
                         : com.android.internal.R.id.text;
                 contentView.setTextViewText(textId, processTextSpans(p.text));
+                updateTextSizeSecondary(contentView, textId);
                 if (!p.ambient) {
                     setTextViewColorSecondary(contentView, textId);
                 }
@@ -3927,6 +3930,25 @@
             return contentView;
         }
 
+        private void updateTextSizeSecondary(RemoteViews contentView, int textId) {
+            updateTextSizeColorized(contentView, textId,
+                    com.android.internal.R.dimen.notification_text_size_colorized,
+                    com.android.internal.R.dimen.notification_text_size);
+        }
+
+        private void updateTextSizePrimary(RemoteViews contentView, int textId) {
+            updateTextSizeColorized(contentView, textId,
+                    com.android.internal.R.dimen.notification_title_text_size_colorized,
+                    com.android.internal.R.dimen.notification_title_text_size);
+        }
+
+        private void updateTextSizeColorized(RemoteViews contentView, int textId,
+                int colorizedDimen, int normalDimen) {
+            int size = mContext.getResources().getDimensionPixelSize(isColorized()
+                    ? colorizedDimen : normalDimen);
+            contentView.setTextViewTextSize(textId, TypedValue.COMPLEX_UNIT_PX, size);
+        }
+
         private CharSequence processTextSpans(CharSequence text) {
             if (hasForegroundColor()) {
                 return NotificationColorUtil.clearColorSpans(text);
@@ -5852,6 +5874,7 @@
             builder.setTextViewColorSecondary(contentView, R.id.big_text);
             contentView.setViewVisibility(R.id.big_text,
                     TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
+            builder.updateTextSizeSecondary(contentView, R.id.big_text);
             contentView.setBoolean(R.id.big_text, "setHasImage", builder.mN.hasLargeIcon());
         }
     }
@@ -6185,6 +6208,7 @@
                 contentView.setViewVisibility(rowId, View.VISIBLE);
                 contentView.setTextViewText(rowId, mBuilder.processTextSpans(
                         makeMessageLine(m, mBuilder)));
+                mBuilder.updateTextSizeSecondary(contentView, rowId);
                 mBuilder.setTextViewColorSecondary(contentView, rowId);
 
                 if (contractedMessage == m) {
@@ -6552,6 +6576,7 @@
                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
                     contentView.setTextViewText(rowIds[i],
                             mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
+                    mBuilder.updateTextSizeSecondary(contentView, rowIds[i]);
                     mBuilder.setTextViewColorSecondary(contentView, rowIds[i]);
                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
                     handleInboxImageMargin(contentView, rowIds[i], first);
@@ -8522,8 +8547,15 @@
 
         final StandardTemplateParams fillTextsFrom(Builder b) {
             Bundle extras = b.mN.extras;
-            title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE), ambient);
-            text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT), ambient);
+            this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE), ambient);
+
+            // Big text notifications should contain their content when viewed in ambient mode.
+            CharSequence text = extras.getCharSequence(EXTRA_BIG_TEXT);
+            if (!ambient || TextUtils.isEmpty(text)) {
+                text = extras.getCharSequence(EXTRA_TEXT);
+            }
+            this.text = b.processLegacyText(text, ambient);
+
             return this;
         }
     }
diff --git a/android/app/RemoteInput.java b/android/app/RemoteInput.java
index 8ab19c0..02a0124 100644
--- a/android/app/RemoteInput.java
+++ b/android/app/RemoteInput.java
@@ -33,8 +33,8 @@
  * an intent inside a {@link android.app.PendingIntent} that is sent.
  * Always use {@link RemoteInput.Builder} to create instances of this class.
  * <p class="note"> See
- * <a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input from
- * a Notification</a> for more information on how to use this class.
+ * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#direct">Replying
+ * to notifications</a> for more information on how to use this class.
  *
  * <p>The following example adds a {@code RemoteInput} to a {@link Notification.Action},
  * sets the result key as {@code quick_reply}, and sets the label as {@code Quick reply}.
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index f135244..fe18243 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,9 +1,136 @@
-//ComputableLiveData interface for tests
+/*
+ * Copyright (C) 2017 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.arch.lifecycle;
-import android.arch.lifecycle.LiveData;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A LiveData class that can be invalidated & computed on demand.
+ * <p>
+ * This is an internal class for now, might be public if we see the necessity.
+ *
+ * @param <T> The type of the live data
+ * @hide internal
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public abstract class ComputableLiveData<T> {
-    public ComputableLiveData(){}
-    abstract protected T compute();
-    public LiveData<T> getLiveData() {return null;}
-    public void invalidate() {}
+
+    private final LiveData<T> mLiveData;
+
+    private AtomicBoolean mInvalid = new AtomicBoolean(true);
+    private AtomicBoolean mComputing = new AtomicBoolean(false);
+
+    /**
+     * Creates a computable live data which is computed when there are active observers.
+     * <p>
+     * It can also be invalidated via {@link #invalidate()} which will result in a call to
+     * {@link #compute()} if there are active observers (or when they start observing)
+     */
+    @SuppressWarnings("WeakerAccess")
+    public ComputableLiveData() {
+        mLiveData = new LiveData<T>() {
+            @Override
+            protected void onActive() {
+                // TODO if we make this class public, we should accept an executor
+                AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+            }
+        };
+    }
+
+    /**
+     * Returns the LiveData managed by this class.
+     *
+     * @return A LiveData that is controlled by ComputableLiveData.
+     */
+    @SuppressWarnings("WeakerAccess")
+    @NonNull
+    public LiveData<T> getLiveData() {
+        return mLiveData;
+    }
+
+    @VisibleForTesting
+    final Runnable mRefreshRunnable = new Runnable() {
+        @WorkerThread
+        @Override
+        public void run() {
+            boolean computed;
+            do {
+                computed = false;
+                // compute can happen only in 1 thread but no reason to lock others.
+                if (mComputing.compareAndSet(false, true)) {
+                    // as long as it is invalid, keep computing.
+                    try {
+                        T value = null;
+                        while (mInvalid.compareAndSet(true, false)) {
+                            computed = true;
+                            value = compute();
+                        }
+                        if (computed) {
+                            mLiveData.postValue(value);
+                        }
+                    } finally {
+                        // release compute lock
+                        mComputing.set(false);
+                    }
+                }
+                // check invalid after releasing compute lock to avoid the following scenario.
+                // Thread A runs compute()
+                // Thread A checks invalid, it is false
+                // Main thread sets invalid to true
+                // Thread B runs, fails to acquire compute lock and skips
+                // Thread A releases compute lock
+                // We've left invalid in set state. The check below recovers.
+            } while (computed && mInvalid.get());
+        }
+    };
+
+    // invalidation check always happens on the main thread
+    @VisibleForTesting
+    final Runnable mInvalidationRunnable = new Runnable() {
+        @MainThread
+        @Override
+        public void run() {
+            boolean isActive = mLiveData.hasActiveObservers();
+            if (mInvalid.compareAndSet(false, true)) {
+                if (isActive) {
+                    // TODO if we make this class public, we should accept an executor.
+                    AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+                }
+            }
+        }
+    };
+
+    /**
+     * Invalidates the LiveData.
+     * <p>
+     * When there are active observers, this will trigger a call to {@link #compute()}.
+     */
+    public void invalidate() {
+        AppToolkitTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @WorkerThread
+    protected abstract T compute();
 }
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 3aea6ac..99d859c 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,4 +1,411 @@
-//LiveData interface for tests
+/*
+ * Copyright (C) 2017 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.arch.lifecycle;
-public class LiveData<T> {
+
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.internal.SafeIterableMap;
+import android.arch.lifecycle.Lifecycle.State;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * LiveData is a data holder class that can be observed within a given lifecycle.
+ * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
+ * this observer will be notified about modifications of the wrapped data only if the paired
+ * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
+ * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
+ * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
+ * about modifications. For those observers, you should manually call
+ * {@link #removeObserver(Observer)}.
+ *
+ * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
+ * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
+ * activities and fragments where they can safely observe LiveData and not worry about leaks:
+ * they will be instantly unsubscribed when they are destroyed.
+ *
+ * <p>
+ * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
+ * to get notified when number of active {@link Observer}s change between 0 and 1.
+ * This allows LiveData to release any heavy resources when it does not have any Observers that
+ * are actively observing.
+ * <p>
+ * This class is designed to hold individual data fields of {@link ViewModel},
+ * but can also be used for sharing data between different modules in your application
+ * in a decoupled fashion.
+ *
+ * @param <T> The type of data hold by this instance
+ * @see ViewModel
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
+// thread.
+public abstract class LiveData<T> {
+    private final Object mDataLock = new Object();
+    static final int START_VERSION = -1;
+    private static final Object NOT_SET = new Object();
+
+    private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
+
+        private LifecycleRegistry mRegistry = init();
+
+        private LifecycleRegistry init() {
+            LifecycleRegistry registry = new LifecycleRegistry(this);
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+            return registry;
+        }
+
+        @Override
+        public Lifecycle getLifecycle() {
+            return mRegistry;
+        }
+    };
+
+    private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
+            new SafeIterableMap<>();
+
+    // how many observers are in active state
+    private int mActiveCount = 0;
+    private volatile Object mData = NOT_SET;
+    // when setData is called, we set the pending data and actual data swap happens on the main
+    // thread
+    private volatile Object mPendingData = NOT_SET;
+    private int mVersion = START_VERSION;
+
+    private boolean mDispatchingValue;
+    @SuppressWarnings("FieldCanBeLocal")
+    private boolean mDispatchInvalidated;
+    private final Runnable mPostValueRunnable = new Runnable() {
+        @Override
+        public void run() {
+            Object newValue;
+            synchronized (mDataLock) {
+                newValue = mPendingData;
+                mPendingData = NOT_SET;
+            }
+            //noinspection unchecked
+            setValue((T) newValue);
+        }
+    };
+
+    private void considerNotify(LifecycleBoundObserver observer) {
+        if (!observer.active) {
+            return;
+        }
+        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
+        //
+        // we still first check observer.active to keep it as the entrance for events. So even if
+        // the observer moved to an active state, if we've not received that event, we better not
+        // notify for a more predictable notification order.
+        if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
+            return;
+        }
+        if (observer.lastVersion >= mVersion) {
+            return;
+        }
+        observer.lastVersion = mVersion;
+        //noinspection unchecked
+        observer.observer.onChanged((T) mData);
+    }
+
+    private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
+        if (mDispatchingValue) {
+            mDispatchInvalidated = true;
+            return;
+        }
+        mDispatchingValue = true;
+        do {
+            mDispatchInvalidated = false;
+            if (initiator != null) {
+                considerNotify(initiator);
+                initiator = null;
+            } else {
+                for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
+                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
+                    considerNotify(iterator.next().getValue());
+                    if (mDispatchInvalidated) {
+                        break;
+                    }
+                }
+            }
+        } while (mDispatchInvalidated);
+        mDispatchingValue = false;
+    }
+
+    /**
+     * Adds the given observer to the observers list within the lifespan of the given
+     * owner. The events are dispatched on the main thread. If LiveData already has data
+     * set, it will be delivered to the observer.
+     * <p>
+     * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
+     * or {@link Lifecycle.State#RESUMED} state (active).
+     * <p>
+     * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
+     * automatically be removed.
+     * <p>
+     * When data changes while the {@code owner} is not active, it will not receive any updates.
+     * If it becomes active again, it will receive the last available data automatically.
+     * <p>
+     * LiveData keeps a strong reference to the observer and the owner as long as the
+     * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
+     * the observer &amp; the owner.
+     * <p>
+     * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
+     * ignores the call.
+     * <p>
+     * If the given owner, observer tuple is already in the list, the call is ignored.
+     * If the observer is already in the list with another owner, LiveData throws an
+     * {@link IllegalArgumentException}.
+     *
+     * @param owner    The LifecycleOwner which controls the observer
+     * @param observer The observer that will receive the events
+     */
+    @MainThread
+    public void observe(LifecycleOwner owner, Observer<T> observer) {
+        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+            // ignore
+            return;
+        }
+        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
+        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
+        if (existing != null && existing.owner != wrapper.owner) {
+            throw new IllegalArgumentException("Cannot add the same observer"
+                    + " with different lifecycles");
+        }
+        if (existing != null) {
+            return;
+        }
+        owner.getLifecycle().addObserver(wrapper);
+        wrapper.activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
+    }
+
+    /**
+     * Adds the given observer to the observers list. This call is similar to
+     * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
+     * is always active. This means that the given observer will receive all events and will never
+     * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
+     * observing this LiveData.
+     * While LiveData has one of such observers, it will be considered
+     * as active.
+     * <p>
+     * If the observer was already added with an owner to this LiveData, LiveData throws an
+     * {@link IllegalArgumentException}.
+     *
+     * @param observer The observer that will receive the events
+     */
+    @MainThread
+    public void observeForever(Observer<T> observer) {
+        observe(ALWAYS_ON, observer);
+    }
+
+    /**
+     * Removes the given observer from the observers list.
+     *
+     * @param observer The Observer to receive events.
+     */
+    @MainThread
+    public void removeObserver(final Observer<T> observer) {
+        assertMainThread("removeObserver");
+        LifecycleBoundObserver removed = mObservers.remove(observer);
+        if (removed == null) {
+            return;
+        }
+        removed.owner.getLifecycle().removeObserver(removed);
+        removed.activeStateChanged(false);
+    }
+
+    /**
+     * Removes all observers that are tied to the given {@link LifecycleOwner}.
+     *
+     * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
+     */
+    @MainThread
+    public void removeObservers(final LifecycleOwner owner) {
+        assertMainThread("removeObservers");
+        for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
+            if (entry.getValue().owner == owner) {
+                removeObserver(entry.getKey());
+            }
+        }
+    }
+
+    /**
+     * Posts a task to a main thread to set the given value. So if you have a following code
+     * executed in the main thread:
+     * <pre class="prettyprint">
+     * liveData.postValue("a");
+     * liveData.setValue("b");
+     * </pre>
+     * The value "b" would be set at first and later the main thread would override it with
+     * the value "a".
+     * <p>
+     * If you called this method multiple times before a main thread executed a posted task, only
+     * the last value would be dispatched.
+     *
+     * @param value The new value
+     */
+    protected void postValue(T value) {
+        boolean postTask;
+        synchronized (mDataLock) {
+            postTask = mPendingData == NOT_SET;
+            mPendingData = value;
+        }
+        if (!postTask) {
+            return;
+        }
+        AppToolkitTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
+    }
+
+    /**
+     * Sets the value. If there are active observers, the value will be dispatched to them.
+     * <p>
+     * This method must be called from the main thread. If you need set a value from a background
+     * thread, you can use {@link #postValue(Object)}
+     *
+     * @param value The new value
+     */
+    @MainThread
+    protected void setValue(T value) {
+        assertMainThread("setValue");
+        mVersion++;
+        mData = value;
+        dispatchingValue(null);
+    }
+
+    /**
+     * Returns the current value.
+     * Note that calling this method on a background thread does not guarantee that the latest
+     * value set will be received.
+     *
+     * @return the current value
+     */
+    @Nullable
+    public T getValue() {
+        Object data = mData;
+        if (data != NOT_SET) {
+            //noinspection unchecked
+            return (T) data;
+        }
+        return null;
+    }
+
+    int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Called when the number of active observers change to 1 from 0.
+     * <p>
+     * This callback can be used to know that this LiveData is being used thus should be kept
+     * up to date.
+     */
+    protected void onActive() {
+
+    }
+
+    /**
+     * Called when the number of active observers change from 1 to 0.
+     * <p>
+     * This does not mean that there are no observers left, there may still be observers but their
+     * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
+     * (like an Activity in the back stack).
+     * <p>
+     * You can check if there are observers via {@link #hasObservers()}.
+     */
+    protected void onInactive() {
+
+    }
+
+    /**
+     * Returns true if this LiveData has observers.
+     *
+     * @return true if this LiveData has observers
+     */
+    public boolean hasObservers() {
+        return mObservers.size() > 0;
+    }
+
+    /**
+     * Returns true if this LiveData has active observers.
+     *
+     * @return true if this LiveData has active observers
+     */
+    public boolean hasActiveObservers() {
+        return mActiveCount > 0;
+    }
+
+    class LifecycleBoundObserver implements LifecycleObserver {
+        public final LifecycleOwner owner;
+        public final Observer<T> observer;
+        public boolean active;
+        public int lastVersion = START_VERSION;
+
+        LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
+            this.owner = owner;
+            this.observer = observer;
+        }
+
+        @SuppressWarnings("unused")
+        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+        void onStateChange() {
+            if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+                removeObserver(observer);
+                return;
+            }
+            // immediately set active state, so we'd never dispatch anything to inactive
+            // owner
+            activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
+
+        }
+
+        void activeStateChanged(boolean newActive) {
+            if (newActive == active) {
+                return;
+            }
+            active = newActive;
+            boolean wasInactive = LiveData.this.mActiveCount == 0;
+            LiveData.this.mActiveCount += active ? 1 : -1;
+            if (wasInactive && active) {
+                onActive();
+            }
+            if (LiveData.this.mActiveCount == 0 && !active) {
+                onInactive();
+            }
+            if (active) {
+                dispatchingValue(this);
+            }
+        }
+    }
+
+    static boolean isActiveState(State state) {
+        return state.isAtLeast(STARTED);
+    }
+
+    private void assertMainThread(String methodName) {
+        if (!AppToolkitTaskExecutor.getInstance().isMainThread()) {
+            throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+                    + " thread");
+        }
+    }
 }
diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java
index 821eac2..b7c68dd 100644
--- a/android/arch/paging/LivePagedListProvider.java
+++ b/android/arch/paging/LivePagedListProvider.java
@@ -16,112 +16,5 @@
 
 package android.arch.paging;
 
-import android.arch.core.executor.AppToolkitTaskExecutor;
-import android.arch.lifecycle.ComputableLiveData;
-import android.arch.lifecycle.LiveData;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-
-/**
- * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource.
- * <p>
- * Return type for data-loading system of an application or library to produce a
- * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the
- * consumer.
- *
- * @param <Key> Tyep of input valued used to load data from the DataSource. Must be integer if
- *             you're using TiledDataSource.
- * @param <Value> Data type produced by the DataSource, and held by the PagedLists.
- *
- * @see PagedListAdapter
- * @see DataSource
- * @see PagedList
- */
-public abstract class LivePagedListProvider<Key, Value> {
-
-    /**
-     * Construct a new data source to be wrapped in a new PagedList, which will be returned
-     * through the LiveData.
-     *
-     * @return The data source.
-     */
-    @WorkerThread
-    protected abstract DataSource<Key, Value> createDataSource();
-
-    /**
-     * Creates a LiveData of PagedLists, given the page size.
-     * <p>
-     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
-     * {@link android.support.v7.widget.RecyclerView}.
-     *
-     * @param initialLoadKey Initial key used to load initial data from the data source.
-     * @param pageSize Page size defining how many items are loaded from a data source at a time.
-     *                 Recommended to be multiple times the size of item displayed at once.
-     *
-     * @return The LiveData of PagedLists.
-     */
-    public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
-        return create(initialLoadKey,
-                new PagedList.Config.Builder()
-                        .setPageSize(pageSize)
-                        .build());
-    }
-
-    /**
-     * Creates a LiveData of PagedLists, given the PagedList.Config.
-     * <p>
-     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
-     * {@link android.support.v7.widget.RecyclerView}.
-     *
-     * @param initialLoadKey Initial key to pass to the data source to initialize data with.
-     * @param config PagedList.Config to use with created PagedLists. This specifies how the
-     *               lists will load data.
-     *
-     * @return The LiveData of PagedLists.
-     */
-    public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey,
-            final PagedList.Config config) {
-        return new ComputableLiveData<PagedList<Value>>() {
-            @Nullable
-            private PagedList<Value> mList;
-            @Nullable
-            private DataSource<Key, Value> mDataSource;
-
-            private final DataSource.InvalidatedCallback mCallback =
-                    new DataSource.InvalidatedCallback() {
-                @Override
-                public void onInvalidated() {
-                    invalidate();
-                }
-            };
-
-            @Override
-            protected PagedList<Value> compute() {
-                @Nullable Key initializeKey = initialLoadKey;
-                if (mList != null) {
-                    //noinspection unchecked
-                    initializeKey = (Key) mList.getLastKey();
-                }
-
-                do {
-                    if (mDataSource != null) {
-                        mDataSource.removeInvalidatedCallback(mCallback);
-                    }
-
-                    mDataSource = createDataSource();
-                    mDataSource.addInvalidatedCallback(mCallback);
-
-                    mList = new PagedList.Builder<Key, Value>()
-                            .setDataSource(mDataSource)
-                            .setMainThreadExecutor(AppToolkitTaskExecutor.getMainThreadExecutor())
-                            .setBackgroundThreadExecutor(
-                                    AppToolkitTaskExecutor.getIOThreadExecutor())
-                            .setConfig(config)
-                            .setInitialKey(initializeKey)
-                            .build();
-                } while (mList.isDetached());
-                return mList;
-            }
-        }.getLiveData();
-    }
-}
+abstract public class LivePagedListProvider<K, T> {
+}
\ No newline at end of file
diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java
index 73c1b64..19a0c55 100644
--- a/android/arch/paging/PagedListAdapter.java
+++ b/android/arch/paging/PagedListAdapter.java
@@ -44,7 +44,7 @@
  * {@literal @}Dao
  * interface UserDao {
  *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- *     public abstract LivePagedListProvider&lt;User> usersByLastName();
+ *     public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
  * }
  *
  * class MyViewModel extends ViewModel {
@@ -58,7 +58,7 @@
  *     }
  * }
  *
- * class MyActivity extends Activity implements LifecycleRegistryOwner {
+ * class MyActivity extends AppCompatActivity {
  *     {@literal @}Override
  *     public void onCreate(Bundle savedState) {
  *         super.onCreate(savedState);
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index 7d7a922..4bff5fc 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -50,7 +50,7 @@
  * {@literal @}Dao
  * interface UserDao {
  *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- *     public abstract LivePagedListProvider&lt;User> usersByLastName();
+ *     public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
  * }
  *
  * class MyViewModel extends ViewModel {
@@ -64,7 +64,7 @@
  *     }
  * }
  *
- * class MyActivity extends Activity implements LifecycleRegistryOwner {
+ * class MyActivity extends AppCompatActivity {
  *     {@literal @}Override
  *     public void onCreate(Bundle savedState) {
  *         super.onCreate(savedState);
diff --git a/android/arch/paging/integration/testapp/PagedListSampleActivity.java b/android/arch/paging/integration/testapp/PagedListSampleActivity.java
index f002218..5d0117d 100644
--- a/android/arch/paging/integration/testapp/PagedListSampleActivity.java
+++ b/android/arch/paging/integration/testapp/PagedListSampleActivity.java
@@ -17,7 +17,6 @@
 package android.arch.paging.integration.testapp;
 
 import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
 import android.arch.lifecycle.Observer;
 import android.arch.lifecycle.ViewModelProviders;
 import android.arch.paging.PagedList;
@@ -31,7 +30,7 @@
 /**
  * Sample NullPaddedList activity with artificial data source.
  */
-public class PagedListSampleActivity extends AppCompatActivity implements LifecycleRegistryOwner {
+public class PagedListSampleActivity extends AppCompatActivity {
 
     @Override
     protected void onCreate(final Bundle savedInstanceState) {
diff --git a/android/arch/persistence/db/SimpleSQLiteQuery.java b/android/arch/persistence/db/SimpleSQLiteQuery.java
index b821176..e2a3829 100644
--- a/android/arch/persistence/db/SimpleSQLiteQuery.java
+++ b/android/arch/persistence/db/SimpleSQLiteQuery.java
@@ -20,7 +20,7 @@
  * A basic implemtation of {@link SupportSQLiteQuery} which receives a query and its args and binds
  * args based on the passed in Object type.
  */
-public class SimpleSQLiteQuery implements SupportSQLiteQuery {
+public final class SimpleSQLiteQuery implements SupportSQLiteQuery {
     private final String mQuery;
     private final Object[] mBindArgs;
 
diff --git a/android/arch/persistence/db/SupportSQLiteQueryBuilder.java b/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
index 183fb0a..52957a3 100644
--- a/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
+++ b/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
@@ -22,7 +22,7 @@
  * A simple query builder to create SQL SELECT queries.
  */
 @SuppressWarnings("unused")
-public class SupportSQLiteQueryBuilder {
+public final class SupportSQLiteQueryBuilder {
     private static final Pattern sLimitPattern =
             Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
 
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
index 7b4245b..2268f45 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -23,7 +23,7 @@
  * framework.
  */
 @SuppressWarnings("unused")
-public class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
+public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
     @Override
     public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
         return new FrameworkSQLiteOpenHelper(
diff --git a/android/arch/persistence/room/ColumnInfo.java b/android/arch/persistence/room/ColumnInfo.java
index 84f5844..65da379 100644
--- a/android/arch/persistence/room/ColumnInfo.java
+++ b/android/arch/persistence/room/ColumnInfo.java
@@ -33,6 +33,7 @@
 public @interface ColumnInfo {
     /**
      * Name of the column in the database. Defaults to the field name if not set.
+     *
      * @return Name of the column in the database.
      */
     String name() default INHERIT_FIELD_NAME;
@@ -40,13 +41,14 @@
     /**
      * The type affinity for the column, which will be used when constructing the database.
      * <p>
-     * If it is not specified, Room resolves it based on the field's type and available
-     * TypeConverters.
+     * If it is not specified, the value defaults to {@link #UNDEFINED} and Room resolves it based
+     * on the field's type and available TypeConverters.
      * <p>
      * See <a href="https://www.sqlite.org/datatype3.html">SQLite types documentation</a> for
      * details.
      *
-     * @return The type affinity of the column.
+     * @return The type affinity of the column. This is either {@link #UNDEFINED}, {@link #TEXT},
+     * {@link #INTEGER}, {@link #REAL}, or {@link #BLOB}.
      */
     @SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED;
 
@@ -60,6 +62,17 @@
     boolean index() default false;
 
     /**
+     * The collation sequence for the column, which will be used when constructing the database.
+     * <p>
+     * The default value is {@link #UNSPECIFIED}. In that case, Room does not add any
+     * collation sequence to the column, and SQLite treats it like {@link #BINARY}.
+     *
+     * @return The collation sequence of the column. This is either {@link #UNSPECIFIED},
+     * {@link #BINARY}, {@link #NOCASE}, or {@link #RTRIM}.
+     */
+    @Collate int collate() default UNSPECIFIED;
+
+    /**
      * Constant to let Room inherit the field name as the column name. If used, Room will use the
      * field name as the column name.
      */
@@ -67,22 +80,32 @@
 
     /**
      * Undefined type affinity. Will be resolved based on the type.
+     *
+     * @see #typeAffinity()
      */
     int UNDEFINED = 1;
     /**
      * Column affinity constant for strings.
+     *
+     * @see #typeAffinity()
      */
     int TEXT = 2;
     /**
      * Column affinity constant for integers or booleans.
+     *
+     * @see #typeAffinity()
      */
     int INTEGER = 3;
     /**
      * Column affinity constant for floats or doubles.
+     *
+     * @see #typeAffinity()
      */
     int REAL = 4;
     /**
      * Column affinity constant for binary data.
+     *
+     * @see #typeAffinity()
      */
     int BLOB = 5;
 
@@ -92,4 +115,34 @@
     @IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
     @interface SQLiteTypeAffinity {
     }
+
+    /**
+     * Collation sequence is not specified. The match will behave like {@link #BINARY}.
+     *
+     * @see #collate()
+     */
+    int UNSPECIFIED = 1;
+    /**
+     * Collation sequence for case-sensitive match.
+     *
+     * @see #collate()
+     */
+    int BINARY = 2;
+    /**
+     * Collation sequence for case-insensitive match.
+     *
+     * @see #collate()
+     */
+    int NOCASE = 3;
+    /**
+     * Collation sequence for case-sensitive match except that trailing space characters are
+     * ignored.
+     *
+     * @see #collate()
+     */
+    int RTRIM = 4;
+
+    @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM})
+    @interface Collate {
+    }
 }
diff --git a/android/arch/persistence/room/Relation.java b/android/arch/persistence/room/Relation.java
index 0d2f152..7206699 100644
--- a/android/arch/persistence/room/Relation.java
+++ b/android/arch/persistence/room/Relation.java
@@ -41,7 +41,7 @@
  *
  * {@literal @}Dao
  * public interface UserPetDao {
- *     {@literal @}Query("SELECT id, name from User WHERE age &gte; ?")
+ *     {@literal @}Query("SELECT id, name from User WHERE age &gt; :minAge")
  *     public List&lt;UserNameAndAllPets&gt; loadUserAndPets(int minAge);
  * }
  * </pre>
@@ -67,7 +67,7 @@
  * }
  * {@literal @}Dao
  * public interface UserPetDao {
- *     {@literal @}Query("SELECT * from User WHERE age &gte; ?")
+ *     {@literal @}Query("SELECT * from User WHERE age &gt; :minAge")
  *     public List&lt;UserAllPets&gt; loadUserAndPets(int minAge);
  * }
  * </pre>
diff --git a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
index 5650829..818c46b 100644
--- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
@@ -17,7 +17,6 @@
 package android.arch.persistence.room.integration.testapp;
 
 import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
 import android.arch.lifecycle.LiveData;
 import android.arch.lifecycle.Observer;
 import android.arch.lifecycle.ViewModelProviders;
@@ -35,7 +34,7 @@
 /**
  * Sample PagedList activity which uses Room.
  */
-public class RoomPagedListActivity extends AppCompatActivity implements LifecycleRegistryOwner {
+public class RoomPagedListActivity extends AppCompatActivity {
 
     private RecyclerView mRecyclerView;
     private PagedListCustomerAdapter mAdapter;
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 428d87c..337c233 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -59,6 +59,9 @@
     @Query("select * from user where mId IN(:ids)")
     public abstract User[] loadByIds(int... ids);
 
+    @Query("select * from user where custommm = :customField")
+    public abstract List<User> findByCustomField(String customField);
+
     @Insert
     public abstract void insert(User user);
 
diff --git a/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java b/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
index 4a95ad8..ef207cf 100644
--- a/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
+++ b/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
@@ -17,6 +17,7 @@
 package android.arch.persistence.room.integration.testapp.migration;
 
 import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.ColumnInfo;
 import android.arch.persistence.room.Dao;
 import android.arch.persistence.room.Database;
 import android.arch.persistence.room.Entity;
@@ -75,6 +76,7 @@
         public static final String TABLE_NAME = "Entity4";
         @PrimaryKey
         public int id;
+        @ColumnInfo(collate = ColumnInfo.NOCASE)
         public String name;
     }
 
diff --git a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
index aa297ed..725d53f 100644
--- a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
+++ b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
@@ -315,7 +315,7 @@
         @Override
         public void migrate(SupportSQLiteDatabase database) {
             database.execSQL("CREATE TABLE IF NOT EXISTS " + MigrationDb.Entity4.TABLE_NAME
-                    + " (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`),"
+                    + " (`id` INTEGER NOT NULL, `name` TEXT COLLATE NOCASE, PRIMARY KEY(`id`),"
                     + " FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`)"
                     + " ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)");
         }
diff --git a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index 2b4a0e9..8861adb 100644
--- a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -265,6 +265,16 @@
     }
 
     @Test
+    public void findByCollateNoCase() {
+        User user = TestUtil.createUser(3);
+        user.setCustomField("abc");
+        mUserDao.insert(user);
+        List<User> users = mUserDao.findByCustomField("ABC");
+        assertThat(users, hasSize(1));
+        assertThat(users.get(0).getId(), is(3));
+    }
+
+    @Test
     public void deleteByAge() {
         User user1 = TestUtil.createUser(3);
         user1.setAge(30);
diff --git a/android/arch/persistence/room/integration/testapp/vo/User.java b/android/arch/persistence/room/integration/testapp/vo/User.java
index 57cf585..a5b8839 100644
--- a/android/arch/persistence/room/integration/testapp/vo/User.java
+++ b/android/arch/persistence/room/integration/testapp/vo/User.java
@@ -43,7 +43,7 @@
 
     private Date mBirthday;
 
-    @ColumnInfo(name = "custommm")
+    @ColumnInfo(name = "custommm", collate = ColumnInfo.NOCASE)
     private String mCustomField;
 
     public int getId() {
diff --git a/android/bluetooth/BluetoothGatt.java b/android/bluetooth/BluetoothGatt.java
index 759d772..a2af342 100644
--- a/android/bluetooth/BluetoothGatt.java
+++ b/android/bluetooth/BluetoothGatt.java
@@ -42,7 +42,7 @@
     private static final boolean VDBG = false;
 
     private IBluetoothGatt mService;
-    private BluetoothGattCallback mCallback;
+    private volatile BluetoothGattCallback mCallback;
     private Handler mHandler;
     private int mClientIf;
     private BluetoothDevice mDevice;
@@ -164,8 +164,9 @@
                         runOrQueueCallback(new Runnable() {
                             @Override
                             public void run() {
-                                if (mCallback != null) {
-                                    mCallback.onConnectionStateChange(BluetoothGatt.this,
+                                final BluetoothGattCallback callback = mCallback;
+                                if (callback != null) {
+                                    callback.onConnectionStateChange(BluetoothGatt.this,
                                             GATT_FAILURE,
                                             BluetoothProfile.STATE_DISCONNECTED);
                                 }
@@ -203,8 +204,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
                             }
                         }
                     });
@@ -227,8 +229,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
                             }
                         }
                     });
@@ -254,8 +257,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onConnectionStateChange(BluetoothGatt.this, status,
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onConnectionStateChange(BluetoothGatt.this, status,
                                         profileState);
                             }
                         }
@@ -307,8 +311,7 @@
 
                         for (BluetoothGattService brokenRef : includedServices) {
                             BluetoothGattService includedService = getService(mDevice,
-                                    brokenRef.getUuid(), brokenRef.getInstanceId(),
-                                    brokenRef.getType());
+                                    brokenRef.getUuid(), brokenRef.getInstanceId());
                             if (includedService != null) {
                                 fixedService.addIncludedService(includedService);
                             } else {
@@ -320,8 +323,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onServicesDiscovered(BluetoothGatt.this, status);
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onServicesDiscovered(BluetoothGatt.this, status);
                             }
                         }
                     });
@@ -371,13 +375,13 @@
                         return;
                     }
 
-                    if (status == 0) characteristic.setValue(value);
-
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic,
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                if (status == 0) characteristic.setValue(value);
+                                callback.onCharacteristicRead(BluetoothGatt.this, characteristic,
                                         status);
                             }
                         }
@@ -429,8 +433,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
                                         status);
                             }
                         }
@@ -454,13 +459,13 @@
                             handle);
                     if (characteristic == null) return;
 
-                    characteristic.setValue(value);
-
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onCharacteristicChanged(BluetoothGatt.this,
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                characteristic.setValue(value);
+                                callback.onCharacteristicChanged(BluetoothGatt.this,
                                         characteristic);
                             }
                         }
@@ -489,7 +494,6 @@
                     BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
                     if (descriptor == null) return;
 
-                    if (status == 0) descriptor.setValue(value);
 
                     if ((status == GATT_INSUFFICIENT_AUTHENTICATION
                             || status == GATT_INSUFFICIENT_ENCRYPTION)
@@ -510,8 +514,10 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                if (status == 0) descriptor.setValue(value);
+                                callback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
                             }
                         }
                     });
@@ -559,8 +565,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
                             }
                         }
                     });
@@ -587,8 +594,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onReliableWriteCompleted(BluetoothGatt.this, status);
                             }
                         }
                     });
@@ -610,8 +618,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
                             }
                         }
                     });
@@ -634,8 +643,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onMtuChanged(BluetoothGatt.this, mtu, status);
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onMtuChanged(BluetoothGatt.this, mtu, status);
                             }
                         }
                     });
@@ -660,8 +670,9 @@
                     runOrQueueCallback(new Runnable() {
                         @Override
                         public void run() {
-                            if (mCallback != null) {
-                                mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
                                         timeout, status);
                             }
                         }
@@ -702,10 +713,9 @@
      * @hide
      */
     /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
-            int instanceId, int type) {
+            int instanceId) {
         for (BluetoothGattService svc : mServices) {
             if (svc.getDevice().equals(device)
-                    && svc.getType() == type
                     && svc.getInstanceId() == instanceId
                     && svc.getUuid().equals(uuid)) {
                 return svc;
@@ -901,7 +911,7 @@
 
     /**
      * Set the preferred connection PHY for this app. Please note that this is just a
-     * recommendation, whether the PHY change will happen depends on other applications peferences,
+     * recommendation, whether the PHY change will happen depends on other applications preferences,
      * local and remote controller capabilities. Controller can override these settings.
      * <p>
      * {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
diff --git a/android/bluetooth/BluetoothGattServerCallback.java b/android/bluetooth/BluetoothGattServerCallback.java
index 22eba35..2c8114b 100644
--- a/android/bluetooth/BluetoothGattServerCallback.java
+++ b/android/bluetooth/BluetoothGattServerCallback.java
@@ -184,7 +184,7 @@
     /**
      * Callback indicating the connection parameters were updated.
      *
-     * @param gatt The remote device involved
+     * @param device The remote device involved
      * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
      * 6 (7.5ms) to 3200 (4000ms).
      * @param latency Slave latency for the connection in number of connection events. Valid range
@@ -195,7 +195,7 @@
      * successfully
      * @hide
      */
-    public void onConnectionUpdated(BluetoothDevice gatt, int interval, int latency, int timeout,
+    public void onConnectionUpdated(BluetoothDevice device, int interval, int latency, int timeout,
             int status) {
     }
 
diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java
index 664bcbc..d73f852 100644
--- a/android/content/pm/ApplicationInfo.java
+++ b/android/content/pm/ApplicationInfo.java
@@ -586,24 +586,32 @@
      */
     public static final int PRIVATE_FLAG_VIRTUAL_PRELOAD = 1 << 16;
 
+    /**
+     * Value for {@linl #privateFlags}: whether this app is pre-installed on the
+     * OEM partition of the system image.
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_OEM = 1 << 17;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
-            PRIVATE_FLAG_HIDDEN,
+            PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
+            PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION,
+            PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE,
+            PRIVATE_FLAG_BACKUP_IN_FOREGROUND,
             PRIVATE_FLAG_CANT_SAVE_STATE,
-            PRIVATE_FLAG_FORWARD_LOCK,
-            PRIVATE_FLAG_PRIVILEGED,
-            PRIVATE_FLAG_HAS_DOMAIN_URLS,
             PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
             PRIVATE_FLAG_DIRECT_BOOT_AWARE,
+            PRIVATE_FLAG_FORWARD_LOCK,
+            PRIVATE_FLAG_HAS_DOMAIN_URLS,
+            PRIVATE_FLAG_HIDDEN,
             PRIVATE_FLAG_INSTANT,
-            PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE,
-            PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER,
-            PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
-            PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE,
-            PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION,
-            PRIVATE_FLAG_BACKUP_IN_FOREGROUND,
-            PRIVATE_FLAG_STATIC_SHARED_LIBRARY,
             PRIVATE_FLAG_ISOLATED_SPLIT_LOADING,
+            PRIVATE_FLAG_OEM,
+            PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE,
+            PRIVATE_FLAG_PRIVILEGED,
+            PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER,
+            PRIVATE_FLAG_STATIC_SHARED_LIBRARY,
             PRIVATE_FLAG_VIRTUAL_PRELOAD,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -1557,6 +1565,13 @@
     /**
      * @hide
      */
+    public boolean isOem() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+    }
+
+    /**
+     * @hide
+     */
     @Override protected ApplicationInfo getApplicationInfo() {
         return this;
     }
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index 8b54b0d..6c7c8a0 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -96,8 +96,8 @@
 import com.android.internal.util.XmlUtils;
 
 import libcore.io.IoUtils;
-
 import libcore.util.EmptyArray;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -815,21 +815,22 @@
         }
     }
 
-    public final static int PARSE_IS_SYSTEM = 1<<0;
-    public final static int PARSE_CHATTY = 1<<1;
-    public final static int PARSE_MUST_BE_APK = 1<<2;
-    public final static int PARSE_IGNORE_PROCESSES = 1<<3;
-    public final static int PARSE_FORWARD_LOCK = 1<<4;
-    public final static int PARSE_EXTERNAL_STORAGE = 1<<5;
-    public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
-    public final static int PARSE_IS_PRIVILEGED = 1<<7;
-    public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
-    public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
-    public final static int PARSE_ENFORCE_CODE = 1<<10;
+    public static final int PARSE_IS_SYSTEM = 1 << 0;
+    public static final int PARSE_CHATTY = 1 << 1;
+    public static final int PARSE_MUST_BE_APK = 1 << 2;
+    public static final int PARSE_IGNORE_PROCESSES = 1 << 3;
+    public static final int PARSE_FORWARD_LOCK = 1 << 4;
+    public static final int PARSE_EXTERNAL_STORAGE = 1 << 5;
+    public static final int PARSE_IS_SYSTEM_DIR = 1 << 6;
+    public static final int PARSE_IS_PRIVILEGED = 1 << 7;
+    public static final int PARSE_COLLECT_CERTIFICATES = 1 << 8;
+    public static final int PARSE_TRUSTED_OVERLAY = 1 << 9;
+    public static final int PARSE_ENFORCE_CODE = 1 << 10;
     /** @deprecated remove when fixing b/34761192 */
     @Deprecated
-    public final static int PARSE_IS_EPHEMERAL = 1<<11;
-    public final static int PARSE_FORCE_SDK = 1<<12;
+    public static final int PARSE_IS_EPHEMERAL = 1 << 11;
+    public static final int PARSE_FORCE_SDK = 1 << 12;
+    public static final int PARSE_IS_OEM = 1 << 13;
 
     private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
 
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
index b84c1b9..17b4f87 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -17,7 +17,6 @@
 package android.content.pm;
 
 import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -135,6 +134,16 @@
     public static final int PROTECTION_FLAG_RUNTIME_ONLY = 0x2000;
 
     /**
+     * Additional flag for {@link #protectionLevel}, corresponding
+     * to the <code>oem</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PROTECTION_FLAG_OEM = 0x4000;
+
+    /**
      * Mask for {@link #protectionLevel}: the basic protection type.
      */
     public static final int PROTECTION_MASK_BASE = 0xf;
@@ -222,7 +231,7 @@
     /** @hide */
     public static String protectionToString(int level) {
         String protLevel = "????";
-        switch (level&PROTECTION_MASK_BASE) {
+        switch (level & PROTECTION_MASK_BASE) {
             case PermissionInfo.PROTECTION_DANGEROUS:
                 protLevel = "dangerous";
                 break;
@@ -236,36 +245,39 @@
                 protLevel = "signatureOrSystem";
                 break;
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
             protLevel += "|privileged";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
             protLevel += "|development";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
             protLevel += "|appop";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_PRE23) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_PRE23) != 0) {
             protLevel += "|pre23";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0) {
             protLevel += "|installer";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0) {
             protLevel += "|verifier";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0) {
             protLevel += "|preinstalled";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_SETUP) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_SETUP) != 0) {
             protLevel += "|setup";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
             protLevel += "|instant";
         }
-        if ((level&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0) {
+        if ((level & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0) {
             protLevel += "|runtime";
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_OEM) != 0) {
+            protLevel += "|oem";
+        }
         return protLevel;
     }
 
diff --git a/android/graphics/BitmapFactory.java b/android/graphics/BitmapFactory.java
index 3b272c8..ffb39e3 100644
--- a/android/graphics/BitmapFactory.java
+++ b/android/graphics/BitmapFactory.java
@@ -433,10 +433,15 @@
         static void validate(Options opts) {
             if (opts == null) return;
 
-            if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
+            if (opts.inBitmap != null && opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) {
                 throw new IllegalArgumentException("Bitmaps with Config.HARWARE are always immutable");
             }
 
+            if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
+                throw new IllegalArgumentException("Bitmaps with Config.HARDWARE cannot be " +
+                        "decoded into - they are immutable");
+            }
+
             if (opts.inPreferredColorSpace != null) {
                 if (!(opts.inPreferredColorSpace instanceof ColorSpace.Rgb)) {
                     throw new IllegalArgumentException("The destination color space must use the " +
diff --git a/android/graphics/drawable/VectorDrawable.java b/android/graphics/drawable/VectorDrawable.java
index c3ef450..ceac325 100644
--- a/android/graphics/drawable/VectorDrawable.java
+++ b/android/graphics/drawable/VectorDrawable.java
@@ -31,6 +31,7 @@
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.graphics.Shader;
+import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -605,38 +606,44 @@
     public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
             @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
-        if (mVectorState.mRootGroup != null || mVectorState.mNativeTree != null) {
-            // This VD has been used to display other VD resource content, clean up.
-            if (mVectorState.mRootGroup != null) {
-                // Subtract the native allocation for all the nodes.
-                VMRuntime.getRuntime().registerNativeFree(mVectorState.mRootGroup.getNativeSize());
-                // Remove child nodes' reference to tree
-                mVectorState.mRootGroup.setTree(null);
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "VectorDrawable#inflate");
+            if (mVectorState.mRootGroup != null || mVectorState.mNativeTree != null) {
+                // This VD has been used to display other VD resource content, clean up.
+                if (mVectorState.mRootGroup != null) {
+                    // Subtract the native allocation for all the nodes.
+                    VMRuntime.getRuntime().registerNativeFree(
+                            mVectorState.mRootGroup.getNativeSize());
+                    // Remove child nodes' reference to tree
+                    mVectorState.mRootGroup.setTree(null);
+                }
+                mVectorState.mRootGroup = new VGroup();
+                if (mVectorState.mNativeTree != null) {
+                    // Subtract the native allocation for the tree wrapper, which contains root node
+                    // as well as rendering related data.
+                    VMRuntime.getRuntime().registerNativeFree(mVectorState.NATIVE_ALLOCATION_SIZE);
+                    mVectorState.mNativeTree.release();
+                }
+                mVectorState.createNativeTree(mVectorState.mRootGroup);
             }
-            mVectorState.mRootGroup = new VGroup();
-            if (mVectorState.mNativeTree != null) {
-                // Subtract the native allocation for the tree wrapper, which contains root node
-                // as well as rendering related data.
-                VMRuntime.getRuntime().registerNativeFree(mVectorState.NATIVE_ALLOCATION_SIZE);
-                mVectorState.mNativeTree.release();
-            }
-            mVectorState.createNativeTree(mVectorState.mRootGroup);
+            final VectorDrawableState state = mVectorState;
+            state.setDensity(Drawable.resolveDensity(r, 0));
+
+            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
+            updateStateFromTypedArray(a);
+            a.recycle();
+
+            mDpiScaledDirty = true;
+
+            state.mCacheDirty = true;
+            inflateChildElements(r, parser, attrs, theme);
+
+            state.onTreeConstructionFinished();
+            // Update local properties.
+            updateLocalState(r);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
-        final VectorDrawableState state = mVectorState;
-        state.setDensity(Drawable.resolveDensity(r, 0));
-
-        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
-        updateStateFromTypedArray(a);
-        a.recycle();
-
-        mDpiScaledDirty = true;
-
-        state.mCacheDirty = true;
-        inflateChildElements(r, parser, attrs, theme);
-
-        state.onTreeConstructionFinished();
-        // Update local properties.
-        updateLocalState(r);
     }
 
     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
diff --git a/android/media/AudioManager.java b/android/media/AudioManager.java
index 1575457..186b265 100644
--- a/android/media/AudioManager.java
+++ b/android/media/AudioManager.java
@@ -3058,7 +3058,11 @@
 
     private final IPlaybackConfigDispatcher mPlayCb = new IPlaybackConfigDispatcher.Stub() {
         @Override
-        public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
+        public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+                boolean flush) {
+            if (flush) {
+                Binder.flushPendingCommands();
+            }
             synchronized(mPlaybackCallbackLock) {
                 if (mPlaybackCallbackList != null) {
                     for (int i=0 ; i < mPlaybackCallbackList.size() ; i++) {
diff --git a/android/media/AudioPlaybackConfiguration.java b/android/media/AudioPlaybackConfiguration.java
index 14bc555..8a36f91 100644
--- a/android/media/AudioPlaybackConfiguration.java
+++ b/android/media/AudioPlaybackConfiguration.java
@@ -212,8 +212,10 @@
      * @hide
      */
     public void init() {
-        if (mIPlayerShell != null) {
-            mIPlayerShell.monitorDeath();
+        synchronized (this) {
+            if (mIPlayerShell != null) {
+                mIPlayerShell.monitorDeath();
+            }
         }
     }
 
@@ -322,7 +324,11 @@
      */
     @SystemApi
     public PlayerProxy getPlayerProxy() {
-        return mIPlayerShell == null ? null : new PlayerProxy(this);
+        final IPlayerShell ips;
+        synchronized (this) {
+            ips = mIPlayerShell;
+        }
+        return ips == null ? null : new PlayerProxy(this);
     }
 
     /**
@@ -330,7 +336,11 @@
      * @return the IPlayer interface for the associated player
      */
     IPlayer getIPlayer() {
-        return mIPlayerShell == null ? null : mIPlayerShell.getIPlayer();
+        final IPlayerShell ips;
+        synchronized (this) {
+            ips = mIPlayerShell;
+        }
+        return ips == null ? null : ips.getIPlayer();
     }
 
     /**
@@ -351,10 +361,14 @@
      * @return true if the state changed, false otherwise
      */
     public boolean handleStateEvent(int event) {
-        final boolean changed = (mPlayerState != event);
-        mPlayerState = event;
-        if ((event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) {
-            mIPlayerShell.release();
+        final boolean changed;
+        synchronized (this) {
+            changed = (mPlayerState != event);
+            mPlayerState = event;
+            if (changed && (event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) {
+                mIPlayerShell.release();
+                mIPlayerShell = null;
+            }
         }
         return changed;
     }
@@ -447,7 +461,11 @@
         dest.writeInt(mClientPid);
         dest.writeInt(mPlayerState);
         mPlayerAttr.writeToParcel(dest, 0);
-        dest.writeStrongInterface(mIPlayerShell == null ? null : mIPlayerShell.getIPlayer());
+        final IPlayerShell ips;
+        synchronized (this) {
+            ips = mIPlayerShell;
+        }
+        dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
     }
 
     private AudioPlaybackConfiguration(Parcel in) {
@@ -479,14 +497,17 @@
     static final class IPlayerShell implements IBinder.DeathRecipient {
 
         final AudioPlaybackConfiguration mMonitor; // never null
-        private IPlayer mIPlayer;
+        private volatile IPlayer mIPlayer;
 
         IPlayerShell(@NonNull AudioPlaybackConfiguration monitor, @NonNull IPlayer iplayer) {
             mMonitor = monitor;
             mIPlayer = iplayer;
         }
 
-        void monitorDeath() {
+        synchronized void monitorDeath() {
+            if (mIPlayer == null) {
+                return;
+            }
             try {
                 mIPlayer.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -509,8 +530,13 @@
             } else if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied"); }
         }
 
-        void release() {
+        synchronized void release() {
+            if (mIPlayer == null) {
+                return;
+            }
             mIPlayer.asBinder().unlinkToDeath(this, 0);
+            mIPlayer = null;
+            Binder.flushPendingCommands();
         }
     }
 
@@ -532,7 +558,7 @@
             case PLAYER_TYPE_HW_SOURCE: return "hardware source";
             case PLAYER_TYPE_EXTERNAL_PROXY: return "external proxy";
             default:
-                return "unknown player type - FIXME";
+                return "unknown player type " + type + " - FIXME";
         }
     }
 
diff --git a/android/media/MediaMuxer.java b/android/media/MediaMuxer.java
index 832b297..91e57ee 100644
--- a/android/media/MediaMuxer.java
+++ b/android/media/MediaMuxer.java
@@ -70,7 +70,7 @@
  <p>
   Per-frame metadata is useful in carrying extra information that correlated with video or audio to
   facilitate offline processing, e.g. gyro signals from the sensor could help video stabilization when
-  doing offline processing. Metaadata track is only supported in MP4 container. When adding a new
+  doing offline processing. Metadata track is only supported in MP4 container. When adding a new
   metadata track, track's mime format must start with prefix "application/", e.g. "applicaton/gyro".
   Metadata's format/layout will be defined by the application. Writing metadata is nearly the same as
   writing video/audio data except that the data will not be from mediacodec. Application just needs
diff --git a/android/media/MediaPlayer.java b/android/media/MediaPlayer.java
index 0d99473..7787d4b 100644
--- a/android/media/MediaPlayer.java
+++ b/android/media/MediaPlayer.java
@@ -2072,6 +2072,20 @@
     private native void _reset();
 
     /**
+     * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be
+     * notified when the presentation time reaches (becomes greater than or equal to)
+     * the value specified.
+     *
+     * @param mediaTimeUs presentation time to get timed event callback at
+     * @hide
+     */
+    public void notifyAt(long mediaTimeUs) {
+        _notifyAt(mediaTimeUs);
+    }
+
+    private native void _notifyAt(long mediaTimeUs);
+
+    /**
      * Sets the audio stream type for this MediaPlayer. See {@link AudioManager}
      * for a list of stream types. Must call this method before prepare() or
      * prepareAsync() in order for the target stream type to become effective
@@ -3155,6 +3169,7 @@
     private static final int MEDIA_PAUSED = 7;
     private static final int MEDIA_STOPPED = 8;
     private static final int MEDIA_SKIPPED = 9;
+    private static final int MEDIA_NOTIFY_TIME = 98;
     private static final int MEDIA_TIMED_TEXT = 99;
     private static final int MEDIA_ERROR = 100;
     private static final int MEDIA_INFO = 200;
@@ -3345,6 +3360,14 @@
                 }
                 // No real default action so far.
                 return;
+
+            case MEDIA_NOTIFY_TIME:
+                    TimeProvider timeProvider = mTimeProvider;
+                    if (timeProvider != null) {
+                        timeProvider.onNotifyTime();
+                    }
+                return;
+
             case MEDIA_TIMED_TEXT:
                 OnTimedTextListener onTimedTextListener = mOnTimedTextListener;
                 if (onTimedTextListener == null)
@@ -5144,19 +5167,16 @@
         private boolean mStopped = true;
         private boolean mBuffering;
         private long mLastReportedTime;
-        private long mTimeAdjustment;
         // since we are expecting only a handful listeners per stream, there is
         // no need for log(N) search performance
         private MediaTimeProvider.OnMediaTimeListener mListeners[];
         private long mTimes[];
-        private long mLastNanoTime;
         private Handler mEventHandler;
         private boolean mRefresh = false;
         private boolean mPausing = false;
         private boolean mSeeking = false;
         private static final int NOTIFY = 1;
         private static final int NOTIFY_TIME = 0;
-        private static final int REFRESH_AND_NOTIFY_TIME = 1;
         private static final int NOTIFY_STOP = 2;
         private static final int NOTIFY_SEEK = 3;
         private static final int NOTIFY_TRACK_DATA = 4;
@@ -5188,13 +5208,11 @@
             mListeners = new MediaTimeProvider.OnMediaTimeListener[0];
             mTimes = new long[0];
             mLastTimeUs = 0;
-            mTimeAdjustment = 0;
         }
 
         private void scheduleNotification(int type, long delayUs) {
             // ignore time notifications until seek is handled
-            if (mSeeking &&
-                    (type == NOTIFY_TIME || type == REFRESH_AND_NOTIFY_TIME)) {
+            if (mSeeking && type == NOTIFY_TIME) {
                 return;
             }
 
@@ -5221,6 +5239,14 @@
         }
 
         /** @hide */
+        public void onNotifyTime() {
+            synchronized (this) {
+                if (DEBUG) Log.d(TAG, "onNotifyTime: ");
+                scheduleNotification(NOTIFY_TIME, 0 /* delay */);
+            }
+        }
+
+        /** @hide */
         public void onPaused(boolean paused) {
             synchronized(this) {
                 if (DEBUG) Log.d(TAG, "onPaused: " + paused);
@@ -5231,7 +5257,7 @@
                 } else {
                     mPausing = paused;  // special handling if player disappeared
                     mSeeking = false;
-                    scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */);
+                    scheduleNotification(NOTIFY_TIME, 0 /* delay */);
                 }
             }
         }
@@ -5241,7 +5267,7 @@
             synchronized (this) {
                 if (DEBUG) Log.d(TAG, "onBuffering: " + buffering);
                 mBuffering = buffering;
-                scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */);
+                scheduleNotification(NOTIFY_TIME, 0 /* delay */);
             }
         }
 
@@ -5438,7 +5464,7 @@
             if (nextTimeUs > nowUs && !mPaused) {
                 // schedule callback at nextTimeUs
                 if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs);
-                scheduleNotification(NOTIFY_TIME, nextTimeUs - nowUs);
+                mPlayer.notifyAt(nextTimeUs);
             } else {
                 mEventHandler.removeMessages(NOTIFY);
                 // no more callbacks
@@ -5449,25 +5475,6 @@
             }
         }
 
-        private long getEstimatedTime(long nanoTime, boolean monotonic) {
-            if (mPaused) {
-                mLastReportedTime = mLastTimeUs + mTimeAdjustment;
-            } else {
-                long timeSinceRead = (nanoTime - mLastNanoTime) / 1000;
-                mLastReportedTime = mLastTimeUs + timeSinceRead;
-                if (mTimeAdjustment > 0) {
-                    long adjustment =
-                        mTimeAdjustment - timeSinceRead / TIME_ADJUSTMENT_RATE;
-                    if (adjustment <= 0) {
-                        mTimeAdjustment = 0;
-                    } else {
-                        mLastReportedTime += adjustment;
-                    }
-                }
-            }
-            return mLastReportedTime;
-        }
-
         public long getCurrentTimeUs(boolean refreshTime, boolean monotonic)
                 throws IllegalStateException {
             synchronized (this) {
@@ -5477,42 +5484,38 @@
                     return mLastReportedTime;
                 }
 
-                long nanoTime = System.nanoTime();
-                if (refreshTime ||
-                        nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) {
-                    try {
-                        mLastTimeUs = mPlayer.getCurrentPosition() * 1000L;
-                        mPaused = !mPlayer.isPlaying() || mBuffering;
-                        if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs);
-                    } catch (IllegalStateException e) {
-                        if (mPausing) {
-                            // if we were pausing, get last estimated timestamp
-                            mPausing = false;
-                            getEstimatedTime(nanoTime, monotonic);
-                            mPaused = true;
-                            if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime);
-                            return mLastReportedTime;
+                try {
+                    mLastTimeUs = mPlayer.getCurrentPosition() * 1000L;
+                    mPaused = !mPlayer.isPlaying() || mBuffering;
+                    if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs);
+                } catch (IllegalStateException e) {
+                    if (mPausing) {
+                        // if we were pausing, get last estimated timestamp
+                        mPausing = false;
+                        if (!monotonic || mLastReportedTime < mLastTimeUs) {
+                            mLastReportedTime = mLastTimeUs;
                         }
-                        // TODO get time when prepared
-                        throw e;
+                        mPaused = true;
+                        if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime);
+                        return mLastReportedTime;
                     }
-                    mLastNanoTime = nanoTime;
-                    if (monotonic && mLastTimeUs < mLastReportedTime) {
-                        /* have to adjust time */
-                        mTimeAdjustment = mLastReportedTime - mLastTimeUs;
-                        if (mTimeAdjustment > 1000000) {
-                            // schedule seeked event if time jumped significantly
-                            // TODO: do this properly by introducing an exception
-                            mStopped = false;
-                            mSeeking = true;
-                            scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
-                        }
-                    } else {
-                        mTimeAdjustment = 0;
+                    // TODO get time when prepared
+                    throw e;
+                }
+                if (monotonic && mLastTimeUs < mLastReportedTime) {
+                    /* have to adjust time */
+                    if (mLastReportedTime - mLastTimeUs > 1000000) {
+                        // schedule seeked event if time jumped significantly
+                        // TODO: do this properly by introducing an exception
+                        mStopped = false;
+                        mSeeking = true;
+                        scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
                     }
+                } else {
+                    mLastReportedTime = mLastTimeUs;
                 }
 
-                return getEstimatedTime(nanoTime, monotonic);
+                return mLastReportedTime;
             }
         }
 
@@ -5526,9 +5529,6 @@
                 if (msg.what == NOTIFY) {
                     switch (msg.arg1) {
                     case NOTIFY_TIME:
-                        notifyTimedEvent(false /* refreshTime */);
-                        break;
-                    case REFRESH_AND_NOTIFY_TIME:
                         notifyTimedEvent(true /* refreshTime */);
                         break;
                     case NOTIFY_STOP:
diff --git a/android/media/MediaRouter.java b/android/media/MediaRouter.java
index 2894e89..fe427a7 100644
--- a/android/media/MediaRouter.java
+++ b/android/media/MediaRouter.java
@@ -193,7 +193,9 @@
                 } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
                     name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
                 } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
-                    name = com.android.internal.R.string.default_media_route_name_hdmi;
+                    name = com.android.internal.R.string.default_audio_route_name_hdmi;
+                } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_USB) != 0) {
+                    name = com.android.internal.R.string.default_audio_route_name_usb;
                 } else {
                     name = com.android.internal.R.string.default_audio_route_name;
                 }
diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java
index f527f77..2c9fb23 100644
--- a/android/net/LinkProperties.java
+++ b/android/net/LinkProperties.java
@@ -70,8 +70,23 @@
      * @hide
      */
     public static class CompareResult<T> {
-        public List<T> removed = new ArrayList<T>();
-        public List<T> added = new ArrayList<T>();
+        public final List<T> removed = new ArrayList<T>();
+        public final List<T> added = new ArrayList<T>();
+
+        public CompareResult() {}
+
+        public CompareResult(Collection<T> oldItems, Collection<T> newItems) {
+            if (oldItems != null) {
+                removed.addAll(oldItems);
+            }
+            if (newItems != null) {
+                for (T newItem : newItems) {
+                    if (!removed.remove(newItem)) {
+                        added.add(newItem);
+                    }
+                }
+            }
+        }
 
         @Override
         public String toString() {
@@ -1000,17 +1015,8 @@
          * are in target but not in mLinkAddresses are placed in the
          * addedAddresses.
          */
-        CompareResult<LinkAddress> result = new CompareResult<LinkAddress>();
-        result.removed = new ArrayList<LinkAddress>(mLinkAddresses);
-        result.added.clear();
-        if (target != null) {
-            for (LinkAddress newAddress : target.getLinkAddresses()) {
-                if (! result.removed.remove(newAddress)) {
-                    result.added.add(newAddress);
-                }
-            }
-        }
-        return result;
+        return new CompareResult<>(mLinkAddresses,
+                target != null ? target.getLinkAddresses() : null);
     }
 
     /**
@@ -1029,18 +1035,7 @@
          * are in target but not in mDnses are placed in the
          * addedAddresses.
          */
-        CompareResult<InetAddress> result = new CompareResult<InetAddress>();
-
-        result.removed = new ArrayList<InetAddress>(mDnses);
-        result.added.clear();
-        if (target != null) {
-            for (InetAddress newAddress : target.getDnsServers()) {
-                if (! result.removed.remove(newAddress)) {
-                    result.added.add(newAddress);
-                }
-            }
-        }
-        return result;
+        return new CompareResult<>(mDnses, target != null ? target.getDnsServers() : null);
     }
 
     /**
@@ -1058,18 +1053,7 @@
          * leaving the routes that are different. And route address which
          * are in target but not in mRoutes are placed in added.
          */
-        CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
-
-        result.removed = getAllRoutes();
-        result.added.clear();
-        if (target != null) {
-            for (RouteInfo r : target.getAllRoutes()) {
-                if (! result.removed.remove(r)) {
-                    result.added.add(r);
-                }
-            }
-        }
-        return result;
+        return new CompareResult<>(getAllRoutes(), target != null ? target.getAllRoutes() : null);
     }
 
     /**
@@ -1087,18 +1071,8 @@
          * leaving the interface names that are different. And interface names which
          * are in target but not in this are placed in added.
          */
-        CompareResult<String> result = new CompareResult<String>();
-
-        result.removed = getAllInterfaceNames();
-        result.added.clear();
-        if (target != null) {
-            for (String r : target.getAllInterfaceNames()) {
-                if (! result.removed.remove(r)) {
-                    result.added.add(r);
-                }
-            }
-        }
-        return result;
+        return new CompareResult<>(getAllInterfaceNames(),
+                target != null ? target.getAllInterfaceNames() : null);
     }
 
 
diff --git a/android/net/metrics/WakeupEvent.java b/android/net/metrics/WakeupEvent.java
new file mode 100644
index 0000000..cbf3fc8
--- /dev/null
+++ b/android/net/metrics/WakeupEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.net.metrics;
+
+/**
+ * An event logged when NFLOG notifies userspace of a wakeup packet for
+ * watched interfaces.
+ * {@hide}
+ */
+public class WakeupEvent {
+    public String iface;
+    public long timestampMs;
+    public int uid;
+
+    @Override
+    public String toString() {
+        return String.format("WakeupEvent(%tT.%tL, %s, uid: %d)",
+                timestampMs, timestampMs, iface, uid);
+    }
+}
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
new file mode 100644
index 0000000..d520b97
--- /dev/null
+++ b/android/net/metrics/WakeupStats.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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.net.metrics;
+
+import android.os.Process;
+import android.os.SystemClock;
+
+/**
+ * An event logged per interface and that aggregates WakeupEvents for that interface.
+ * {@hide}
+ */
+public class WakeupStats {
+
+    private static final int NO_UID = -1;
+
+    public final long creationTimeMs = SystemClock.elapsedRealtime();
+    public final String iface;
+
+    public long totalWakeups = 0;
+    public long rootWakeups = 0;
+    public long systemWakeups = 0;
+    public long nonApplicationWakeups = 0;
+    public long applicationWakeups = 0;
+    public long unroutedWakeups = 0;
+    public long durationSec = 0;
+
+    public WakeupStats(String iface) {
+        this.iface = iface;
+    }
+
+    /** Update durationSec with current time. */
+    public void updateDuration() {
+        durationSec = (SystemClock.elapsedRealtime() - creationTimeMs) / 1000;
+    }
+
+    /** Update wakeup counters for the given WakeupEvent. */
+    public void countEvent(WakeupEvent ev) {
+        totalWakeups++;
+        switch (ev.uid) {
+            case Process.ROOT_UID:
+                rootWakeups++;
+                break;
+            case Process.SYSTEM_UID:
+                systemWakeups++;
+                break;
+            case NO_UID:
+                unroutedWakeups++;
+                break;
+            default:
+                if (ev.uid >= Process.FIRST_APPLICATION_UID) {
+                    applicationWakeups++;
+                } else {
+                    nonApplicationWakeups++;
+                }
+                break;
+        }
+    }
+
+    @Override
+    public String toString() {
+        updateDuration();
+        return new StringBuilder()
+                .append("WakeupStats(").append(iface)
+                .append(", total: ").append(totalWakeups)
+                .append(", root: ").append(rootWakeups)
+                .append(", system: ").append(systemWakeups)
+                .append(", apps: ").append(applicationWakeups)
+                .append(", non-apps: ").append(nonApplicationWakeups)
+                .append(", unrouted: ").append(unroutedWakeups)
+                .append(", ").append(durationSec).append("s)")
+                .toString();
+    }
+}
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index cea5715..66b6b47 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -53,6 +53,8 @@
     private static final String TAG = "BatteryStats";
 
     private static final boolean LOCAL_LOGV = false;
+    /** Fetching RPM stats is too slow to do each time screen changes, so disable it. */
+    protected static final boolean SCREEN_OFF_RPM_STATS_ENABLED = false;
 
     /** @hide */
     public static final String SERVICE_NAME = "batterystats";
@@ -213,8 +215,10 @@
      *   - Fixed bugs in background timers and BLE scan time
      * New in version 25:
      *   - Package wakeup alarms are now on screen-off timebase
+     * New in version 26:
+     *   - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
      */
-    static final String CHECKIN_VERSION = "25";
+    static final String CHECKIN_VERSION = "26";
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -233,6 +237,10 @@
     private static final String CPU_DATA = "cpu";
     private static final String GLOBAL_CPU_FREQ_DATA = "gcf";
     private static final String CPU_TIMES_AT_FREQ_DATA = "ctf";
+    // rpm line is:
+    // BATTERY_STATS_CHECKIN_VERSION, uid, which, "rpm", state/voter name, total time, total count,
+    // screen-off time, screen-off count
+    private static final String RESOURCE_POWER_MANAGER_DATA = "rpm";
     private static final String SENSOR_DATA = "sr";
     private static final String VIBRATOR_DATA = "vib";
     private static final String FOREGROUND_ACTIVITY_DATA = "fg";
@@ -2648,6 +2656,16 @@
 
     public abstract Map<String, ? extends Timer> getKernelWakelockStats();
 
+    /**
+     * Returns Timers tracking the total time of each Resource Power Manager state and voter.
+     */
+    public abstract Map<String, ? extends Timer> getRpmStats();
+    /**
+     * Returns Timers tracking the screen-off time of each Resource Power Manager state and voter.
+     */
+    public abstract Map<String, ? extends Timer> getScreenOffRpmStats();
+
+
     public abstract LongSparseArray<? extends Timer> getKernelMemoryStats();
 
     public abstract void writeToParcelWithoutUids(Parcel out, int flags);
@@ -3309,6 +3327,30 @@
             }
         }
 
+        final Map<String, ? extends Timer> rpmStats = getRpmStats();
+        final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+        if (rpmStats.size() > 0) {
+            for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+                sb.setLength(0);
+                Timer totalTimer = ent.getValue();
+                long timeMs = (totalTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                int count = totalTimer.getCountLocked(which);
+                Timer screenOffTimer = screenOffRpmStats.get(ent.getKey());
+                long screenOffTimeMs = screenOffTimer != null
+                        ? (screenOffTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : 0;
+                int screenOffCount = screenOffTimer != null
+                        ? screenOffTimer.getCountLocked(which) : 0;
+                if (SCREEN_OFF_RPM_STATS_ENABLED) {
+                    dumpLine(pw, 0 /* uid */, category, RESOURCE_POWER_MANAGER_DATA,
+                            "\"" + ent.getKey() + "\"", timeMs, count, screenOffTimeMs,
+                            screenOffCount);
+                } else {
+                    dumpLine(pw, 0 /* uid */, category, RESOURCE_POWER_MANAGER_DATA,
+                            "\"" + ent.getKey() + "\"", timeMs, count);
+                }
+            }
+        }
+
         final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
         helper.create(this);
         helper.refreshStats(which, UserHandle.USER_ALL);
@@ -4570,24 +4612,56 @@
         }
 
         final LongSparseArray<? extends Timer> mMemoryStats = getKernelMemoryStats();
-        pw.println("Memory Stats");
-        for (int i = 0; i < mMemoryStats.size(); i++) {
-            sb.setLength(0);
-            sb.append("Bandwidth ");
-            sb.append(mMemoryStats.keyAt(i));
-            sb.append(" Time ");
-            sb.append(mMemoryStats.valueAt(i).getTotalTimeLocked(rawRealtime, which));
-            pw.println(sb.toString());
+        if (mMemoryStats.size() > 0) {
+            pw.println("  Memory Stats");
+            for (int i = 0; i < mMemoryStats.size(); i++) {
+                sb.setLength(0);
+                sb.append("  Bandwidth ");
+                sb.append(mMemoryStats.keyAt(i));
+                sb.append(" Time ");
+                sb.append(mMemoryStats.valueAt(i).getTotalTimeLocked(rawRealtime, which));
+                pw.println(sb.toString());
+            }
+            pw.println();
+        }
+
+        final Map<String, ? extends Timer> rpmStats = getRpmStats();
+        if (rpmStats.size() > 0) {
+            pw.print(prefix); pw.println("  Resource Power Manager Stats");
+            if (rpmStats.size() > 0) {
+                for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+                    final String timerName = ent.getKey();
+                    final Timer timer = ent.getValue();
+                    printTimer(pw, sb, timer, rawRealtime, which, prefix, timerName);
+                }
+            }
+            pw.println();
+        }
+        if (SCREEN_OFF_RPM_STATS_ENABLED) {
+            final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+            if (screenOffRpmStats.size() > 0) {
+                pw.print(prefix);
+                pw.println("  Resource Power Manager Stats for when screen was off");
+                if (screenOffRpmStats.size() > 0) {
+                    for (Map.Entry<String, ? extends Timer> ent : screenOffRpmStats.entrySet()) {
+                        final String timerName = ent.getKey();
+                        final Timer timer = ent.getValue();
+                        printTimer(pw, sb, timer, rawRealtime, which, prefix, timerName);
+                    }
+                }
+                pw.println();
+            }
         }
 
         final long[] cpuFreqs = getCpuFreqs();
         if (cpuFreqs != null) {
             sb.setLength(0);
-            sb.append("CPU freqs:");
+            sb.append("  CPU freqs:");
             for (int i = 0; i < cpuFreqs.length; ++i) {
                 sb.append(" " + cpuFreqs[i]);
             }
             pw.println(sb.toString());
+            pw.println();
         }
 
         for (int iu=0; iu<NU; iu++) {
diff --git a/android/os/Process.java b/android/os/Process.java
index 9351661..b5d62e5 100644
--- a/android/os/Process.java
+++ b/android/os/Process.java
@@ -19,8 +19,8 @@
 import android.annotation.TestApi;
 import android.system.Os;
 import android.system.OsConstants;
-import android.util.Log;
 import android.webkit.WebViewZygote;
+
 import dalvik.system.VMRuntime;
 
 /**
@@ -423,7 +423,7 @@
      * 
      * When invokeWith is not null, the process will be started as a fresh app
      * and not a zygote fork. Note that this is only allowed for uid 0 or when
-     * debugFlags contains DEBUG_ENABLE_DEBUGGER.
+     * runtimeFlags contains DEBUG_ENABLE_DEBUGGER.
      *
      * @param processClass The class to use as the process's main entry
      *                     point.
@@ -431,7 +431,7 @@
      * @param uid The user-id under which the process will run.
      * @param gid The group-id under which the process will run.
      * @param gids Additional group-ids associated with the process.
-     * @param debugFlags Additional flags.
+     * @param runtimeFlags Additional flags for the runtime.
      * @param targetSdkVersion The target SDK version for the app.
      * @param seInfo null-ok SELinux information for the new process.
      * @param abi non-null the ABI this app should be started with.
@@ -448,7 +448,7 @@
     public static final ProcessStartResult start(final String processClass,
                                   final String niceName,
                                   int uid, int gid, int[] gids,
-                                  int debugFlags, int mountExternal,
+                                  int runtimeFlags, int mountExternal,
                                   int targetSdkVersion,
                                   String seInfo,
                                   String abi,
@@ -457,7 +457,7 @@
                                   String invokeWith,
                                   String[] zygoteArgs) {
         return zygoteProcess.start(processClass, niceName, uid, gid, gids,
-                    debugFlags, mountExternal, targetSdkVersion, seInfo,
+                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
     }
 
@@ -465,7 +465,7 @@
     public static final ProcessStartResult startWebView(final String processClass,
                                   final String niceName,
                                   int uid, int gid, int[] gids,
-                                  int debugFlags, int mountExternal,
+                                  int runtimeFlags, int mountExternal,
                                   int targetSdkVersion,
                                   String seInfo,
                                   String abi,
@@ -474,7 +474,7 @@
                                   String invokeWith,
                                   String[] zygoteArgs) {
         return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
-                    debugFlags, mountExternal, targetSdkVersion, seInfo,
+                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
     }
 
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index e11494d..34c7845 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,114 +16,44 @@
 
 package android.os;
 
-import android.util.Log;
-
-import com.android.internal.os.BinderInternal;
-
-import java.util.HashMap;
 import java.util.Map;
 
-/** @hide */
 public final class ServiceManager {
-    private static final String TAG = "ServiceManager";
-
-    private static IServiceManager sServiceManager;
-    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
-
-    private static IServiceManager getIServiceManager() {
-        if (sServiceManager != null) {
-            return sServiceManager;
-        }
-
-        // Find the service manager
-        sServiceManager = ServiceManagerNative
-                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
-        return sServiceManager;
-    }
 
     /**
      * Returns a reference to a service with the given name.
-     * 
+     *
      * @param name the name of the service to get
      * @return a reference to the service, or <code>null</code> if the service doesn't exist
      */
     public static IBinder getService(String name) {
-        try {
-            IBinder service = sCache.get(name);
-            if (service != null) {
-                return service;
-            } else {
-                return Binder.allowBlocking(getIServiceManager().getService(name));
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in getService", e);
-        }
         return null;
     }
 
     /**
-     * Returns a reference to a service with the given name, or throws
-     * {@link NullPointerException} if none is found.
-     *
-     * @hide
+     * Is not supposed to return null, but that is fine for layoutlib.
      */
     public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
-        final IBinder binder = getService(name);
-        if (binder != null) {
-            return binder;
-        } else {
-            throw new ServiceNotFoundException(name);
-        }
+        throw new ServiceNotFoundException(name);
     }
 
     /**
      * Place a new @a service called @a name into the service
      * manager.
-     * 
+     *
      * @param name the name of the new service
      * @param service the service object
      */
     public static void addService(String name, IBinder service) {
-        try {
-            getIServiceManager().addService(name, service, false);
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in addService", e);
-        }
+        // pass
     }
 
     /**
-     * Place a new @a service called @a name into the service
-     * manager.
-     * 
-     * @param name the name of the new service
-     * @param service the service object
-     * @param allowIsolated set to true to allow isolated sandboxed processes
-     * to access this service
-     */
-    public static void addService(String name, IBinder service, boolean allowIsolated) {
-        try {
-            getIServiceManager().addService(name, service, allowIsolated);
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in addService", e);
-        }
-    }
-    
-    /**
      * Retrieve an existing service called @a name from the
      * service manager.  Non-blocking.
      */
     public static IBinder checkService(String name) {
-        try {
-            IBinder service = sCache.get(name);
-            if (service != null) {
-                return service;
-            } else {
-                return Binder.allowBlocking(getIServiceManager().checkService(name));
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in checkService", e);
-            return null;
-        }
+        return null;
     }
 
     /**
@@ -132,27 +62,21 @@
      * case of an exception
      */
     public static String[] listServices() {
-        try {
-            return getIServiceManager().listServices();
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in listServices", e);
-            return null;
-        }
+        // actual implementation returns null sometimes, so it's ok
+        // to return null instead of an empty list.
+        return null;
     }
 
     /**
      * This is only intended to be called when the process is first being brought
      * up and bound by the activity manager. There is only one thread in the process
      * at that time, so no locking is done.
-     * 
+     *
      * @param cache the cache of service references
      * @hide
      */
     public static void initServiceCache(Map<String, IBinder> cache) {
-        if (sCache.size() != 0) {
-            throw new IllegalStateException("setServiceCache may only be called once");
-        }
-        sCache.putAll(cache);
+        // pass
     }
 
     /**
@@ -163,6 +87,7 @@
      * @hide
      */
     public static class ServiceNotFoundException extends Exception {
+        // identical to the original implementation
         public ServiceNotFoundException(String name) {
             super("No service published for: " + name);
         }
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index 615d3c4..f02631c 100644
--- a/android/os/StrictMode.java
+++ b/android/os/StrictMode.java
@@ -722,7 +722,7 @@
             }
 
             /**
-             * Detect when an {@link java.io.Closeable} or other object with a explict termination
+             * Detect when an {@link java.io.Closeable} or other object with an explicit termination
              * method is finalized without having been closed.
              *
              * <p>You always want to explicitly close such objects to avoid unnecessary resources
diff --git a/android/os/ZygoteProcess.java b/android/os/ZygoteProcess.java
index 7a13ee8..670f794 100644
--- a/android/os/ZygoteProcess.java
+++ b/android/os/ZygoteProcess.java
@@ -20,9 +20,11 @@
 import android.net.LocalSocketAddress;
 import android.util.Log;
 import android.util.Slog;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.Preconditions;
+
 import java.io.BufferedWriter;
 import java.io.DataInputStream;
 import java.io.IOException;
@@ -173,7 +175,7 @@
      *
      * When invokeWith is not null, the process will be started as a fresh app
      * and not a zygote fork. Note that this is only allowed for uid 0 or when
-     * debugFlags contains DEBUG_ENABLE_DEBUGGER.
+     * runtimeFlags contains DEBUG_ENABLE_DEBUGGER.
      *
      * @param processClass The class to use as the process's main entry
      *                     point.
@@ -181,7 +183,7 @@
      * @param uid The user-id under which the process will run.
      * @param gid The group-id under which the process will run.
      * @param gids Additional group-ids associated with the process.
-     * @param debugFlags Additional flags.
+     * @param runtimeFlags Additional flags.
      * @param targetSdkVersion The target SDK version for the app.
      * @param seInfo null-ok SELinux information for the new process.
      * @param abi non-null the ABI this app should be started with.
@@ -196,7 +198,7 @@
     public final Process.ProcessStartResult start(final String processClass,
                                                   final String niceName,
                                                   int uid, int gid, int[] gids,
-                                                  int debugFlags, int mountExternal,
+                                                  int runtimeFlags, int mountExternal,
                                                   int targetSdkVersion,
                                                   String seInfo,
                                                   String abi,
@@ -206,7 +208,7 @@
                                                   String[] zygoteArgs) {
         try {
             return startViaZygote(processClass, niceName, uid, gid, gids,
-                    debugFlags, mountExternal, targetSdkVersion, seInfo,
+                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
@@ -317,7 +319,7 @@
      * @param gid a POSIX gid that the new process shuold setgid() to
      * @param gids null-ok; a list of supplementary group IDs that the
      * new process should setgroup() to.
-     * @param debugFlags Additional flags.
+     * @param runtimeFlags Additional flags for the runtime.
      * @param targetSdkVersion The target SDK version for the app.
      * @param seInfo null-ok SELinux information for the new process.
      * @param abi the ABI the process should use.
@@ -331,7 +333,7 @@
                                                       final String niceName,
                                                       final int uid, final int gid,
                                                       final int[] gids,
-                                                      int debugFlags, int mountExternal,
+                                                      int runtimeFlags, int mountExternal,
                                                       int targetSdkVersion,
                                                       String seInfo,
                                                       String abi,
@@ -347,33 +349,7 @@
         argsForZygote.add("--runtime-args");
         argsForZygote.add("--setuid=" + uid);
         argsForZygote.add("--setgid=" + gid);
-        if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) {
-            argsForZygote.add("--enable-jni-logging");
-        }
-        if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
-            argsForZygote.add("--enable-safemode");
-        }
-        if ((debugFlags & Zygote.DEBUG_ENABLE_JDWP) != 0) {
-            argsForZygote.add("--enable-jdwp");
-        }
-        if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
-            argsForZygote.add("--enable-checkjni");
-        }
-        if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
-            argsForZygote.add("--generate-debug-info");
-        }
-        if ((debugFlags & Zygote.DEBUG_ALWAYS_JIT) != 0) {
-            argsForZygote.add("--always-jit");
-        }
-        if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
-            argsForZygote.add("--native-debuggable");
-        }
-        if ((debugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) {
-            argsForZygote.add("--java-debuggable");
-        }
-        if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
-            argsForZygote.add("--enable-assert");
-        }
+        argsForZygote.add("--runtime-flags=" + runtimeFlags);
         if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
             argsForZygote.add("--mount-external-default");
         } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
diff --git a/android/os/storage/StorageManager.java b/android/os/storage/StorageManager.java
index 7036346..6594cd0 100644
--- a/android/os/storage/StorageManager.java
+++ b/android/os/storage/StorageManager.java
@@ -221,8 +221,6 @@
 
     /** {@hide} */
     public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
-    /** {@hide} */
-    public static final int FSTRIM_FLAG_BENCHMARK = IVold.FSTRIM_FLAG_BENCHMARK_AFTER;
 
     /** @hide The volume is not encrypted. */
     public static final int ENCRYPTION_STATE_NONE =
diff --git a/android/os/storage/VolumeInfo.java b/android/os/storage/VolumeInfo.java
index b8353d7..76f79f1 100644
--- a/android/os/storage/VolumeInfo.java
+++ b/android/os/storage/VolumeInfo.java
@@ -76,21 +76,21 @@
     /** Real volume representing internal emulated storage */
     public static final String ID_EMULATED_INTERNAL = "emulated";
 
-    public static final int TYPE_PUBLIC = IVold.TYPE_PUBLIC;
-    public static final int TYPE_PRIVATE = IVold.TYPE_PRIVATE;
-    public static final int TYPE_EMULATED = IVold.TYPE_EMULATED;
-    public static final int TYPE_ASEC = IVold.TYPE_ASEC;
-    public static final int TYPE_OBB = IVold.TYPE_OBB;
+    public static final int TYPE_PUBLIC = IVold.VOLUME_TYPE_PUBLIC;
+    public static final int TYPE_PRIVATE = IVold.VOLUME_TYPE_PRIVATE;
+    public static final int TYPE_EMULATED = IVold.VOLUME_TYPE_EMULATED;
+    public static final int TYPE_ASEC = IVold.VOLUME_TYPE_ASEC;
+    public static final int TYPE_OBB = IVold.VOLUME_TYPE_OBB;
 
-    public static final int STATE_UNMOUNTED = IVold.STATE_UNMOUNTED;
-    public static final int STATE_CHECKING = IVold.STATE_CHECKING;
-    public static final int STATE_MOUNTED = IVold.STATE_MOUNTED;
-    public static final int STATE_MOUNTED_READ_ONLY = IVold.STATE_MOUNTED_READ_ONLY;
-    public static final int STATE_FORMATTING = IVold.STATE_FORMATTING;
-    public static final int STATE_EJECTING = IVold.STATE_EJECTING;
-    public static final int STATE_UNMOUNTABLE = IVold.STATE_UNMOUNTABLE;
-    public static final int STATE_REMOVED = IVold.STATE_REMOVED;
-    public static final int STATE_BAD_REMOVAL = IVold.STATE_BAD_REMOVAL;
+    public static final int STATE_UNMOUNTED = IVold.VOLUME_STATE_UNMOUNTED;
+    public static final int STATE_CHECKING = IVold.VOLUME_STATE_CHECKING;
+    public static final int STATE_MOUNTED = IVold.VOLUME_STATE_MOUNTED;
+    public static final int STATE_MOUNTED_READ_ONLY = IVold.VOLUME_STATE_MOUNTED_READ_ONLY;
+    public static final int STATE_FORMATTING = IVold.VOLUME_STATE_FORMATTING;
+    public static final int STATE_EJECTING = IVold.VOLUME_STATE_EJECTING;
+    public static final int STATE_UNMOUNTABLE = IVold.VOLUME_STATE_UNMOUNTABLE;
+    public static final int STATE_REMOVED = IVold.VOLUME_STATE_REMOVED;
+    public static final int STATE_BAD_REMOVAL = IVold.VOLUME_STATE_BAD_REMOVAL;
 
     public static final int MOUNT_FLAG_PRIMARY = IVold.MOUNT_FLAG_PRIMARY;
     public static final int MOUNT_FLAG_VISIBLE = IVold.MOUNT_FLAG_VISIBLE;
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index 2cb3864..40ced6c 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -5194,17 +5194,39 @@
         public static final String ALLOW_MOCK_LOCATION = "mock_location";
 
         /**
-         * A 64-bit number (as a hex string) that is randomly
-         * generated when the user first sets up the device and should remain
-         * constant for the lifetime of the user's device. The value may
-         * change if a factory reset is performed on the device.
-         * <p class="note"><strong>Note:</strong> When a device has <a
-         * href="{@docRoot}about/versions/android-4.2.html#MultipleUsers">multiple users</a>
-         * (available on certain devices running Android 4.2 or higher), each user appears as a
-         * completely separate device, so the {@code ANDROID_ID} value is unique to each
-         * user.</p>
+         * On Android 8.0 (API level 26) and higher versions of the platform,
+         * a 64-bit number (expressed as a hexadecimal string), unique to
+         * each combination of app-signing key, user, and device.
+         * Values of {@code ANDROID_ID} are scoped by signing key and user.
+         * The value may change if a factory reset is performed on the
+         * device or if an APK signing key changes.
          *
-         * <p class="note"><strong>Note:</strong> If the caller is an Instant App the id is scoped
+         * For more information about how the platform handles {@code ANDROID_ID}
+         * in Android 8.0 (API level 26) and higher, see <a
+         * href="{@docRoot}preview/behavior-changes.html#privacy-all">
+         * Android 8.0 Behavior Changes</a>.
+         *
+         * <p class="note"><strong>Note:</strong> For apps that were installed
+         * prior to updating the device to a version of Android 8.0
+         * (API level 26) or higher, the value of {@code ANDROID_ID} changes
+         * if the app is uninstalled and then reinstalled after the OTA.
+         * To preserve values across uninstalls after an OTA to Android 8.0
+         * or higher, developers can use
+         * <a href="{@docRoot}guide/topics/data/keyvaluebackup.html">
+         * Key/Value Backup</a>.</p>
+         *
+         * <p>In versions of the platform lower than Android 8.0 (API level 26),
+         * a 64-bit number (expressed as a hexadecimal string) that is randomly
+         * generated when the user first sets up the device and should remain
+         * constant for the lifetime of the user's device.
+         *
+         * On devices that have
+         * <a href="{@docRoot}about/versions/android-4.2.html#MultipleUsers">
+         * multiple users</a>, each user appears as a
+         * completely separate device, so the {@code ANDROID_ID} value is
+         * unique to each user.</p>
+         *
+         * <p class="note"><strong>Note:</strong> If the caller is an Instant App the ID is scoped
          * to the Instant App, it is generated when the Instant App is first installed and reset if
          * the user clears the Instant App.
          */
@@ -7124,6 +7146,31 @@
          * @hide
          */
         public static final String LOCKDOWN_IN_POWER_MENU = "lockdown_in_power_menu";
+
+        /**
+         * Backup manager behavioral parameters.
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "key_value_backup_interval_milliseconds=14400000,key_value_backup_require_charging=true"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * key_value_backup_interval_milliseconds  (long)
+         * key_value_backup_fuzz_milliseconds      (long)
+         * key_value_backup_require_charging       (boolean)
+         * key_value_backup_required_network_type  (int)
+         * full_backup_interval_milliseconds       (long)
+         * full_backup_require_charging            (boolean)
+         * full_backup_required_network_type       (int)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         */
+        public static final String BACKUP_MANAGER_CONSTANTS = "backup_manager_constants";
+
         /**
          * This are the settings to be backed up.
          *
diff --git a/android/service/settings/suggestions/Suggestion.java b/android/service/settings/suggestions/Suggestion.java
new file mode 100644
index 0000000..f27cc2e
--- /dev/null
+++ b/android/service/settings/suggestions/Suggestion.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 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.service.settings.suggestions;
+
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Data object that has information about a device suggestion.
+ *
+ * @hide
+ */
+@SystemApi
+public final class Suggestion implements Parcelable {
+
+    private final String mId;
+    private final CharSequence mTitle;
+    private final CharSequence mSummary;
+    private final PendingIntent mPendingIntent;
+
+    /**
+     * Gets the id for the suggestion object.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Title of the suggestion that is shown to the user.
+     */
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Optional summary describing what this suggestion controls.
+     */
+    public CharSequence getSummary() {
+        return mSummary;
+    }
+
+    /**
+     * The Intent to launch when the suggestion is activated.
+     */
+    public PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    private Suggestion(Builder builder) {
+        mId = builder.mId;
+        mTitle = builder.mTitle;
+        mSummary = builder.mSummary;
+        mPendingIntent = builder.mPendingIntent;
+    }
+
+    private Suggestion(Parcel in) {
+        mId = in.readString();
+        mTitle = in.readCharSequence();
+        mSummary = in.readCharSequence();
+        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+    }
+
+    public static final Creator<Suggestion> CREATOR = new Creator<Suggestion>() {
+        @Override
+        public Suggestion createFromParcel(Parcel in) {
+            return new Suggestion(in);
+        }
+
+        @Override
+        public Suggestion[] newArray(int size) {
+            return new Suggestion[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeCharSequence(mTitle);
+        dest.writeCharSequence(mSummary);
+        dest.writeParcelable(mPendingIntent, flags);
+    }
+
+    /**
+     * Builder class for {@link Suggestion}.
+     */
+    public static class Builder {
+        private final String mId;
+        private CharSequence mTitle;
+        private CharSequence mSummary;
+        private PendingIntent mPendingIntent;
+
+        public Builder(String id) {
+            if (TextUtils.isEmpty(id)) {
+                throw new IllegalArgumentException("Suggestion id cannot be empty");
+            }
+            mId = id;
+        }
+
+        /**
+         * Sets suggestion title
+         */
+        public Builder setTitle(CharSequence title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets suggestion summary
+         */
+        public Builder setSummary(CharSequence summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        /**
+         * Sets suggestion intent
+         */
+        public Builder setPendingIntent(PendingIntent pendingIntent) {
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
+         * Builds an immutable {@link Suggestion} object.
+         */
+        public Suggestion build() {
+            return new Suggestion(this /* builder */);
+        }
+    }
+}
diff --git a/android/service/settings/suggestions/SuggestionService.java b/android/service/settings/suggestions/SuggestionService.java
new file mode 100644
index 0000000..2a4c84c
--- /dev/null
+++ b/android/service/settings/suggestions/SuggestionService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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.service.settings.suggestions;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * This is the base class for implementing suggestion service. A suggestion service is responsible
+ * to provide a collection of {@link Suggestion}s for the current user when queried.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SuggestionService extends Service {
+
+    private static final String TAG = "SuggestionService";
+    private static final boolean DEBUG = false;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new ISuggestionService.Stub() {
+            @Override
+            public List<Suggestion> getSuggestions() {
+                if (DEBUG) {
+                    Log.d(TAG, "getSuggestions() " + getPackageName());
+                }
+                return onGetSuggestions();
+            }
+
+            @Override
+            public  void dismissSuggestion(Suggestion suggestion) {
+                if (DEBUG) {
+                    Log.d(TAG, "dismissSuggestion() " + getPackageName());
+                }
+                onSuggestionDismissed(suggestion);
+            }
+        };
+    }
+
+    /**
+     * Return all available suggestions.
+     */
+    public abstract List<Suggestion> onGetSuggestions();
+
+    /**
+     * Dismiss a suggestion. The suggestion will not be included in future
+     * {@link #onGetSuggestions()} calls.
+     * @param suggestion
+     */
+    public abstract void onSuggestionDismissed(Suggestion suggestion);
+}
diff --git a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
index d0496e4..61ea52b 100644
--- a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
+++ b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
@@ -254,9 +254,7 @@
             // playing    paused                  paused
             // paused     playing       playing
             // ff/rw      playing       playing   paused
-            if (canPause
-                    && (canPlay ? mIsPlaying :
-                    !mIsPlaying)) {
+            if (canPause && mIsPlaying) {
                 mIsPlaying = false;
                 pause();
             } else if (canPlay && !mIsPlaying) {
diff --git a/android/support/v4/app/FragmentManager.java b/android/support/v4/app/FragmentManager.java
index 20e8d9e..6e6caa0 100644
--- a/android/support/v4/app/FragmentManager.java
+++ b/android/support/v4/app/FragmentManager.java
@@ -1594,6 +1594,8 @@
     private void animateRemoveFragment(@NonNull final Fragment fragment,
             @NonNull AnimationOrAnimator anim, final int newState) {
         final View viewToAnimate = fragment.mView;
+        final ViewGroup container = fragment.mContainer;
+        container.startViewTransition(viewToAnimate);
         fragment.setStateAfterAnimating(newState);
         if (anim.animation != null) {
             Animation animation = anim.animation;
@@ -1603,6 +1605,8 @@
                 @Override
                 public void onAnimationEnd(Animation animation) {
                     super.onAnimationEnd(animation);
+                    container.endViewTransition(viewToAnimate);
+
                     if (fragment.getAnimatingAway() != null) {
                         fragment.setAnimatingAway(null);
                         moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false);
@@ -1612,25 +1616,18 @@
             setHWLayerAnimListenerIfAlpha(viewToAnimate, anim);
             fragment.mView.startAnimation(animation);
         } else {
-            final Animator animator = anim.animator;
+            Animator animator = anim.animator;
             fragment.setAnimator(anim.animator);
-            final ViewGroup container = fragment.mContainer;
-            if (container != null) {
-                container.startViewTransition(viewToAnimate);
-            }
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator anim) {
-                    // AnimatorSet in API 26 (only) can end() during start(), so delay by posting
-                    if (container == null || container.indexOfChild(viewToAnimate) < 0) {
-                        finishAnimatedFragmentRemoval(fragment, container, viewToAnimate);
-                    } else {
-                        viewToAnimate.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                finishAnimatedFragmentRemoval(fragment, container, viewToAnimate);
-                            }
-                        });
+                    container.endViewTransition(viewToAnimate);
+                    // If an animator ends immediately, we can just pretend there is no animation.
+                    // When that happens the the fragment's view won't have been removed yet.
+                    Animator animator = fragment.getAnimator();
+                    fragment.setAnimator(null);
+                    if (animator != null && container.indexOfChild(viewToAnimate) < 0) {
+                        moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false);
                     }
                 }
             });
@@ -1640,17 +1637,6 @@
         }
     }
 
-    void finishAnimatedFragmentRemoval(Fragment fragment, ViewGroup container, View view) {
-        if (container != null) {
-            container.endViewTransition(view);
-        }
-        if (fragment.getAnimator() != null) {
-            fragment.setAnimator(null);
-            moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0,
-                    false);
-        }
-    }
-
     void moveToState(Fragment f) {
         moveToState(f, mCurState, 0, 0, false);
     }
@@ -2646,7 +2632,6 @@
                     // Give up waiting for the animation and just end it.
                     final int stateAfterAnimating = fragment.getStateAfterAnimating();
                     final View animatingAway = fragment.getAnimatingAway();
-                    fragment.setAnimatingAway(null);
                     Animation animation = animatingAway.getAnimation();
                     if (animation != null) {
                         animation.cancel();
@@ -2654,6 +2639,7 @@
                         // and will instead cause the animation to infinitely loop
                         animatingAway.clearAnimation();
                     }
+                    fragment.setAnimatingAway(null);
                     moveToState(fragment, stateAfterAnimating, 0, 0, false);
                 } else if (fragment.getAnimator() != null) {
                     fragment.getAnimator().end();
diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java
index 8175aae..53b111a 100644
--- a/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -550,7 +550,7 @@
     /**
      * All the info about a connection.
      */
-    private static class ConnectionRecord {
+    private class ConnectionRecord implements IBinder.DeathRecipient {
         String pkg;
         Bundle rootHints;
         ServiceCallbacks callbacks;
@@ -559,6 +559,16 @@
 
         ConnectionRecord() {
         }
+
+        @Override
+        public void binderDied() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mConnections.remove(callbacks.asBinder());
+                }
+            });
+        }
     }
 
     /**
@@ -747,6 +757,7 @@
                     } else {
                         try {
                             mConnections.put(b, connection);
+                            b.linkToDeath(connection, 0);
                             if (mSession != null) {
                                 callbacks.onConnect(connection.root.getRootId(),
                                         mSession, connection.root.getExtras());
@@ -771,6 +782,7 @@
                     final ConnectionRecord old = mConnections.remove(b);
                     if (old != null) {
                         // TODO
+                        old.callbacks.asBinder().unlinkToDeath(old, 0);
                     }
                 }
             });
@@ -852,6 +864,11 @@
                     connection.callbacks = callbacks;
                     connection.rootHints = rootHints;
                     mConnections.put(b, connection);
+                    try {
+                        b.linkToDeath(connection, 0);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "IBinder is already dead.");
+                    }
                 }
             });
         }
@@ -862,7 +879,10 @@
                 @Override
                 public void run() {
                     final IBinder b = callbacks.asBinder();
-                    mConnections.remove(b);
+                    ConnectionRecord old = mConnections.remove(b);
+                    if (old != null) {
+                        b.unlinkToDeath(old, 0);
+                    }
                 }
             });
         }
diff --git a/android/support/v4/os/BuildCompat.java b/android/support/v4/os/BuildCompat.java
index 9a48c5f..586557d 100644
--- a/android/support/v4/os/BuildCompat.java
+++ b/android/support/v4/os/BuildCompat.java
@@ -60,6 +60,7 @@
      *             be removed in a future release of the Support Library. Instead use
      *             {@code Build.SDK_INT >= Build.VERSION_CODES#O}.
      */
+    @Deprecated
     public static boolean isAtLeastO() {
         return VERSION.SDK_INT >= 26;
     }
@@ -68,7 +69,11 @@
      * Checks if the device is running on a pre-release version of Android O MR1 or newer.
      * <p>
      * @return {@code true} if O MR1 APIs are available for use, {@code false} otherwise
+     * @deprecated Android O MR1 is a finalized release and this method is no longer necessary. It
+     *             will be removed in a future release of the Support Library. Instead, use
+     *             {@code Build.SDK_INT >= Build.VERSION_CODES#O_MR1}.
      */
+    @Deprecated
     public static boolean isAtLeastOMR1() {
         return VERSION.SDK_INT >= 27;
     }
diff --git a/android/support/v7/preference/CollapsiblePreferenceGroupController.java b/android/support/v7/preference/CollapsiblePreferenceGroupController.java
new file mode 100644
index 0000000..e15ca18
--- /dev/null
+++ b/android/support/v7/preference/CollapsiblePreferenceGroupController.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 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.support.v7.preference;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A controller to handle advanced children display logic with collapsible functionality.
+ */
+final class CollapsiblePreferenceGroupController
+        implements PreferenceGroup.PreferenceInstanceStateCallback {
+
+    private final PreferenceGroupAdapter mPreferenceGroupAdapter;
+    private int mMaxPreferenceToShow;
+    private final Context mContext;
+
+    CollapsiblePreferenceGroupController(PreferenceGroup preferenceGroup,
+            PreferenceGroupAdapter preferenceGroupAdapter) {
+        mPreferenceGroupAdapter = preferenceGroupAdapter;
+        mMaxPreferenceToShow = preferenceGroup.getInitialExpandedChildrenCount();
+        mContext = preferenceGroup.getContext();
+        preferenceGroup.setPreferenceInstanceStateCallback(this);
+    }
+
+    /**
+     * Creates the visible portion of the flattened preferences.
+     *
+     * @param flattenedPreferenceList the flattened children of the preference group
+     * @return the visible portion of the flattened preferences
+     */
+    public List<Preference> createVisiblePreferencesList(List<Preference> flattenedPreferenceList) {
+        int visiblePreferenceCount = 0;
+        final List<Preference> visiblePreferenceList =
+                new ArrayList<>(flattenedPreferenceList.size());
+        // Copy only the visible preferences to the active list up to the maximum specified
+        for (final Preference preference : flattenedPreferenceList) {
+            if (preference.isVisible()) {
+                if (visiblePreferenceCount < mMaxPreferenceToShow) {
+                    visiblePreferenceList.add(preference);
+                }
+                // Do no count PreferenceGroup as expanded preference because the list of its child
+                // is already contained in the flattenedPreferenceList
+                if (!(preference instanceof PreferenceGroup)) {
+                    visiblePreferenceCount++;
+                }
+            }
+        }
+        // If there are any visible preferences being hidden, add an expand button to show the rest
+        // of the preferences. Clicking the expand button will show all the visible preferences and
+        // reset mMaxPreferenceToShow
+        if (showLimitedChildren() && visiblePreferenceCount > mMaxPreferenceToShow) {
+            final ExpandButton expandButton  = createExpandButton(visiblePreferenceList,
+                    flattenedPreferenceList);
+            visiblePreferenceList.add(expandButton);
+        }
+        return visiblePreferenceList;
+    }
+
+    /**
+     * Called when a preference has changed its visibility.
+     *
+     * @param preference The preference whose visibility has changed.
+     * @return {@code true} if view update has been handled by this controller.
+     */
+    public boolean onPreferenceVisibilityChange(Preference preference) {
+        if (showLimitedChildren()) {
+            // We only want to show up to the max number of preferences. Preference visibility
+            // change can result in the expand button being added/removed, as well as expand button
+            // summary change. Rebulid the data to ensure the correct data is shown.
+            mPreferenceGroupAdapter.onPreferenceHierarchyChange(preference);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public Parcelable saveInstanceState(Parcelable state) {
+        final SavedState myState = new SavedState(state);
+        myState.mMaxPreferenceToShow = mMaxPreferenceToShow;
+        return myState;
+    }
+
+    @Override
+    public Parcelable restoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in saveInstanceState
+            return state;
+        }
+        SavedState myState = (SavedState) state;
+        final int restoredMaxToShow = myState.mMaxPreferenceToShow;
+        if (mMaxPreferenceToShow != restoredMaxToShow) {
+            mMaxPreferenceToShow = restoredMaxToShow;
+            mPreferenceGroupAdapter.onPreferenceHierarchyChange(null);
+        }
+        return myState.getSuperState();
+    }
+
+    private ExpandButton createExpandButton(List<Preference> visiblePreferenceList,
+            List<Preference> flattenedPreferenceList) {
+        final ExpandButton preference = new ExpandButton(mContext, visiblePreferenceList,
+                flattenedPreferenceList);
+        preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                mMaxPreferenceToShow = Integer.MAX_VALUE;
+                mPreferenceGroupAdapter.onPreferenceHierarchyChange(preference);
+                return true;
+            }
+        });
+        return preference;
+    }
+
+    private boolean showLimitedChildren() {
+        return mMaxPreferenceToShow != Integer.MAX_VALUE;
+    }
+
+    /**
+     * A {@link Preference} that provides capability to expand the collapsed items in the
+     * {@link PreferenceGroup}.
+     */
+    static class ExpandButton extends Preference {
+        ExpandButton(Context context, List<Preference> visiblePreferenceList,
+                List<Preference> flattenedPreferenceList) {
+            super(context);
+            initLayout();
+            setSummary(visiblePreferenceList, flattenedPreferenceList);
+        }
+
+        private void initLayout() {
+            setLayoutResource(R.layout.expand_button);
+            setIcon(R.drawable.ic_arrow_down_24dp);
+            setTitle(R.string.expand_button_title);
+            // Sets a high order so that the expand button will be placed at the bottom of the group
+            setOrder(999);
+        }
+
+        /*
+         * The summary of this will be the list of title for collapsed preferences. Iterate through
+         * the preferences not in the visible list and add its title to the summary text.
+         */
+        private void setSummary(List<Preference> visiblePreferenceList,
+                List<Preference> flattenedPreferenceList) {
+            final Preference lastVisiblePreference =
+                    visiblePreferenceList.get(visiblePreferenceList.size() - 1);
+            final int collapsedIndex = flattenedPreferenceList.indexOf(lastVisiblePreference) + 1;
+            CharSequence summary = null;
+            for (int i = collapsedIndex; i < flattenedPreferenceList.size(); i++) {
+                final Preference preference = flattenedPreferenceList.get(i);
+                if (preference instanceof PreferenceGroup) {
+                    continue;
+                }
+                final CharSequence title = preference.getTitle();
+                if (!TextUtils.isEmpty(title)) {
+                    if (summary == null) {
+                        summary = title;
+                    } else {
+                        summary = getContext().getString(
+                                R.string.summary_collapsed_preference_list, summary, title);
+                    }
+                }
+            }
+            setSummary(summary);
+        }
+
+        @Override
+        public void onBindViewHolder(PreferenceViewHolder holder) {
+            super.onBindViewHolder(holder);
+            holder.setDividerAllowedAbove(false);
+        }
+    }
+
+    /**
+     * A class for managing the instance state of a {@link PreferenceGroup}.
+     */
+    static class SavedState extends Preference.BaseSavedState {
+        int mMaxPreferenceToShow;
+
+        SavedState(Parcel source) {
+            super(source);
+            mMaxPreferenceToShow = source.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(mMaxPreferenceToShow);
+        }
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+}
diff --git a/android/support/v7/preference/PreferenceGroup.java b/android/support/v7/preference/PreferenceGroup.java
index d285ee6..a951e70 100644
--- a/android/support/v7/preference/PreferenceGroup.java
+++ b/android/support/v7/preference/PreferenceGroup.java
@@ -22,7 +22,9 @@
 import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.content.res.TypedArrayUtils;
 import android.support.v4.util.SimpleArrayMap;
 import android.text.TextUtils;
@@ -45,6 +47,7 @@
  * </div>
  *
  * @attr name android:orderingFromXml
+ * @attr name initialExpandedChildrenCount
  */
 public abstract class PreferenceGroup extends Preference {
     /**
@@ -60,6 +63,9 @@
 
     private boolean mAttachedToHierarchy = false;
 
+    private int mInitialExpandedChildrenCount = Integer.MAX_VALUE;
+    private PreferenceInstanceStateCallback mPreferenceInstanceStateCallback;
+
     private final SimpleArrayMap<String, Long> mIdRecycleCache = new SimpleArrayMap<>();
     private final Handler mHandler = new Handler();
     private final Runnable mClearRecycleCacheRunnable = new Runnable() {
@@ -83,6 +89,11 @@
                 TypedArrayUtils.getBoolean(a, R.styleable.PreferenceGroup_orderingFromXml,
                         R.styleable.PreferenceGroup_orderingFromXml, true);
 
+        if (a.hasValue(R.styleable.PreferenceGroup_initialExpandedChildrenCount)) {
+            mInitialExpandedChildrenCount = TypedArrayUtils.getInt(
+                    a, R.styleable.PreferenceGroup_initialExpandedChildrenCount,
+                            R.styleable.PreferenceGroup_initialExpandedChildrenCount, -1);
+        }
         a.recycle();
     }
 
@@ -120,6 +131,35 @@
     }
 
     /**
+     * Sets the maximal number of children that are shown when the preference group is launched
+     * where the rest of the children will be hidden.
+     * If some children are hidden an expand button will be provided to show all the hidden
+     * children. Any child in any level of the hierarchy that is also a preference group (e.g.
+     * preference category) will not be counted towards the limit. But instead the children of such
+     * group will be counted.
+     * By default, all children will be shown, so the default value of this attribute is equal to
+     * Integer.MAX_VALUE.
+     *
+     * @param expandedCount the number of children that is initially shown.
+     *
+     * @attr ref R.styleable#PreferenceGroup_initialExpandedChildrenCount
+     */
+    public void setInitialExpandedChildrenCount(int expandedCount) {
+        mInitialExpandedChildrenCount = expandedCount;
+    }
+
+    /**
+     * Gets the maximal number of children that is initially shown.
+     *
+     * @return the maximal number of children that is initially shown.
+     *
+     * @attr ref R.styleable#PreferenceGroup_initialExpandedChildrenCount
+     */
+    public int getInitialExpandedChildrenCount() {
+        return mInitialExpandedChildrenCount;
+    }
+
+    /**
      * Called by the inflater to add an item to this group.
      */
     public void addItemFromInflater(Preference preference) {
@@ -400,6 +440,44 @@
         }
     }
 
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (mPreferenceInstanceStateCallback != null) {
+            return mPreferenceInstanceStateCallback.saveInstanceState(superState);
+        }
+        return superState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (mPreferenceInstanceStateCallback != null) {
+            state = mPreferenceInstanceStateCallback.restoreInstanceState(state);
+        }
+        super.onRestoreInstanceState(state);
+    }
+
+    /**
+     * Sets the instance state callback.
+     *
+     * @param callback The callback.
+     * @see #onSaveInstanceState()
+     * @see #onRestoreInstanceState()
+     */
+    final void setPreferenceInstanceStateCallback(PreferenceInstanceStateCallback callback) {
+        mPreferenceInstanceStateCallback = callback;
+    }
+
+    /**
+     * Gets the instance state callback.
+     *
+     * @return the instance state callback.
+     */
+    @VisibleForTesting
+    final PreferenceInstanceStateCallback getPreferenceInstanceStateCallback() {
+        return mPreferenceInstanceStateCallback;
+    }
+
     /**
      * Interface for PreferenceGroup Adapters to implement so that
      * {@link android.support.v14.preference.PreferenceFragment#scrollToPreference(String)} and
@@ -426,4 +504,29 @@
          */
         int getPreferenceAdapterPosition(Preference preference);
     }
+
+    /**
+     * Interface for callback to implement so that they can save and restore the preference group's
+     * instance state.
+     */
+    interface PreferenceInstanceStateCallback {
+
+        /**
+         * Save the internal state that can later be used to create a new instance with that
+         * same state.
+         *
+         * @param state The Parcelable to save the current dynamic state.
+         */
+        Parcelable saveInstanceState(Parcelable state);
+
+        /**
+         * Restore the previously saved state from the given parcelable.
+         *
+         * @param state The Parcelable that holds the previously saved state.
+         * @return the super state if data has been saved in the state in {@link saveInstanceState}
+         *         or state otherwise
+         */
+        Parcelable restoreInstanceState(Parcelable state);
+    }
+
 }
diff --git a/android/support/v7/preference/PreferenceGroupAdapter.java b/android/support/v7/preference/PreferenceGroupAdapter.java
index d1c630f..00a0c5b 100644
--- a/android/support/v7/preference/PreferenceGroupAdapter.java
+++ b/android/support/v7/preference/PreferenceGroupAdapter.java
@@ -22,6 +22,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.util.DiffUtil;
@@ -73,7 +74,9 @@
 
     private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
 
-    private Handler mHandler = new Handler();
+    private Handler mHandler;
+
+    private CollapsiblePreferenceGroupController mPreferenceGroupController;
 
     private Runnable mSyncRunnable = new Runnable() {
         @Override
@@ -117,7 +120,14 @@
     }
 
     public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
+        this(preferenceGroup, new Handler());
+    }
+
+    private PreferenceGroupAdapter(PreferenceGroup preferenceGroup, Handler handler) {
         mPreferenceGroup = preferenceGroup;
+        mHandler = handler;
+        mPreferenceGroupController =
+                new CollapsiblePreferenceGroupController(preferenceGroup, this);
         // If this group gets or loses any children, let us know
         mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
 
@@ -134,6 +144,12 @@
         syncMyPreferences();
     }
 
+    @VisibleForTesting
+    static PreferenceGroupAdapter createInstanceWithCustomHandler(PreferenceGroup preferenceGroup,
+            Handler handler) {
+        return new PreferenceGroupAdapter(preferenceGroup, handler);
+    }
+
     private void syncMyPreferences() {
         for (final Preference preference : mPreferenceListInternal) {
             // Clear out the listeners in anticipation of some items being removed. This listener
@@ -143,13 +159,8 @@
         final List<Preference> fullPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
         flattenPreferenceGroup(fullPreferenceList, mPreferenceGroup);
 
-        final List<Preference> visiblePreferenceList = new ArrayList<>(fullPreferenceList.size());
-        // Copy only the visible preferences to the active list
-        for (final Preference preference : fullPreferenceList) {
-            if (preference.isVisible()) {
-                visiblePreferenceList.add(preference);
-            }
-        }
+        final List<Preference> visiblePreferenceList =
+                mPreferenceGroupController.createVisiblePreferencesList(fullPreferenceList);
 
         final List<Preference> oldVisibleList = mPreferenceList;
         mPreferenceList = visiblePreferenceList;
@@ -277,6 +288,9 @@
         if (!mPreferenceListInternal.contains(preference)) {
             return;
         }
+        if (mPreferenceGroupController.onPreferenceVisibilityChange(preference)) {
+            return;
+        }
         if (preference.isVisible()) {
             // The preference has become visible, we need to add it in the correct location.
 
diff --git a/android/support/v7/recyclerview/extensions/ListAdapter.java b/android/support/v7/recyclerview/extensions/ListAdapter.java
index 8035a27..e08cb53 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapter.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapter.java
@@ -46,7 +46,7 @@
  *     }
  * }
  *
- * class MyActivity extends Activity implements LifecycleRegistryOwner {
+ * class MyActivity extends AppCompatActivity {
  *     {@literal @}Override
  *     public void onCreate(Bundle savedState) {
  *         super.onCreate(savedState);
diff --git a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
index e4a53fd..b47b833 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
@@ -55,7 +55,7 @@
  *     }
  * }
  *
- * class MyActivity extends Activity implements LifecycleRegistryOwner {
+ * class MyActivity extends AppCompatActivity {
  *     {@literal @}Override
  *     public void onCreate(Bundle savedState) {
  *         super.onCreate(savedState);
diff --git a/android/support/v7/view/menu/CascadingMenuPopup.java b/android/support/v7/view/menu/CascadingMenuPopup.java
index 73499cf..564bbfc 100644
--- a/android/support/v7/view/menu/CascadingMenuPopup.java
+++ b/android/support/v7/view/menu/CascadingMenuPopup.java
@@ -404,14 +404,14 @@
             final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
             mLastPosition = nextMenuPosition;
 
-            final int parentOffsetLeft;
-            final int parentOffsetTop;
+            final int parentOffsetX;
+            final int parentOffsetY;
             if (Build.VERSION.SDK_INT >= 26) {
                 // Anchor the submenu directly to the parent menu item view. This allows for
                 // accurate submenu positioning when the parent menu is being moved.
                 popupWindow.setAnchorView(parentView);
-                parentOffsetLeft = 0;
-                parentOffsetTop = 0;
+                parentOffsetX = 0;
+                parentOffsetY = 0;
             } else {
                 // Framework does not allow anchoring to a view in another popup window. Use the
                 // same top-level anchor as the parent menu is using, with appropriate offsets.
@@ -428,10 +428,19 @@
                 final int[] parentViewScreenLocation = new int[2];
                 parentView.getLocationOnScreen(parentViewScreenLocation);
 
+                // For Gravity.LEFT case, the baseline is just the left border of the view. So we
+                // can use the X of the location directly. But for Gravity.RIGHT case, the baseline
+                // is the right border. So we need add view's width with the location to make the
+                // baseline as the right border correctly.
+                if ((mDropDownGravity & (Gravity.RIGHT | Gravity.LEFT)) == Gravity.RIGHT) {
+                    anchorScreenLocation[0] += mAnchorView.getWidth();
+                    parentViewScreenLocation[0] += parentView.getWidth();
+                }
+
                 // If used as horizontal/vertical offsets, these values would position the submenu
                 // at the exact same position as the parent item.
-                parentOffsetLeft = parentViewScreenLocation[0] - anchorScreenLocation[0];
-                parentOffsetTop = parentViewScreenLocation[1] - anchorScreenLocation[1];
+                parentOffsetX = parentViewScreenLocation[0] - anchorScreenLocation[0];
+                parentOffsetY = parentViewScreenLocation[1] - anchorScreenLocation[1];
             }
 
             // Adjust the horizontal offset to display the submenu to the right or to the left
@@ -441,22 +450,22 @@
             final int x;
             if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
                 if (showOnRight) {
-                    x = parentOffsetLeft + menuWidth;
+                    x = parentOffsetX + menuWidth;
                 } else {
-                    x = parentOffsetLeft - parentView.getWidth();
+                    x = parentOffsetX - parentView.getWidth();
                 }
             } else {
                 if (showOnRight) {
-                    x = parentOffsetLeft + parentView.getWidth();
+                    x = parentOffsetX + parentView.getWidth();
                 } else {
-                    x = parentOffsetLeft - menuWidth;
+                    x = parentOffsetX - menuWidth;
                 }
             }
             popupWindow.setHorizontalOffset(x);
 
             // Vertically align with the parent item.
             popupWindow.setOverlapAnchor(true);
-            popupWindow.setVerticalOffset(parentOffsetTop);
+            popupWindow.setVerticalOffset(parentOffsetY);
         } else {
             if (mHasXOffset) {
                 popupWindow.setHorizontalOffset(mXOffset);
diff --git a/android/support/v7/widget/AppCompatImageButton.java b/android/support/v7/widget/AppCompatImageButton.java
index 90e6aa9..b2b1f10 100644
--- a/android/support/v7/widget/AppCompatImageButton.java
+++ b/android/support/v7/widget/AppCompatImageButton.java
@@ -23,7 +23,6 @@
 import android.graphics.Bitmap;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.Nullable;
@@ -102,14 +101,6 @@
     }
 
     @Override
-    public void setImageIcon(@Nullable Icon icon) {
-        super.setImageIcon(icon);
-        if (mImageHelper != null) {
-            mImageHelper.applySupportImageTint();
-        }
-    }
-
-    @Override
     public void setImageURI(@Nullable Uri uri) {
         super.setImageURI(uri);
         if (mImageHelper != null) {
diff --git a/android/support/v7/widget/AppCompatImageView.java b/android/support/v7/widget/AppCompatImageView.java
index 0844f9a..f50799e 100644
--- a/android/support/v7/widget/AppCompatImageView.java
+++ b/android/support/v7/widget/AppCompatImageView.java
@@ -23,7 +23,6 @@
 import android.graphics.Bitmap;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.Nullable;
@@ -112,14 +111,6 @@
     }
 
     @Override
-    public void setImageIcon(@Nullable Icon icon) {
-        super.setImageIcon(icon);
-        if (mImageHelper != null) {
-            mImageHelper.applySupportImageTint();
-        }
-    }
-
-    @Override
     public void setImageURI(@Nullable Uri uri) {
         super.setImageURI(uri);
         if (mImageHelper != null) {
diff --git a/android/support/v7/widget/RecyclerView.java b/android/support/v7/widget/RecyclerView.java
index dea8546..7009733 100644
--- a/android/support/v7/widget/RecyclerView.java
+++ b/android/support/v7/widget/RecyclerView.java
@@ -44,6 +44,7 @@
 import android.support.annotation.RestrictTo;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.os.TraceCompat;
+import android.support.v4.util.Preconditions;
 import android.support.v4.view.AbsSavedState;
 import android.support.v4.view.InputDeviceCompat;
 import android.support.v4.view.MotionEventCompat;
@@ -417,6 +418,8 @@
      */
     private int mDispatchScrollCounter = 0;
 
+    @NonNull
+    private EdgeEffectFactory mEdgeEffectFactory = new EdgeEffectFactory();
     private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
 
     ItemAnimator mItemAnimator = new DefaultItemAnimator();
@@ -2306,7 +2309,7 @@
         if (mLeftGlow != null) {
             return;
         }
-        mLeftGlow = new EdgeEffect(getContext());
+        mLeftGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_LEFT);
         if (mClipToPadding) {
             mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                     getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2319,7 +2322,7 @@
         if (mRightGlow != null) {
             return;
         }
-        mRightGlow = new EdgeEffect(getContext());
+        mRightGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_RIGHT);
         if (mClipToPadding) {
             mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                     getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2332,7 +2335,7 @@
         if (mTopGlow != null) {
             return;
         }
-        mTopGlow = new EdgeEffect(getContext());
+        mTopGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_TOP);
         if (mClipToPadding) {
             mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                     getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2346,7 +2349,7 @@
         if (mBottomGlow != null) {
             return;
         }
-        mBottomGlow = new EdgeEffect(getContext());
+        mBottomGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_BOTTOM);
         if (mClipToPadding) {
             mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                     getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2360,6 +2363,32 @@
     }
 
     /**
+     * Set a {@link EdgeEffectFactory} for this {@link RecyclerView}.
+     * <p>
+     * When a new {@link EdgeEffectFactory} is set, any existing over-scroll effects are cleared
+     * and new effects are created as needed using
+     * {@link EdgeEffectFactory#createEdgeEffect(RecyclerView, int)}
+     *
+     * @param edgeEffectFactory The {@link EdgeEffectFactory} instance.
+     */
+    public void setEdgeEffectFactory(@NonNull EdgeEffectFactory edgeEffectFactory) {
+        Preconditions.checkNotNull(edgeEffectFactory);
+        mEdgeEffectFactory = edgeEffectFactory;
+        invalidateGlows();
+    }
+
+    /**
+     * Retrieves the previously set {@link EdgeEffectFactory} or the default factory if nothing
+     * was set.
+     *
+     * @return The previously set {@link EdgeEffectFactory}
+     * @see #setEdgeEffectFactory(EdgeEffectFactory)
+     */
+    public EdgeEffectFactory getEdgeEffectFactory() {
+        return mEdgeEffectFactory;
+    }
+
+    /**
      * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
      * in the Adapter but not visible in the UI), it employs a more involved focus search strategy
      * that differs from other ViewGroups.
@@ -5130,6 +5159,46 @@
     }
 
     /**
+     * EdgeEffectFactory lets you customize the over-scroll edge effect for RecyclerViews.
+     *
+     * @see RecyclerView#setEdgeEffectFactory(EdgeEffectFactory)
+     */
+    public static class EdgeEffectFactory {
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM})
+        public @interface EdgeDirection {}
+
+        /**
+         * Direction constant for the left edge
+         */
+        public static final int DIRECTION_LEFT = 0;
+
+        /**
+         * Direction constant for the top edge
+         */
+        public static final int DIRECTION_TOP = 1;
+
+        /**
+         * Direction constant for the right edge
+         */
+        public static final int DIRECTION_RIGHT = 2;
+
+        /**
+         * Direction constant for the bottom edge
+         */
+        public static final int DIRECTION_BOTTOM = 3;
+
+        /**
+         * Create a new EdgeEffect for the provided direction.
+         */
+        protected @NonNull EdgeEffect createEdgeEffect(RecyclerView view,
+                @EdgeDirection int direction) {
+            return new EdgeEffect(view.getContext());
+        }
+    }
+
+    /**
      * RecycledViewPool lets you share Views between multiple RecyclerViews.
      * <p>
      * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
diff --git a/android/telecom/DefaultDialerManager.java b/android/telecom/DefaultDialerManager.java
index 2a707c9..1806aee 100644
--- a/android/telecom/DefaultDialerManager.java
+++ b/android/telecom/DefaultDialerManager.java
@@ -22,6 +22,7 @@
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Process;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 
@@ -163,7 +164,10 @@
 
         for (ResolveInfo resolveInfo : resolveInfoList) {
             final ActivityInfo activityInfo = resolveInfo.activityInfo;
-            if (activityInfo != null && !packageNames.contains(activityInfo.packageName)) {
+            if (activityInfo != null
+                    && !packageNames.contains(activityInfo.packageName)
+                    // ignore cross profile intent handler
+                    && resolveInfo.targetUserId == UserHandle.USER_CURRENT) {
                 packageNames.add(activityInfo.packageName);
             }
         }
diff --git a/android/telephony/MbmsDownloadManager.java b/android/telephony/MbmsDownloadManager.java
deleted file mode 100644
index 1e8cf18..0000000
--- a/android/telephony/MbmsDownloadManager.java
+++ /dev/null
@@ -1,586 +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 android.telephony;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.telephony.mbms.DownloadProgressListener;
-import android.telephony.mbms.FileInfo;
-import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.MbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsDownloadReceiver;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsTempFileProvider;
-import android.telephony.mbms.MbmsUtils;
-import android.telephony.mbms.vendor.IMbmsDownloadService;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-/** @hide */
-public class MbmsDownloadManager {
-    private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
-
-    /** @hide */
-    // TODO: systemapi
-    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
-    public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
-            "android.telephony.action.EmbmsDownload";
-
-    /**
-     * Integer extra indicating the result code of the download. One of
-     * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}.
-     */
-    public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
-
-    /**
-     * Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
-     * is for. Must not be null.
-     */
-    public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO";
-
-    /**
-     * Extra containing a single {@link Uri} indicating the location of the successfully
-     * downloaded file. Set on the intent provided via
-     * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
-     * Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to
-     * {@link #RESULT_SUCCESSFUL}.
-     */
-    public static final String EXTRA_COMPLETED_FILE_URI =
-            "android.telephony.mbms.extra.COMPLETED_FILE_URI";
-
-    public static final int RESULT_SUCCESSFUL = 1;
-    public static final int RESULT_CANCELLED = 2;
-    public static final int RESULT_EXPIRED = 3;
-    public static final int RESULT_IO_ERROR = 4;
-    // TODO - more results!
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD,
-            STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW})
-    public @interface DownloadStatus {}
-
-    public static final int STATUS_UNKNOWN = 0;
-    public static final int STATUS_ACTIVELY_DOWNLOADING = 1;
-    public static final int STATUS_PENDING_DOWNLOAD = 2;
-    public static final int STATUS_PENDING_REPAIR = 3;
-    public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
-
-    private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
-
-    private final Context mContext;
-    private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
-    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
-        @Override
-        public void binderDied() {
-            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
-        }
-    };
-
-    private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
-    private final MbmsDownloadManagerCallback mCallback;
-
-    private MbmsDownloadManager(Context context, MbmsDownloadManagerCallback callback, int subId) {
-        mContext = context;
-        mCallback = callback;
-        mSubscriptionId = subId;
-    }
-
-    /**
-     * Create a new MbmsDownloadManager using the system default data subscription ID.
-     * See {@link #create(Context, MbmsDownloadManagerCallback, int)}
-     *
-     * @hide
-     */
-    public static MbmsDownloadManager create(Context context,
-            MbmsDownloadManagerCallback listener)
-            throws MbmsException {
-        return create(context, listener, SubscriptionManager.getDefaultSubscriptionId());
-    }
-
-    /**
-     * Create a new MbmsDownloadManager using the given subscription ID.
-     *
-     * Note that this call will bind a remote service and that may take a bit. The instance of
-     * {@link MbmsDownloadManager} that is returned will not be ready for use until
-     * {@link MbmsDownloadManagerCallback#middlewareReady()} is called on the provided callback.
-     * If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown.
-     *
-     * This also may throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
-     *
-     * You may only have one instance of {@link MbmsDownloadManager} per UID. If you call this
-     * method while there is an active instance of {@link MbmsDownloadManager} in your process
-     * (in other words, one that has not had {@link #dispose()} called on it), this method will
-     * throw an {@link MbmsException}. If you call this method in a different process
-     * running under the same UID, an error will be indicated via
-     * {@link MbmsDownloadManagerCallback#error(int, String)}.
-     *
-     * Note that initialization may fail asynchronously. If you wish to try again after you
-     * receive such an asynchronous error, you must call dispose() on the instance of
-     * {@link MbmsDownloadManager} that you received before calling this method again.
-     *
-     * @param context The instance of {@link Context} to use
-     * @param listener A callback to get asynchronous error messages and file service updates.
-     * @param subscriptionId The data subscription ID to use
-     * @hide
-     */
-    public static MbmsDownloadManager create(Context context,
-            MbmsDownloadManagerCallback listener, int subscriptionId)
-            throws MbmsException {
-        if (!sIsInitialized.compareAndSet(false, true)) {
-            throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
-        }
-        MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, subscriptionId);
-        try {
-            mdm.bindAndInitialize();
-        } catch (MbmsException e) {
-            sIsInitialized.set(false);
-            throw e;
-        }
-        return mdm;
-    }
-
-    private void bindAndInitialize() throws MbmsException {
-        MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
-                new ServiceConnection() {
-                    @Override
-                    public void onServiceConnected(ComponentName name, IBinder service) {
-                        IMbmsDownloadService downloadService =
-                                IMbmsDownloadService.Stub.asInterface(service);
-                        int result;
-                        try {
-                            result = downloadService.initialize(mSubscriptionId, mCallback);
-                        } catch (RemoteException e) {
-                            Log.e(LOG_TAG, "Service died before initialization");
-                            sIsInitialized.set(false);
-                            return;
-                        } catch (RuntimeException e) {
-                            Log.e(LOG_TAG, "Runtime exception during initialization");
-                            sendErrorToApp(
-                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
-                                    e.toString());
-                            sIsInitialized.set(false);
-                            return;
-                        }
-                        if (result != MbmsException.SUCCESS) {
-                            sendErrorToApp(result, "Error returned during initialization");
-                            sIsInitialized.set(false);
-                            return;
-                        }
-                        try {
-                            downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
-                        } catch (RemoteException e) {
-                            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
-                                    "Middleware lost during initialization");
-                            sIsInitialized.set(false);
-                            return;
-                        }
-                        mService.set(downloadService);
-                    }
-
-                    @Override
-                    public void onServiceDisconnected(ComponentName name) {
-                        sIsInitialized.set(false);
-                        mService.set(null);
-                    }
-                });
-    }
-
-    /**
-     * An inspection API to retrieve the list of available
-     * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
-     * The results are returned asynchronously via a call to
-     * {@link MbmsDownloadManagerCallback#fileServicesUpdated(List)}
-     *
-     * The serviceClasses argument lets the app filter on types of programming and is opaque data
-     * negotiated beforehand between the app and the carrier.
-     *
-     * This may throw an {@link MbmsException} containing one of the following errors:
-     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
-     *
-     * Asynchronous error codes via the {@link MbmsDownloadManagerCallback#error(int, String)}
-     * callback can include any of the errors except:
-     * {@link MbmsException.StreamingErrors#ERROR_UNABLE_TO_START_SERVICE}
-     *
-     * @param classList A list of service classes which the app wishes to receive
-     *                  {@link MbmsDownloadManagerCallback#fileServicesUpdated(List)} callbacks
-     *                  about. Subsequent calls to this method will replace this list of service
-     *                  classes (i.e. the middleware will no longer send updates for services
-     *                  matching classes only in the old list).
-     */
-    public void getFileServices(List<String> classList) throws MbmsException {
-        IMbmsDownloadService downloadService = mService.get();
-        if (downloadService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-        try {
-            int returnCode = downloadService.getFileServices(mSubscriptionId, classList);
-            if (returnCode != MbmsException.SUCCESS) {
-                throw new MbmsException(returnCode);
-            }
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Remote process died");
-            mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-    }
-
-    /**
-     * Sets the temp file root for downloads.
-     * All temp files created for the middleware to write to will be contained in the specified
-     * directory. Applications that wish to specify a location only need to call this method once
-     * as long their data is persisted in storage -- the argument will be stored both in a
-     * local instance of {@link android.content.SharedPreferences} and by the middleware.
-     *
-     * If this method is not called at least once before calling
-     * {@link #download(DownloadRequest, DownloadProgressListener)}, the framework
-     * will default to a directory formed by the concatenation of the app's files directory and
-     * {@link android.telephony.mbms.MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
-     *
-     * Before calling this method, the app must cancel all of its pending
-     * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
-     * an {@link MbmsException} will be thrown with code
-     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
-     * provided directory is the same as what has been previously configured.
-     *
-     * The {@link File} supplied as a root temp file directory must already exist. If not, an
-     * {@link IllegalArgumentException} will be thrown.
-     * @param tempFileRootDirectory A directory to place temp files in.
-     */
-    public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory)
-            throws MbmsException {
-        IMbmsDownloadService downloadService = mService.get();
-        if (downloadService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-        if (!tempFileRootDirectory.exists()) {
-            throw new IllegalArgumentException("Provided directory does not exist");
-        }
-        if (!tempFileRootDirectory.isDirectory()) {
-            throw new IllegalArgumentException("Provided File is not a directory");
-        }
-        String filePath;
-        try {
-            filePath = tempFileRootDirectory.getCanonicalPath();
-        } catch (IOException e) {
-            throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
-        }
-
-        try {
-            int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
-            if (result != MbmsException.SUCCESS) {
-                throw new MbmsException(result);
-            }
-        } catch (RemoteException e) {
-            mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-
-        SharedPreferences prefs = mContext.getSharedPreferences(
-                MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
-        prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
-    }
-
-    /**
-     * Retrieves the currently configured temp file root directory. Returns the file that was
-     * configured via {@link #setTempFileRootDirectory(File)} or the default directory
-     * {@link #download(DownloadRequest, DownloadProgressListener)} was called without ever setting
-     * the temp file root. If neither method has been called since the last time the app's shared
-     * preferences were reset, returns null.
-     *
-     * @return A {@link File} pointing to the configured temp file directory, or null if not yet
-     *         configured.
-     */
-    public @Nullable File getTempFileRootDirectory() {
-        SharedPreferences prefs = mContext.getSharedPreferences(
-                MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
-        String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null);
-        if (path != null) {
-            return new File(path);
-        }
-        return null;
-    }
-
-    /**
-     * Requests a download of a file that is available via multicast.
-     *
-     * downloadListener is an optional callback object which can be used to get progress reports
-     *     of a currently occuring download.  Note this can only run while the calling app
-     *     is running, so future downloads will simply result in resultIntents being sent
-     *     for completed or errored-out downloads.  A NULL indicates no callbacks are needed.
-     *
-     * May throw an {@link IllegalArgumentException}
-     *
-     * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,
-     * this method will create a directory at the default location defined at
-     * {@link MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
-     * file root directory.
-     *
-     * Asynchronous errors through the listener include any of the errors
-     *
-     * @param request The request that specifies what should be downloaded
-     * @param progressListener Optional listener that will be provided progress updates
-     *                         if the app is running.
-     */
-    public void download(DownloadRequest request, DownloadProgressListener progressListener)
-            throws MbmsException {
-        IMbmsDownloadService downloadService = mService.get();
-        if (downloadService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-
-        // Check to see whether the app's set a temp root dir yet, and set it if not.
-        SharedPreferences prefs = mContext.getSharedPreferences(
-                MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
-        if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) {
-            File tempRootDirectory = new File(mContext.getFilesDir(),
-                    MbmsTempFileProvider.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
-            tempRootDirectory.mkdirs();
-            setTempFileRootDirectory(tempRootDirectory);
-        }
-
-        checkValidDownloadDestination(request);
-        writeDownloadRequestToken(request);
-        try {
-            downloadService.download(request, progressListener);
-        } catch (RemoteException e) {
-            mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-    }
-
-    /**
-     * Returns a list of pending {@link DownloadRequest}s that originated from this application.
-     * A pending request is one that was issued via
-     * {@link #download(DownloadRequest, DownloadProgressListener)} but not cancelled through
-     * {@link #cancelDownload(DownloadRequest)}.
-     * @return A list, possibly empty, of {@link DownloadRequest}s
-     */
-    public @NonNull List<DownloadRequest> listPendingDownloads() throws MbmsException {
-        IMbmsDownloadService downloadService = mService.get();
-        if (downloadService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-
-        try {
-            return downloadService.listPendingDownloads(mSubscriptionId);
-        } catch (RemoteException e) {
-            mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-    }
-
-    /**
-     * Attempts to cancel the specified {@link DownloadRequest}.
-     *
-     * If the middleware is not aware of the specified download request, an MbmsException will be
-     * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
-     *
-     * If this method returns without throwing an exception, you may assume that cancellation
-     * was successful.
-     * @param downloadRequest The download request that you wish to cancel.
-     */
-    public void cancelDownload(DownloadRequest downloadRequest) throws MbmsException {
-        IMbmsDownloadService downloadService = mService.get();
-        if (downloadService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-
-        try {
-            int result = downloadService.cancelDownload(downloadRequest);
-            if (result != MbmsException.SUCCESS) {
-                throw new MbmsException(result);
-            }
-        } catch (RemoteException e) {
-            mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-        deleteDownloadRequestToken(downloadRequest);
-    }
-
-    /**
-     * Gets information about the status of a file pending download.
-     *
-     * If the middleware has not yet been properly initialized or if it has no records of the
-     * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
-     * {@link #STATUS_UNKNOWN} will be returned.
-     *
-     * @param downloadRequest The download request to query.
-     * @param fileInfo The particular file within the request to get information on.
-     * @return The status of the download.
-     */
-    @DownloadStatus
-    public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo)
-            throws MbmsException {
-        IMbmsDownloadService downloadService = mService.get();
-        if (downloadService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-
-        try {
-            return downloadService.getDownloadStatus(downloadRequest, fileInfo);
-        } catch (RemoteException e) {
-            mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-    }
-
-    /**
-     * Resets the middleware's knowledge of previously-downloaded files in this download request.
-     *
-     * Normally, the middleware keeps track of the hashes of downloaded files and won't re-download
-     * files whose server-reported hash matches one of the already-downloaded files. This means
-     * that if the file is accidentally deleted by the user or by the app, the middleware will
-     * not try to download it again.
-     * This method will reset the middleware's cache of hashes for the provided
-     * {@link DownloadRequest}, so that previously downloaded content will be downloaded again
-     * when available.
-     * This will not interrupt in-progress downloads.
-     *
-     * If the middleware is not aware of the specified download request, an MbmsException will be
-     * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
-     *
-     * May throw a {@link MbmsException} with error code
-     * @param downloadRequest The request to re-download files for.
-     */
-    public void resetDownloadKnowledge(DownloadRequest downloadRequest) throws MbmsException {
-        IMbmsDownloadService downloadService = mService.get();
-        if (downloadService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-
-        try {
-            int result = downloadService.resetDownloadKnowledge(downloadRequest);
-            if (result != MbmsException.SUCCESS) {
-                throw new MbmsException(result);
-            }
-        } catch (RemoteException e) {
-            mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-    }
-
-    public void dispose() {
-        try {
-            IMbmsDownloadService downloadService = mService.get();
-            if (downloadService == null) {
-                Log.i(LOG_TAG, "Service already dead");
-                return;
-            }
-            downloadService.dispose(mSubscriptionId);
-        } catch (RemoteException e) {
-            // Ignore
-            Log.i(LOG_TAG, "Remote exception while disposing of service");
-        } finally {
-            mService.set(null);
-            sIsInitialized.set(false);
-        }
-    }
-
-    private void writeDownloadRequestToken(DownloadRequest request) {
-        File token = getDownloadRequestTokenPath(request);
-        if (!token.getParentFile().exists()) {
-            token.getParentFile().mkdirs();
-        }
-        if (token.exists()) {
-            Log.w(LOG_TAG, "Download token " + token.getName() + " already exists");
-            return;
-        }
-        try {
-            if (!token.createNewFile()) {
-                throw new RuntimeException("Failed to create download token for request "
-                        + request);
-            }
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to create download token for request " + request
-                    + " due to IOException " + e);
-        }
-    }
-
-    private void deleteDownloadRequestToken(DownloadRequest request) {
-        File token = getDownloadRequestTokenPath(request);
-        if (!token.isFile()) {
-            Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token);
-            return;
-        }
-        if (!token.delete()) {
-            Log.w(LOG_TAG, "Couldn't delete download token at " + token);
-        }
-    }
-
-    private File getDownloadRequestTokenPath(DownloadRequest request) {
-        File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
-                request.getFileServiceId());
-        String downloadTokenFileName = request.getHash()
-                + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
-        return new File(tempFileLocation, downloadTokenFileName);
-    }
-
-    /**
-     * Verifies the following:
-     * If a request is multi-part,
-     *     1. Destination Uri must exist and be a directory
-     *     2. Directory specified must contain no files.
-     * Otherwise
-     *     1. The file specified by the destination Uri must not exist.
-     */
-    private void checkValidDownloadDestination(DownloadRequest request) {
-        File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
-        if (request.isMultipartDownload()) {
-            if (!toFile.isDirectory()) {
-                throw new IllegalArgumentException("Multipart download must specify valid " +
-                        "destination directory.");
-            }
-            if (toFile.listFiles().length > 0) {
-                throw new IllegalArgumentException("Destination directory must be clear of all " +
-                        "files.");
-            }
-        } else {
-            if (toFile.exists()) {
-                throw new IllegalArgumentException("Destination file must not exist.");
-            }
-        }
-    }
-
-    private void sendErrorToApp(int errorCode, String message) {
-        try {
-            mCallback.error(errorCode, message);
-        } catch (RemoteException e) {
-            // Ignore, should not happen locally.
-        }
-    }
-}
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
new file mode 100644
index 0000000..ebac041
--- /dev/null
+++ b/android/telephony/MbmsDownloadSession.java
@@ -0,0 +1,773 @@
+/*
+ * 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 android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.mbms.DownloadStateCallback;
+import android.telephony.mbms.FileInfo;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.InternalDownloadSessionCallback;
+import android.telephony.mbms.InternalDownloadStateCallback;
+import android.telephony.mbms.MbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsDownloadReceiver;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsTempFileProvider;
+import android.telephony.mbms.MbmsUtils;
+import android.telephony.mbms.vendor.IMbmsDownloadService;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/**
+ * This class provides functionality for file download over MBMS.
+ */
+public class MbmsDownloadSession implements AutoCloseable {
+    private static final String LOG_TAG = MbmsDownloadSession.class.getSimpleName();
+
+    /**
+     * Service action which must be handled by the middleware implementing the MBMS file download
+     * interface.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
+            "android.telephony.action.EmbmsDownload";
+
+    /**
+     * Integer extra that Android will attach to the intent supplied via
+     * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+     * Indicates the result code of the download. One of
+     * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
+     * {@link #RESULT_IO_ERROR}.
+     *
+     * This extra may also be used by the middleware when it is sending intents to the app.
+     */
+    public static final String EXTRA_MBMS_DOWNLOAD_RESULT =
+            "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
+
+    /**
+     * {@link FileInfo} extra that Android will attach to the intent supplied via
+     * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+     * Indicates the file for which the download result is for. Never null.
+     *
+     * This extra may also be used by the middleware when it is sending intents to the app.
+     */
+    public static final String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
+
+    /**
+     * {@link Uri} extra that Android will attach to the intent supplied via
+     * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+     * Indicates the location of the successfully downloaded file within the temp file root set
+     * via {@link #setTempFileRootDirectory(File)}.
+     * While you may use this file in-place, it is highly encouraged that you move
+     * this file to a different location after receiving the download completion intent, as this
+     * file resides within the temp file directory.
+     *
+     * Will always be set to a non-null value if
+     * {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}.
+     */
+    public static final String EXTRA_MBMS_COMPLETED_FILE_URI =
+            "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
+
+    /**
+     * Extra containing the {@link DownloadRequest} for which the download result or file
+     * descriptor request is for. Must not be null.
+     */
+    public static final String EXTRA_MBMS_DOWNLOAD_REQUEST =
+            "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
+
+    /**
+     * The default directory name for all MBMS temp files. If you call
+     * {@link #download(DownloadRequest)} without first calling
+     * {@link #setTempFileRootDirectory(File)}, this directory will be created for you under the
+     * path returned by {@link Context#getFilesDir()}.
+     */
+    public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
+
+    /**
+     * Indicates that the download was successful.
+     */
+    public static final int RESULT_SUCCESSFUL = 1;
+
+    /**
+     * Indicates that the download was cancelled via {@link #cancelDownload(DownloadRequest)}.
+     */
+    public static final int RESULT_CANCELLED = 2;
+
+    /**
+     * Indicates that the download will not be completed due to the expiration of its download
+     * window on the carrier's network.
+     */
+    public static final int RESULT_EXPIRED = 3;
+
+    /**
+     * Indicates that the download will not be completed due to an I/O error incurred while
+     * writing to temp files. This commonly indicates that the device is out of storage space,
+     * but may indicate other conditions as well (such as an SD card being removed).
+     */
+    public static final int RESULT_IO_ERROR = 4;
+    // TODO - more results!
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD,
+            STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW})
+    public @interface DownloadStatus {}
+
+    /**
+     * Indicates that the middleware has no information on the file.
+     */
+    public static final int STATUS_UNKNOWN = 0;
+
+    /**
+     * Indicates that the file is actively downloading.
+     */
+    public static final int STATUS_ACTIVELY_DOWNLOADING = 1;
+
+    /**
+     * TODO: I don't know...
+     */
+    public static final int STATUS_PENDING_DOWNLOAD = 2;
+
+    /**
+     * Indicates that the file is being repaired after the download being interrupted.
+     */
+    public static final int STATUS_PENDING_REPAIR = 3;
+
+    /**
+     * Indicates that the file is waiting to download because its download window has not yet
+     * started.
+     */
+    public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
+
+    private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
+
+    private final Context mContext;
+    private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
+        }
+    };
+
+    private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
+    private final InternalDownloadSessionCallback mInternalCallback;
+    private final Map<DownloadStateCallback, InternalDownloadStateCallback>
+            mInternalDownloadCallbacks = new HashMap<>();
+
+    private MbmsDownloadSession(Context context, MbmsDownloadSessionCallback callback,
+            int subscriptionId, Handler handler) {
+        mContext = context;
+        mSubscriptionId = subscriptionId;
+        if (handler == null) {
+            handler = new Handler(Looper.getMainLooper());
+        }
+        mInternalCallback = new InternalDownloadSessionCallback(callback, handler);
+    }
+
+    /**
+     * Create a new {@link MbmsDownloadSession} using the system default data subscription ID.
+     * See {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)}
+     */
+    public static MbmsDownloadSession create(@NonNull Context context,
+            @NonNull MbmsDownloadSessionCallback callback, @NonNull Handler handler) {
+        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
+    }
+
+    /**
+     * Create a new MbmsDownloadManager using the given subscription ID.
+     *
+     * Note that this call will bind a remote service and that may take a bit. The instance of
+     * {@link MbmsDownloadSession} that is returned will not be ready for use until
+     * {@link MbmsDownloadSessionCallback#onMiddlewareReady()} is called on the provided callback.
+     * If you attempt to use the instance before it is ready, an {@link IllegalStateException}
+     * will be thrown or an error will be delivered through
+     * {@link MbmsDownloadSessionCallback#onError(int, String)}.
+     *
+     * This also may throw an {@link IllegalArgumentException}.
+     *
+     * You may only have one instance of {@link MbmsDownloadSession} per UID. If you call this
+     * method while there is an active instance of {@link MbmsDownloadSession} in your process
+     * (in other words, one that has not had {@link #close()} called on it), this method will
+     * throw an {@link IllegalStateException}. If you call this method in a different process
+     * running under the same UID, an error will be indicated via
+     * {@link MbmsDownloadSessionCallback#onError(int, String)}.
+     *
+     * Note that initialization may fail asynchronously. If you wish to try again after you
+     * receive such an asynchronous error, you must call {@link #close()} on the instance of
+     * {@link MbmsDownloadSession} that you received before calling this method again.
+     *
+     * @param context The instance of {@link Context} to use
+     * @param callback A callback to get asynchronous error messages and file service updates.
+     * @param subscriptionId The data subscription ID to use
+     * @param handler The {@link Handler} on which callbacks should be enqueued.
+     * @return A new instance of {@link MbmsDownloadSession}, or null if an error occurred during
+     * setup.
+     */
+    public static @Nullable MbmsDownloadSession create(@NonNull Context context,
+            final @NonNull MbmsDownloadSessionCallback callback,
+            int subscriptionId, @NonNull Handler handler) {
+        if (!sIsInitialized.compareAndSet(false, true)) {
+            throw new IllegalStateException("Cannot have two active instances");
+        }
+        MbmsDownloadSession session =
+                new MbmsDownloadSession(context, callback, subscriptionId, handler);
+        final int result = session.bindAndInitialize();
+        if (result != MbmsErrors.SUCCESS) {
+            sIsInitialized.set(false);
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onError(result, null);
+                }
+            });
+            return null;
+        }
+        return session;
+    }
+
+    private int bindAndInitialize() {
+        return MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
+                new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder service) {
+                        IMbmsDownloadService downloadService =
+                                IMbmsDownloadService.Stub.asInterface(service);
+                        int result;
+                        try {
+                            result = downloadService.initialize(mSubscriptionId, mInternalCallback);
+                        } catch (RemoteException e) {
+                            Log.e(LOG_TAG, "Service died before initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        } catch (RuntimeException e) {
+                            Log.e(LOG_TAG, "Runtime exception during initialization");
+                            sendErrorToApp(
+                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+                                    e.toString());
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        if (result != MbmsErrors.SUCCESS) {
+                            sendErrorToApp(result, "Error returned during initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        try {
+                            downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
+                        } catch (RemoteException e) {
+                            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
+                                    "Middleware lost during initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        mService.set(downloadService);
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName name) {
+                        sIsInitialized.set(false);
+                        mService.set(null);
+                    }
+                });
+    }
+
+    /**
+     * An inspection API to retrieve the list of available
+     * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
+     * The results are returned asynchronously via a call to
+     * {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)}
+     *
+     * Asynchronous error codes via the {@link MbmsDownloadSessionCallback#onError(int, String)}
+     * callback may include any of the errors that are not specific to the streaming use-case.
+     *
+     * May throw an {@link IllegalStateException} or {@link IllegalArgumentException}.
+     *
+     * @param classList A list of service classes which the app wishes to receive
+     *                  {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)} callbacks
+     *                  about. Subsequent calls to this method will replace this list of service
+     *                  classes (i.e. the middleware will no longer send updates for services
+     *                  matching classes only in the old list).
+     *                  Values in this list should be negotiated with the wireless carrier prior
+     *                  to using this API.
+     */
+    public void requestUpdateFileServices(@NonNull List<String> classList) {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+        try {
+            int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList);
+            if (returnCode != MbmsErrors.SUCCESS) {
+                sendErrorToApp(returnCode, null);
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService.set(null);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+        }
+    }
+
+    /**
+     * Sets the temp file root for downloads.
+     * All temp files created for the middleware to write to will be contained in the specified
+     * directory. Applications that wish to specify a location only need to call this method once
+     * as long their data is persisted in storage -- the argument will be stored both in a
+     * local instance of {@link android.content.SharedPreferences} and by the middleware.
+     *
+     * If this method is not called at least once before calling
+     * {@link #download(DownloadRequest)}, the framework
+     * will default to a directory formed by the concatenation of the app's files directory and
+     * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
+     *
+     * Before calling this method, the app must cancel all of its pending
+     * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
+     * you will receive an asynchronous error with code
+     * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
+     * provided directory is the same as what has been previously configured.
+     *
+     * The {@link File} supplied as a root temp file directory must already exist. If not, an
+     * {@link IllegalArgumentException} will be thrown. In addition, as an additional sanity
+     * check, an {@link IllegalArgumentException} will be thrown if you attempt to set the temp
+     * file root directory to one of your data roots (the value of {@link Context#getDataDir()},
+     * {@link Context#getFilesDir()}, or {@link Context#getCacheDir()}).
+     * @param tempFileRootDirectory A directory to place temp files in.
+     */
+    public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+        try {
+            validateTempFileRootSanity(tempFileRootDirectory);
+        } catch (IOException e) {
+            throw new IllegalStateException("Got IOException checking directory sanity");
+        }
+        String filePath;
+        try {
+            filePath = tempFileRootDirectory.getCanonicalPath();
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
+        }
+
+        try {
+            int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
+            if (result != MbmsErrors.SUCCESS) {
+                sendErrorToApp(result, null);
+            }
+        } catch (RemoteException e) {
+            mService.set(null);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return;
+        }
+
+        SharedPreferences prefs = mContext.getSharedPreferences(
+                MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+        prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
+    }
+
+    private void validateTempFileRootSanity(File tempFileRootDirectory) throws IOException {
+        if (!tempFileRootDirectory.exists()) {
+            throw new IllegalArgumentException("Provided directory does not exist");
+        }
+        if (!tempFileRootDirectory.isDirectory()) {
+            throw new IllegalArgumentException("Provided File is not a directory");
+        }
+        String canonicalTempFilePath = tempFileRootDirectory.getCanonicalPath();
+        if (mContext.getDataDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+            throw new IllegalArgumentException("Temp file root cannot be your data dir");
+        }
+        if (mContext.getCacheDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+            throw new IllegalArgumentException("Temp file root cannot be your cache dir");
+        }
+        if (mContext.getFilesDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+            throw new IllegalArgumentException("Temp file root cannot be your files dir");
+        }
+    }
+    /**
+     * Retrieves the currently configured temp file root directory. Returns the file that was
+     * configured via {@link #setTempFileRootDirectory(File)} or the default directory
+     * {@link #download(DownloadRequest)} was called without ever
+     * setting the temp file root. If neither method has been called since the last time the app's
+     * shared preferences were reset, returns {@code null}.
+     *
+     * @return A {@link File} pointing to the configured temp file directory, or null if not yet
+     *         configured.
+     */
+    public @Nullable File getTempFileRootDirectory() {
+        SharedPreferences prefs = mContext.getSharedPreferences(
+                MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+        String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null);
+        if (path != null) {
+            return new File(path);
+        }
+        return null;
+    }
+
+    /**
+     * Requests the download of a file or set of files that the carrier has indicated to be
+     * available.
+     *
+     * May throw an {@link IllegalArgumentException}
+     *
+     * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,
+     * this method will create a directory at the default location defined at
+     * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
+     * file root directory.
+     *
+     * Asynchronous errors through the callback may include any error not specific to the
+     * streaming use-case.
+     * @param request The request that specifies what should be downloaded.
+     */
+    public void download(@NonNull DownloadRequest request) {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+
+        // Check to see whether the app's set a temp root dir yet, and set it if not.
+        SharedPreferences prefs = mContext.getSharedPreferences(
+                MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+        if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) {
+            File tempRootDirectory = new File(mContext.getFilesDir(),
+                    DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
+            tempRootDirectory.mkdirs();
+            setTempFileRootDirectory(tempRootDirectory);
+        }
+
+        writeDownloadRequestToken(request);
+        try {
+            downloadService.download(request);
+        } catch (RemoteException e) {
+            mService.set(null);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+        }
+    }
+
+    /**
+     * Returns a list of pending {@link DownloadRequest}s that originated from this application.
+     * A pending request is one that was issued via
+     * {@link #download(DownloadRequest)} but not cancelled through
+     * {@link #cancelDownload(DownloadRequest)}.
+     * @return A list, possibly empty, of {@link DownloadRequest}s
+     */
+    public @NonNull List<DownloadRequest> listPendingDownloads() {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+
+        try {
+            return downloadService.listPendingDownloads(mSubscriptionId);
+        } catch (RemoteException e) {
+            mService.set(null);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Registers a callback for a {@link DownloadRequest} previously requested via
+     * {@link #download(DownloadRequest)}. This callback will only be called as long as both this
+     * app and the middleware are both running -- if either one stops, no further calls on the
+     * provided {@link DownloadStateCallback} will be enqueued.
+     *
+     * If the middleware is not aware of the specified download request,
+     * this method will throw an {@link IllegalArgumentException}.
+     *
+     * @param request The {@link DownloadRequest} that you want updates on.
+     * @param callback The callback that should be called when the middleware has information to
+     *                 share on the download.
+     * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
+     */
+    public void registerStateCallback(@NonNull DownloadRequest request,
+            @NonNull DownloadStateCallback callback,
+            @NonNull Handler handler) {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+
+        InternalDownloadStateCallback internalCallback =
+                new InternalDownloadStateCallback(callback, handler);
+
+        try {
+            int result = downloadService.registerStateCallback(request, internalCallback);
+            if (result != MbmsErrors.SUCCESS) {
+                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+                    throw new IllegalArgumentException("Unknown download request.");
+                }
+                sendErrorToApp(result, null);
+                return;
+            }
+        } catch (RemoteException e) {
+            mService.set(null);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return;
+        }
+        mInternalDownloadCallbacks.put(callback, internalCallback);
+    }
+
+    /**
+     * Un-register a callback previously registered via
+     * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}. After
+     * this method is called, no further callbacks will be enqueued on the {@link Handler}
+     * provided upon registration, even if this method throws an exception.
+     *
+     * If the middleware is not aware of the specified download request,
+     * this method will throw an {@link IllegalArgumentException}.
+     *
+     * @param request The {@link DownloadRequest} provided during registration
+     * @param callback The callback provided during registration.
+     */
+    public void unregisterStateCallback(@NonNull DownloadRequest request,
+            @NonNull DownloadStateCallback callback) {
+        try {
+            IMbmsDownloadService downloadService = mService.get();
+            if (downloadService == null) {
+                throw new IllegalStateException("Middleware not yet bound");
+            }
+
+            InternalDownloadStateCallback internalCallback =
+                    mInternalDownloadCallbacks.get(callback);
+
+            try {
+                int result = downloadService.unregisterStateCallback(request, internalCallback);
+                if (result != MbmsErrors.SUCCESS) {
+                    if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+                        throw new IllegalArgumentException("Unknown download request.");
+                    }
+                    sendErrorToApp(result, null);
+                }
+            } catch (RemoteException e) {
+                mService.set(null);
+                sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            }
+        } finally {
+            InternalDownloadStateCallback internalCallback =
+                    mInternalDownloadCallbacks.remove(callback);
+            if (internalCallback != null) {
+                internalCallback.stop();
+            }
+        }
+    }
+
+    /**
+     * Attempts to cancel the specified {@link DownloadRequest}.
+     *
+     * If the middleware is not aware of the specified download request,
+     * this method will throw an {@link IllegalArgumentException}.
+     *
+     * @param downloadRequest The download request that you wish to cancel.
+     */
+    public void cancelDownload(@NonNull DownloadRequest downloadRequest) {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+
+        try {
+            int result = downloadService.cancelDownload(downloadRequest);
+            if (result != MbmsErrors.SUCCESS) {
+                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+                    throw new IllegalArgumentException("Unknown download request.");
+                }
+                sendErrorToApp(result, null);
+                return;
+            }
+        } catch (RemoteException e) {
+            mService.set(null);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return;
+        }
+        deleteDownloadRequestToken(downloadRequest);
+    }
+
+    /**
+     * Gets information about the status of a file pending download.
+     *
+     * If there was a problem communicating with the middleware or if it has no records of the
+     * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
+     * {@link #STATUS_UNKNOWN} will be returned.
+     *
+     * @param downloadRequest The download request to query.
+     * @param fileInfo The particular file within the request to get information on.
+     * @return The status of the download.
+     */
+    @DownloadStatus
+    public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+
+        try {
+            return downloadService.getDownloadStatus(downloadRequest, fileInfo);
+        } catch (RemoteException e) {
+            mService.set(null);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return STATUS_UNKNOWN;
+        }
+    }
+
+    /**
+     * Resets the middleware's knowledge of previously-downloaded files in this download request.
+     *
+     * Normally, the middleware keeps track of the hashes of downloaded files and won't re-download
+     * files whose server-reported hash matches one of the already-downloaded files. This means
+     * that if the file is accidentally deleted by the user or by the app, the middleware will
+     * not try to download it again.
+     * This method will reset the middleware's cache of hashes for the provided
+     * {@link DownloadRequest}, so that previously downloaded content will be downloaded again
+     * when available.
+     * This will not interrupt in-progress downloads.
+     *
+     * This is distinct from cancelling and re-issuing the download request -- if you cancel and
+     * re-issue, the middleware will not clear its cache of download state information.
+     *
+     * If the middleware is not aware of the specified download request, an
+     * {@link IllegalArgumentException} will be thrown.
+     *
+     * @param downloadRequest The request to re-download files for.
+     */
+    public void resetDownloadKnowledge(DownloadRequest downloadRequest) {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+
+        try {
+            int result = downloadService.resetDownloadKnowledge(downloadRequest);
+            if (result != MbmsErrors.SUCCESS) {
+                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+                    throw new IllegalArgumentException("Unknown download request.");
+                }
+                sendErrorToApp(result, null);
+            }
+        } catch (RemoteException e) {
+            mService.set(null);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+        }
+    }
+
+    /**
+     * Terminates this instance.
+     *
+     * After this method returns,
+     * no further callbacks originating from the middleware will be enqueued on the provided
+     * instance of {@link MbmsDownloadSessionCallback}, but callbacks that have already been
+     * enqueued will still be delivered.
+     *
+     * It is safe to call {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)} to
+     * obtain another instance of {@link MbmsDownloadSession} immediately after this method
+     * returns.
+     *
+     * May throw an {@link IllegalStateException}
+     */
+    @Override
+    public void close() {
+        try {
+            IMbmsDownloadService downloadService = mService.get();
+            if (downloadService == null) {
+                Log.i(LOG_TAG, "Service already dead");
+                return;
+            }
+            downloadService.dispose(mSubscriptionId);
+        } catch (RemoteException e) {
+            // Ignore
+            Log.i(LOG_TAG, "Remote exception while disposing of service");
+        } finally {
+            mService.set(null);
+            sIsInitialized.set(false);
+            mInternalCallback.stop();
+        }
+    }
+
+    private void writeDownloadRequestToken(DownloadRequest request) {
+        File token = getDownloadRequestTokenPath(request);
+        if (!token.getParentFile().exists()) {
+            token.getParentFile().mkdirs();
+        }
+        if (token.exists()) {
+            Log.w(LOG_TAG, "Download token " + token.getName() + " already exists");
+            return;
+        }
+        try {
+            if (!token.createNewFile()) {
+                throw new RuntimeException("Failed to create download token for request "
+                        + request);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to create download token for request " + request
+                    + " due to IOException " + e);
+        }
+    }
+
+    private void deleteDownloadRequestToken(DownloadRequest request) {
+        File token = getDownloadRequestTokenPath(request);
+        if (!token.isFile()) {
+            Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token);
+            return;
+        }
+        if (!token.delete()) {
+            Log.w(LOG_TAG, "Couldn't delete download token at " + token);
+        }
+    }
+
+    private File getDownloadRequestTokenPath(DownloadRequest request) {
+        File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
+                request.getFileServiceId());
+        String downloadTokenFileName = request.getHash()
+                + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
+        return new File(tempFileLocation, downloadTokenFileName);
+    }
+
+    private void sendErrorToApp(int errorCode, String message) {
+        try {
+            mInternalCallback.onError(errorCode, message);
+        } catch (RemoteException e) {
+            // Ignore, should not happen locally.
+        }
+    }
+}
diff --git a/android/telephony/MbmsStreamingManager.java b/android/telephony/MbmsStreamingManager.java
deleted file mode 100644
index b6b253e..0000000
--- a/android/telephony/MbmsStreamingManager.java
+++ /dev/null
@@ -1,323 +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 android.telephony;
-
-import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.telephony.mbms.InternalStreamingManagerCallback;
-import android.telephony.mbms.InternalStreamingServiceCallback;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
-import android.telephony.mbms.MbmsUtils;
-import android.telephony.mbms.StreamingService;
-import android.telephony.mbms.StreamingServiceCallback;
-import android.telephony.mbms.StreamingServiceInfo;
-import android.telephony.mbms.vendor.IMbmsStreamingService;
-import android.util.Log;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-/**
- * This class provides functionality for streaming media over MBMS.
- */
-public class MbmsStreamingManager {
-    private static final String LOG_TAG = "MbmsStreamingManager";
-
-    /**
-     * Service action which must be handled by the middleware implementing the MBMS streaming
-     * interface.
-     * @hide
-     */
-    @SystemApi
-    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
-    public static final String MBMS_STREAMING_SERVICE_ACTION =
-            "android.telephony.action.EmbmsStreaming";
-
-    private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
-
-    private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
-    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
-        @Override
-        public void binderDied() {
-            sIsInitialized.set(false);
-            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
-        }
-    };
-
-    private InternalStreamingManagerCallback mInternalCallback;
-
-    private final Context mContext;
-    private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
-
-    /** @hide */
-    private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback,
-                    int subscriptionId, Handler handler) {
-        mContext = context;
-        mSubscriptionId = subscriptionId;
-        if (handler == null) {
-            handler = new Handler(Looper.getMainLooper());
-        }
-        mInternalCallback = new InternalStreamingManagerCallback(callback, handler);
-    }
-
-    /**
-     * Create a new MbmsStreamingManager using the given subscription ID.
-     *
-     * Note that this call will bind a remote service. You may not call this method on your app's
-     * main thread. This may throw an {@link MbmsException}, indicating errors that may happen
-     * during the initialization or binding process.
-     *
-     *
-     * You may only have one instance of {@link MbmsStreamingManager} per UID. If you call this
-     * method while there is an active instance of {@link MbmsStreamingManager} in your process
-     * (in other words, one that has not had {@link #dispose()} called on it), this method will
-     * throw an {@link MbmsException}. If you call this method in a different process
-     * running under the same UID, an error will be indicated via
-     * {@link MbmsStreamingManagerCallback#onError(int, String)}.
-     *
-     * Note that initialization may fail asynchronously. If you wish to try again after you
-     * receive such an asynchronous error, you must call dispose() on the instance of
-     * {@link MbmsStreamingManager} that you received before calling this method again.
-     *
-     * @param context The {@link Context} to use.
-     * @param callback A callback object on which you wish to receive results of asynchronous
-     *                 operations.
-     * @param subscriptionId The subscription ID to use.
-     * @param handler The handler you wish to receive callbacks on. If null, callbacks will be
-     *                processed on the main looper (in other words, the looper returned from
-     *                {@link Looper#getMainLooper()}).
-     */
-    public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler)
-            throws MbmsException {
-        if (!sIsInitialized.compareAndSet(false, true)) {
-            throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
-        }
-        MbmsStreamingManager manager = new MbmsStreamingManager(context, callback,
-                subscriptionId, handler);
-        try {
-            manager.bindAndInitialize();
-        } catch (MbmsException e) {
-            sIsInitialized.set(false);
-            throw e;
-        }
-        return manager;
-    }
-
-    /**
-     * Create a new MbmsStreamingManager using the system default data subscription ID.
-     * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
-     */
-    public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback callback, Handler handler)
-            throws MbmsException {
-        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
-    }
-
-    /**
-     * Create a new MbmsStreamingManager using the system default data subscription ID and
-     * default {@link Handler}.
-     * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
-     */
-    public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback callback)
-            throws MbmsException {
-        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null);
-    }
-
-    /**
-     * Terminates this instance, ending calls to the registered listener.  Also terminates
-     * any streaming services spawned from this instance.
-     *
-     * May throw an {@link IllegalStateException}
-     */
-    public void dispose() {
-        try {
-            IMbmsStreamingService streamingService = mService.get();
-            if (streamingService == null) {
-                // Ignore and return, assume already disposed.
-                return;
-            }
-            streamingService.dispose(mSubscriptionId);
-        } catch (RemoteException e) {
-            // Ignore for now
-        } finally {
-            mService.set(null);
-            sIsInitialized.set(false);
-        }
-    }
-
-    /**
-     * An inspection API to retrieve the list of streaming media currently be advertised.
-     * The results are returned asynchronously through the previously registered callback.
-     * serviceClasses lets the app filter on types of programming and is opaque data between
-     * the app and the carrier.
-     *
-     * Multiple calls replace the list of serviceClasses of interest.
-     *
-     * This may throw an {@link MbmsException} containing any error in
-     * {@link android.telephony.mbms.MbmsException.GeneralErrors},
-     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
-     *
-     * May also throw an unchecked {@link IllegalArgumentException} or an
-     * {@link IllegalStateException}
-     *
-     * @param classList A list of streaming service classes that the app would like updates on.
-     */
-    public void getStreamingServices(List<String> classList) throws MbmsException {
-        IMbmsStreamingService streamingService = mService.get();
-        if (streamingService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-        try {
-            int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList);
-            if (returnCode != MbmsException.SUCCESS) {
-                throw new MbmsException(returnCode);
-            }
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Remote process died");
-            mService.set(null);
-            sIsInitialized.set(false);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-    }
-
-    /**
-     * Starts streaming a requested service, reporting status to the indicated callback.
-     * Returns an object used to control that stream. The stream may not be ready for consumption
-     * immediately upon return from this method -- wait until the streaming state has been
-     * reported via
-     * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
-     *
-     * May throw an
-     * {@link MbmsException} containing any of the error codes in
-     * {@link android.telephony.mbms.MbmsException.GeneralErrors},
-     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
-     *
-     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
-     *
-     * Asynchronous errors through the callback include any of the errors in
-     * {@link android.telephony.mbms.MbmsException.GeneralErrors} or
-     * {@link android.telephony.mbms.MbmsException.StreamingErrors}.
-     *
-     * @param serviceInfo The information about the service to stream.
-     * @param callback A callback that'll be called when something about the stream changes.
-     * @param handler A handler that calls to {@code callback} should be called on. If null,
-     *                defaults to the handler provided via
-     *                {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
-     * @return An instance of {@link StreamingService} through which the stream can be controlled.
-     */
-    public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
-            StreamingServiceCallback callback, Handler handler) throws MbmsException {
-        IMbmsStreamingService streamingService = mService.get();
-        if (streamingService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
-        }
-
-        InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
-                callback, handler == null ? mInternalCallback.getHandler() : handler);
-
-        StreamingService serviceForApp = new StreamingService(
-                mSubscriptionId, streamingService, serviceInfo, serviceCallback);
-
-        try {
-            int returnCode = streamingService.startStreaming(
-                    mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
-            if (returnCode != MbmsException.SUCCESS) {
-                throw new MbmsException(returnCode);
-            }
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Remote process died");
-            mService.set(null);
-            sIsInitialized.set(false);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        }
-
-        return serviceForApp;
-    }
-
-    private void bindAndInitialize() throws MbmsException {
-        MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
-                new ServiceConnection() {
-                    @Override
-                    public void onServiceConnected(ComponentName name, IBinder service) {
-                        IMbmsStreamingService streamingService =
-                                IMbmsStreamingService.Stub.asInterface(service);
-                        int result;
-                        try {
-                            result = streamingService.initialize(mInternalCallback,
-                                    mSubscriptionId);
-                        } catch (RemoteException e) {
-                            Log.e(LOG_TAG, "Service died before initialization");
-                            sendErrorToApp(
-                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
-                                    e.toString());
-                            sIsInitialized.set(false);
-                            return;
-                        } catch (RuntimeException e) {
-                            Log.e(LOG_TAG, "Runtime exception during initialization");
-                            sendErrorToApp(
-                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
-                                    e.toString());
-                            sIsInitialized.set(false);
-                            return;
-                        }
-                        if (result != MbmsException.SUCCESS) {
-                            sendErrorToApp(result, "Error returned during initialization");
-                            sIsInitialized.set(false);
-                            return;
-                        }
-                        try {
-                            streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
-                        } catch (RemoteException e) {
-                            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
-                                    "Middleware lost during initialization");
-                            sIsInitialized.set(false);
-                            return;
-                        }
-                        mService.set(streamingService);
-                    }
-
-                    @Override
-                    public void onServiceDisconnected(ComponentName name) {
-                        sIsInitialized.set(false);
-                        mService.set(null);
-                    }
-                });
-    }
-
-    private void sendErrorToApp(int errorCode, String message) {
-        try {
-            mInternalCallback.error(errorCode, message);
-        } catch (RemoteException e) {
-            // Ignore, should not happen locally.
-        }
-    }
-}
diff --git a/android/telephony/MbmsStreamingSession.java b/android/telephony/MbmsStreamingSession.java
new file mode 100644
index 0000000..a8c4607
--- /dev/null
+++ b/android/telephony/MbmsStreamingSession.java
@@ -0,0 +1,330 @@
+/*
+ * 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 android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.mbms.InternalStreamingSessionCallback;
+import android.telephony.mbms.InternalStreamingServiceCallback;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsStreamingSessionCallback;
+import android.telephony.mbms.MbmsUtils;
+import android.telephony.mbms.StreamingService;
+import android.telephony.mbms.StreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
+import android.telephony.mbms.vendor.IMbmsStreamingService;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/**
+ * This class provides functionality for streaming media over MBMS.
+ */
+public class MbmsStreamingSession implements AutoCloseable {
+    private static final String LOG_TAG = "MbmsStreamingSession";
+
+    /**
+     * Service action which must be handled by the middleware implementing the MBMS streaming
+     * interface.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String MBMS_STREAMING_SERVICE_ACTION =
+            "android.telephony.action.EmbmsStreaming";
+
+    private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
+
+    private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            sIsInitialized.set(false);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
+        }
+    };
+
+    private InternalStreamingSessionCallback mInternalCallback;
+    private Set<StreamingService> mKnownActiveStreamingServices = new ArraySet<>();
+
+    private final Context mContext;
+    private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
+
+    /** @hide */
+    private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback,
+                    int subscriptionId, Handler handler) {
+        mContext = context;
+        mSubscriptionId = subscriptionId;
+        if (handler == null) {
+            handler = new Handler(Looper.getMainLooper());
+        }
+        mInternalCallback = new InternalStreamingSessionCallback(callback, handler);
+    }
+
+    /**
+     * Create a new {@link MbmsStreamingSession} using the given subscription ID.
+     *
+     * Note that this call will bind a remote service. You may not call this method on your app's
+     * main thread.
+     *
+     * You may only have one instance of {@link MbmsStreamingSession} per UID. If you call this
+     * method while there is an active instance of {@link MbmsStreamingSession} in your process
+     * (in other words, one that has not had {@link #close()} called on it), this method will
+     * throw an {@link IllegalStateException}. If you call this method in a different process
+     * running under the same UID, an error will be indicated via
+     * {@link MbmsStreamingSessionCallback#onError(int, String)}.
+     *
+     * Note that initialization may fail asynchronously. If you wish to try again after you
+     * receive such an asynchronous error, you must call {@link #close()} on the instance of
+     * {@link MbmsStreamingSession} that you received before calling this method again.
+     *
+     * @param context The {@link Context} to use.
+     * @param callback A callback object on which you wish to receive results of asynchronous
+     *                 operations.
+     * @param subscriptionId The subscription ID to use.
+     * @param handler The handler you wish to receive callbacks on.
+     * @return An instance of {@link MbmsStreamingSession}, or null if an error occurred.
+     */
+    public static @Nullable MbmsStreamingSession create(@NonNull Context context,
+            final @NonNull MbmsStreamingSessionCallback callback, int subscriptionId,
+            @NonNull Handler handler) {
+        if (!sIsInitialized.compareAndSet(false, true)) {
+            throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession");
+        }
+        MbmsStreamingSession session = new MbmsStreamingSession(context, callback,
+                subscriptionId, handler);
+
+        final int result = session.bindAndInitialize();
+        if (result != MbmsErrors.SUCCESS) {
+            sIsInitialized.set(false);
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onError(result, null);
+                }
+            });
+            return null;
+        }
+        return session;
+    }
+
+    /**
+     * Create a new {@link MbmsStreamingSession} using the system default data subscription ID.
+     * See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}.
+     */
+    public static MbmsStreamingSession create(@NonNull Context context,
+            @NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) {
+        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
+    }
+
+    /**
+     * Terminates this instance. Also terminates
+     * any streaming services spawned from this instance as if
+     * {@link StreamingService#stopStreaming()} had been called on them. After this method returns,
+     * no further callbacks originating from the middleware will be enqueued on the provided
+     * instance of {@link MbmsStreamingSessionCallback}, but callbacks that have already been
+     * enqueued will still be delivered.
+     *
+     * It is safe to call {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)} to
+     * obtain another instance of {@link MbmsStreamingSession} immediately after this method
+     * returns.
+     *
+     * May throw an {@link IllegalStateException}
+     */
+    public void close() {
+        try {
+            IMbmsStreamingService streamingService = mService.get();
+            if (streamingService == null) {
+                // Ignore and return, assume already disposed.
+                return;
+            }
+            streamingService.dispose(mSubscriptionId);
+            for (StreamingService s : mKnownActiveStreamingServices) {
+                s.getCallback().stop();
+            }
+            mKnownActiveStreamingServices.clear();
+        } catch (RemoteException e) {
+            // Ignore for now
+        } finally {
+            mService.set(null);
+            sIsInitialized.set(false);
+            mInternalCallback.stop();
+        }
+    }
+
+    /**
+     * An inspection API to retrieve the list of streaming media currently be advertised.
+     * The results are returned asynchronously via
+     * {@link MbmsStreamingSessionCallback#onStreamingServicesUpdated(List)} on the callback
+     * provided upon creation.
+     *
+     * Multiple calls replace the list of service classes of interest.
+     *
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
+     *
+     * @param serviceClassList A list of streaming service classes that the app would like updates
+     *                         on. The exact names of these classes should be negotiated with the
+     *                         wireless carrier separately.
+     */
+    public void requestUpdateStreamingServices(List<String> serviceClassList) {
+        IMbmsStreamingService streamingService = mService.get();
+        if (streamingService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+        try {
+            int returnCode = streamingService.requestUpdateStreamingServices(
+                    mSubscriptionId, serviceClassList);
+            if (returnCode != MbmsErrors.SUCCESS) {
+                sendErrorToApp(returnCode, null);
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService.set(null);
+            sIsInitialized.set(false);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+        }
+    }
+
+    /**
+     * Starts streaming a requested service, reporting status to the indicated callback.
+     * Returns an object used to control that stream. The stream may not be ready for consumption
+     * immediately upon return from this method -- wait until the streaming state has been
+     * reported via
+     * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
+     *
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+     *
+     * Asynchronous errors through the callback include any of the errors in
+     * {@link MbmsErrors.GeneralErrors} or
+     * {@link MbmsErrors.StreamingErrors}.
+     *
+     * @param serviceInfo The information about the service to stream.
+     * @param callback A callback that'll be called when something about the stream changes.
+     * @param handler A handler that calls to {@code callback} should be called on.
+     * @return An instance of {@link StreamingService} through which the stream can be controlled.
+     *         May be {@code null} if an error occurred.
+     */
+    public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo,
+            StreamingServiceCallback callback, @NonNull Handler handler) {
+        IMbmsStreamingService streamingService = mService.get();
+        if (streamingService == null) {
+            throw new IllegalStateException("Middleware not yet bound");
+        }
+
+        InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
+                callback, handler);
+
+        StreamingService serviceForApp = new StreamingService(
+                mSubscriptionId, streamingService, this, serviceInfo, serviceCallback);
+        mKnownActiveStreamingServices.add(serviceForApp);
+
+        try {
+            int returnCode = streamingService.startStreaming(
+                    mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
+            if (returnCode != MbmsErrors.SUCCESS) {
+                sendErrorToApp(returnCode, null);
+                return null;
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService.set(null);
+            sIsInitialized.set(false);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return null;
+        }
+
+        return serviceForApp;
+    }
+
+    /** @hide */
+    public void onStreamingServiceStopped(StreamingService service) {
+        mKnownActiveStreamingServices.remove(service);
+    }
+
+    private int bindAndInitialize() {
+        return MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
+                new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder service) {
+                        IMbmsStreamingService streamingService =
+                                IMbmsStreamingService.Stub.asInterface(service);
+                        int result;
+                        try {
+                            result = streamingService.initialize(mInternalCallback,
+                                    mSubscriptionId);
+                        } catch (RemoteException e) {
+                            Log.e(LOG_TAG, "Service died before initialization");
+                            sendErrorToApp(
+                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+                                    e.toString());
+                            sIsInitialized.set(false);
+                            return;
+                        } catch (RuntimeException e) {
+                            Log.e(LOG_TAG, "Runtime exception during initialization");
+                            sendErrorToApp(
+                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+                                    e.toString());
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        if (result != MbmsErrors.SUCCESS) {
+                            sendErrorToApp(result, "Error returned during initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        try {
+                            streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
+                        } catch (RemoteException e) {
+                            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
+                                    "Middleware lost during initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        mService.set(streamingService);
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName name) {
+                        sIsInitialized.set(false);
+                        mService.set(null);
+                    }
+                });
+    }
+
+    private void sendErrorToApp(int errorCode, String message) {
+        try {
+            mInternalCallback.onError(errorCode, message);
+        } catch (RemoteException e) {
+            // Ignore, should not happen locally.
+        }
+    }
+}
diff --git a/android/telephony/mbms/DownloadProgressListener.java b/android/telephony/mbms/DownloadProgressListener.java
deleted file mode 100644
index d91e9ad..0000000
--- a/android/telephony/mbms/DownloadProgressListener.java
+++ /dev/null
@@ -1,48 +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 android.telephony.mbms;
-
-import android.os.RemoteException;
-
-/**
- * A optional listener class used by download clients to track progress.
- * @hide
- */
-public class DownloadProgressListener extends IDownloadProgressListener.Stub {
-    /**
-     * Gives process callbacks for a given DownloadRequest.
-     * This is optionally specified when requesting a download and
-     * only lives while the app is running - it's unlikely to be useful for
-     * downloads far in the future.
-     *
-     * @param request a {@link DownloadRequest}, indicating which download is being referenced.
-     * @param fileInfo a {@link FileInfo} specifying the file to report progress on.  Note that
-     *   the request may result in many files being downloaded and the client
-     *   may not have been able to get a list of them in advance.
-     * @param currentDownloadSize is the current amount downloaded.
-     * @param fullDownloadSize is the total number of bytes that make up the downloaded content.
-     *   This may be different from the decoded final size, but is useful in gauging download
-     *   progress.
-     * @param currentDecodedSize is the number of bytes that have been decoded.
-     * @param fullDecodedSize is the total number of bytes that make up the final decoded content.
-     */
-    @Override
-    public void progress(DownloadRequest request, FileInfo fileInfo,
-            int currentDownloadSize, int fullDownloadSize,
-            int currentDecodedSize, int fullDecodedSize) throws RemoteException {
-    }
-}
diff --git a/android/telephony/mbms/DownloadRequest.java b/android/telephony/mbms/DownloadRequest.java
index eae9011..5a57f32 100644
--- a/android/telephony/mbms/DownloadRequest.java
+++ b/android/telephony/mbms/DownloadRequest.java
@@ -16,6 +16,7 @@
 
 package android.telephony.mbms;
 
+import android.annotation.SystemApi;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Parcel;
@@ -37,34 +38,27 @@
 import java.util.Objects;
 
 /**
- * A Parcelable class describing a pending Cell-Broadcast download request
- * @hide
+ * Describes a request to download files over cell-broadcast. Instances of this class should be
+ * created by the app when requesting a download, and instances of this class will be passed back
+ * to the app when the middleware updates the status of the download.
  */
-public class DownloadRequest implements Parcelable {
+public final class DownloadRequest implements Parcelable {
     // Version code used to keep token calculation consistent.
     private static final int CURRENT_VERSION = 1;
     private static final String LOG_TAG = "MbmsDownloadRequest";
 
-    /**
-     * Maximum permissible length for the app's download-completion intent, when serialized via
-     * {@link Intent#toUri(int)}.
-     */
+    /** @hide */
     public static final int MAX_APP_INTENT_SIZE = 50000;
 
-    /**
-     * Maximum permissible length for the app's destination path, when serialized via
-     * {@link Uri#toString()}.
-     */
+    /** @hide */
     public static final int MAX_DESTINATION_URI_SIZE = 50000;
 
     /** @hide */
     private static class OpaqueDataContainer implements Serializable {
-        private final String destinationUri;
         private final String appIntent;
         private final int version;
 
-        public OpaqueDataContainer(String destinationUri, String appIntent, int version) {
-            this.destinationUri = destinationUri;
+        public OpaqueDataContainer(String appIntent, int version) {
             this.appIntent = appIntent;
             this.version = version;
         }
@@ -73,7 +67,6 @@
     public static class Builder {
         private String fileServiceId;
         private Uri source;
-        private Uri dest;
         private int subscriptionId;
         private String appIntent;
         private int version = CURRENT_VERSION;
@@ -91,8 +84,8 @@
         /**
          * Set the service ID for the download request. For use by the middleware only.
          * @hide
-         * TODO: systemapi
          */
+        @SystemApi
         public Builder setServiceId(String serviceId) {
             fileServiceId = serviceId;
             return this;
@@ -101,7 +94,6 @@
         /**
          * Sets the source URI for the download request to be built.
          * @param source
-         * @return
          */
         public Builder setSource(Uri source) {
             this.source = source;
@@ -109,25 +101,8 @@
         }
 
         /**
-         * Sets the destination URI for the download request to be built. The middleware should
-         * not set this directly.
-         * @param dest A URI obtained from {@link Uri#fromFile(File)}, denoting the requested
-         *             final destination of the download.
-         * @return
-         */
-        public Builder setDest(Uri dest) {
-            if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
-                throw new IllegalArgumentException("Destination uri must not exceed length " +
-                        MAX_DESTINATION_URI_SIZE);
-            }
-            this.dest = dest;
-            return this;
-        }
-
-        /**
          * Set the subscription ID on which the file(s) should be downloaded.
          * @param subscriptionId
-         * @return
          */
         public Builder setSubscriptionId(int subscriptionId) {
             this.subscriptionId = subscriptionId;
@@ -141,7 +116,6 @@
          *
          * The middleware should not use this method.
          * @param intent
-         * @return
          */
         public Builder setAppIntent(Intent intent) {
             this.appIntent = intent.toUri(0);
@@ -158,17 +132,15 @@
          * manager code, but is irrelevant to the middleware.
          * @param data A byte array, the contents of which should have been originally obtained
          *             from {@link DownloadRequest#getOpaqueData()}.
-         * @return
-         * TODO: systemapi
          * @hide
          */
+        @SystemApi
         public Builder setOpaqueData(byte[] data) {
             try {
                 ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
                 OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
                 version = dataContainer.version;
                 appIntent = dataContainer.appIntent;
-                dest = Uri.parse(dataContainer.destinationUri);
             } catch (IOException e) {
                 // Really should never happen
                 Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
@@ -181,24 +153,21 @@
         }
 
         public DownloadRequest build() {
-            return new DownloadRequest(fileServiceId, source, dest,
-                    subscriptionId, appIntent, version);
+            return new DownloadRequest(fileServiceId, source, subscriptionId, appIntent, version);
         }
     }
 
     private final String fileServiceId;
     private final Uri sourceUri;
-    private final Uri destinationUri;
     private final int subscriptionId;
     private final String serializedResultIntentForApp;
     private final int version;
 
     private DownloadRequest(String fileServiceId,
-            Uri source, Uri dest,
-            int sub, String appIntent, int version) {
+            Uri source, int sub,
+            String appIntent, int version) {
         this.fileServiceId = fileServiceId;
         sourceUri = source;
-        destinationUri = dest;
         subscriptionId = sub;
         serializedResultIntentForApp = appIntent;
         this.version = version;
@@ -211,7 +180,6 @@
     private DownloadRequest(DownloadRequest dr) {
         fileServiceId = dr.fileServiceId;
         sourceUri = dr.sourceUri;
-        destinationUri = dr.destinationUri;
         subscriptionId = dr.subscriptionId;
         serializedResultIntentForApp = dr.serializedResultIntentForApp;
         version = dr.version;
@@ -220,7 +188,6 @@
     private DownloadRequest(Parcel in) {
         fileServiceId = in.readString();
         sourceUri = in.readParcelable(getClass().getClassLoader());
-        destinationUri = in.readParcelable(getClass().getClassLoader());
         subscriptionId = in.readInt();
         serializedResultIntentForApp = in.readString();
         version = in.readInt();
@@ -233,7 +200,6 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(fileServiceId);
         out.writeParcelable(sourceUri, flags);
-        out.writeParcelable(destinationUri, flags);
         out.writeInt(subscriptionId);
         out.writeString(serializedResultIntentForApp);
         out.writeInt(version);
@@ -254,14 +220,6 @@
     }
 
     /**
-     * For use by the client app only.
-     * @return The URI of the final destination of the download.
-     */
-    public Uri getDestinationUri() {
-        return destinationUri;
-    }
-
-    /**
      * @return The subscription ID on which to perform MBMS operations.
      */
     public int getSubscriptionId() {
@@ -287,14 +245,14 @@
      * {@link Builder#setOpaqueData(byte[])}.
      * @return A byte array of opaque data to persist.
      * @hide
-     * TODO: systemapi
      */
+    @SystemApi
     public byte[] getOpaqueData() {
         try {
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
             OpaqueDataContainer container = new OpaqueDataContainer(
-                    destinationUri.toString(), serializedResultIntentForApp, version);
+                    serializedResultIntentForApp, version);
             stream.writeObject(container);
             stream.flush();
             return byteArrayOutputStream.toByteArray();
@@ -321,6 +279,22 @@
     };
 
     /**
+     * Maximum permissible length for the app's destination path, when serialized via
+     * {@link Uri#toString()}.
+     */
+    public static int getMaxAppIntentSize() {
+        return MAX_APP_INTENT_SIZE;
+    }
+
+    /**
+     * Maximum permissible length for the app's download-completion intent, when serialized via
+     * {@link Intent#toUri(int)}.
+     */
+    public static int getMaxDestinationUriSize() {
+        return MAX_DESTINATION_URI_SIZE;
+    }
+
+    /**
      * @hide
      */
     public boolean isMultipartDownload() {
@@ -344,7 +318,6 @@
         if (version >= 1) {
             // Hash the source URI, destination URI, and the app intent
             digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
-            digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
             digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
         }
         // Add updates for future versions here
@@ -365,13 +338,12 @@
                 version == request.version &&
                 Objects.equals(fileServiceId, request.fileServiceId) &&
                 Objects.equals(sourceUri, request.sourceUri) &&
-                Objects.equals(destinationUri, request.destinationUri) &&
                 Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(fileServiceId, sourceUri, destinationUri,
+        return Objects.hash(fileServiceId, sourceUri,
                 subscriptionId, serializedResultIntentForApp, version);
     }
 }
diff --git a/android/telephony/mbms/DownloadStateCallback.java b/android/telephony/mbms/DownloadStateCallback.java
new file mode 100644
index 0000000..86920bd
--- /dev/null
+++ b/android/telephony/mbms/DownloadStateCallback.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.telephony.mbms;
+
+import android.telephony.MbmsDownloadSession;
+
+/**
+ * A optional listener class used by download clients to track progress. Apps should extend this
+ * class and pass an instance into
+ * {@link MbmsDownloadSession#download(DownloadRequest)}
+ *
+ * This is optionally specified when requesting a download and will only be called while the app
+ * is running.
+ */
+public class DownloadStateCallback {
+
+    /**
+     * Called when the middleware wants to report progress for a file in a {@link DownloadRequest}.
+     *
+     * @param request a {@link DownloadRequest}, indicating which download is being referenced.
+     * @param fileInfo a {@link FileInfo} specifying the file to report progress on.  Note that
+     *   the request may result in many files being downloaded and the client
+     *   may not have been able to get a list of them in advance.
+     * @param currentDownloadSize is the current amount downloaded.
+     * @param fullDownloadSize is the total number of bytes that make up the downloaded content.
+     *   This may be different from the decoded final size, but is useful in gauging download
+     *   progress.
+     * @param currentDecodedSize is the number of bytes that have been decoded.
+     * @param fullDecodedSize is the total number of bytes that make up the final decoded content.
+     */
+    public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
+            int currentDownloadSize, int fullDownloadSize,
+            int currentDecodedSize, int fullDecodedSize) {
+    }
+
+    /**
+     * Gives download state callbacks for a file in a {@link DownloadRequest}.
+     *
+     * @param request a {@link DownloadRequest}, indicating which download is being referenced.
+     * @param fileInfo a {@link FileInfo} specifying the file to report progress on.  Note that
+     *   the request may result in many files being downloaded and the client
+     *   may not have been able to get a list of them in advance.
+     * @param state The current state of the download.
+     */
+    public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+            @MbmsDownloadSession.DownloadStatus int state) {
+    }
+}
diff --git a/android/telephony/mbms/FileInfo.java b/android/telephony/mbms/FileInfo.java
index f97131d..0d737b5 100644
--- a/android/telephony/mbms/FileInfo.java
+++ b/android/telephony/mbms/FileInfo.java
@@ -16,26 +16,18 @@
 
 package android.telephony.mbms;
 
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
- * A Parcelable class Cell-Broadcast downloadable file information.
- * @hide
+ * Describes a single file that is available over MBMS.
  */
-public class FileInfo implements Parcelable {
+public final class FileInfo implements Parcelable {
 
-    /**
-     * The URI into the carriers infrastructure which points to this file.
-     * This is used internally but is also one of the few pieces of data about the content that is
-     * exposed and may be needed for disambiguation by the application.
-     */
     private final Uri uri;
 
-    /**
-     * The mime type of the content.
-     */
     private final String mimeType;
 
     public static final Parcelable.Creator<FileInfo> CREATOR =
@@ -53,8 +45,8 @@
 
     /**
      * @hide
-     * TODO: systemapi
      */
+    @SystemApi
     public FileInfo(Uri uri, String mimeType) {
         this.uri = uri;
         this.mimeType = mimeType;
@@ -76,10 +68,17 @@
         return 0;
     }
 
+    /**
+     * @return The URI in the carrier's infrastructure which points to this file. Apps should
+     * negotiate the contents of this URI separately with the carrier.
+     */
     public Uri getUri() {
         return uri;
     }
 
+    /**
+     * @return The MIME type of the file.
+     */
     public String getMimeType() {
         return mimeType;
     }
diff --git a/android/telephony/mbms/FileServiceInfo.java b/android/telephony/mbms/FileServiceInfo.java
index 8afe4d3..d8d7f48 100644
--- a/android/telephony/mbms/FileServiceInfo.java
+++ b/android/telephony/mbms/FileServiceInfo.java
@@ -16,6 +16,7 @@
 
 package android.telephony.mbms;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -26,13 +27,14 @@
 import java.util.Map;
 
 /**
- * A Parcelable class Cell-Broadcast downloadable file information.
- * @hide
+ * Describes a file service available from the carrier from which files can be downloaded via
+ * cell-broadcast.
  */
-public class FileServiceInfo extends ServiceInfo implements Parcelable {
+public final class FileServiceInfo extends ServiceInfo implements Parcelable {
     private final List<FileInfo> files;
 
-    /** @hide TODO: systemapi */
+    /** @hide */
+    @SystemApi
     public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
             List<Locale> newLocales, String newServiceId, Date start, Date end,
             List<FileInfo> newFiles) {
@@ -56,7 +58,7 @@
     FileServiceInfo(Parcel in) {
         super(in);
         files = new ArrayList<FileInfo>();
-        in.readList(files, null);
+        in.readList(files, FileInfo.class.getClassLoader());
     }
 
     @Override
@@ -70,8 +72,12 @@
         return 0;
     }
 
+    /**
+     * @return A list of files available from this service. Note that this list may not be
+     * exhaustive -- the middleware may not have information on all files that are available.
+     * Consult the carrier for an authoritative and exhaustive list.
+     */
     public List<FileInfo> getFiles() {
         return files;
     }
-
 }
diff --git a/android/telephony/mbms/InternalStreamingManagerCallback.java b/android/telephony/mbms/InternalDownloadSessionCallback.java
similarity index 64%
copy from android/telephony/mbms/InternalStreamingManagerCallback.java
copy to android/telephony/mbms/InternalDownloadSessionCallback.java
index b52df8c..a7a5958 100644
--- a/android/telephony/mbms/InternalStreamingManagerCallback.java
+++ b/android/telephony/mbms/InternalDownloadSessionCallback.java
@@ -18,25 +18,28 @@
 
 import android.os.Handler;
 import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
-import android.telephony.mbms.StreamingServiceInfo;
 
 import java.util.List;
 
 /** @hide */
-public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
-    private final Handler mHandler;
-    private final MbmsStreamingManagerCallback mAppCallback;
+public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallback.Stub {
 
-    public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback,
+    private final Handler mHandler;
+    private final MbmsDownloadSessionCallback mAppCallback;
+    private volatile boolean mIsStopped = false;
+
+    public InternalDownloadSessionCallback(MbmsDownloadSessionCallback appCallback,
             Handler handler) {
         mAppCallback = appCallback;
         mHandler = handler;
     }
 
     @Override
-    public void error(int errorCode, String message) throws RemoteException {
+    public void onError(final int errorCode, final String message) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -46,18 +49,25 @@
     }
 
     @Override
-    public void streamingServicesUpdated(List<StreamingServiceInfo> services)
-            throws RemoteException {
+    public void onFileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                mAppCallback.onStreamingServicesUpdated(services);
+                mAppCallback.onFileServicesUpdated(services);
             }
         });
     }
 
     @Override
-    public void middlewareReady() throws RemoteException {
+    public void onMiddlewareReady() throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -69,4 +79,8 @@
     public Handler getHandler() {
         return mHandler;
     }
+
+    public void stop() {
+        mIsStopped = true;
+    }
 }
diff --git a/android/telephony/mbms/InternalDownloadStateCallback.java b/android/telephony/mbms/InternalDownloadStateCallback.java
new file mode 100644
index 0000000..8702952
--- /dev/null
+++ b/android/telephony/mbms/InternalDownloadStateCallback.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.telephony.mbms;
+
+import android.os.Handler;
+import android.os.RemoteException;
+
+/**
+ * @hide
+ */
+public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {
+    private final Handler mHandler;
+    private final DownloadStateCallback mAppCallback;
+    private volatile boolean mIsStopped = false;
+
+    public InternalDownloadStateCallback(DownloadStateCallback appCallback, Handler handler) {
+        mAppCallback = appCallback;
+        mHandler = handler;
+    }
+
+    @Override
+    public void onProgressUpdated(final DownloadRequest request, final FileInfo fileInfo,
+            final int currentDownloadSize, final int fullDownloadSize, final int currentDecodedSize,
+            final int fullDecodedSize) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+                        fullDownloadSize, currentDecodedSize, fullDecodedSize);
+            }
+        });
+    }
+
+    @Override
+    public void onStateUpdated(final DownloadRequest request, final FileInfo fileInfo,
+            final int state) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onStateUpdated(request, fileInfo, state);
+            }
+        });
+    }
+
+    public void stop() {
+        mIsStopped = true;
+    }
+}
diff --git a/android/telephony/mbms/InternalStreamingServiceCallback.java b/android/telephony/mbms/InternalStreamingServiceCallback.java
index bb337b2..eb6579c 100644
--- a/android/telephony/mbms/InternalStreamingServiceCallback.java
+++ b/android/telephony/mbms/InternalStreamingServiceCallback.java
@@ -23,6 +23,7 @@
 public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub {
     private final StreamingServiceCallback mAppCallback;
     private final Handler mHandler;
+    private volatile boolean mIsStopped = false;
 
     public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) {
         mAppCallback = appCallback;
@@ -30,7 +31,11 @@
     }
 
     @Override
-    public void error(int errorCode, String message) throws RemoteException {
+    public void onError(final int errorCode, final String message) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -40,7 +45,11 @@
     }
 
     @Override
-    public void streamStateUpdated(int state, int reason) throws RemoteException {
+    public void onStreamStateUpdated(final int state, final int reason) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -50,7 +59,11 @@
     }
 
     @Override
-    public void mediaDescriptionUpdated() throws RemoteException {
+    public void onMediaDescriptionUpdated() throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -60,7 +73,11 @@
     }
 
     @Override
-    public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
+    public void onBroadcastSignalStrengthUpdated(final int signalStrength) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -70,7 +87,11 @@
     }
 
     @Override
-    public void streamMethodUpdated(int methodType) throws RemoteException {
+    public void onStreamMethodUpdated(final int methodType) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -78,4 +99,8 @@
             }
         });
     }
+
+    public void stop() {
+        mIsStopped = true;
+    }
 }
diff --git a/android/telephony/mbms/InternalStreamingManagerCallback.java b/android/telephony/mbms/InternalStreamingSessionCallback.java
similarity index 68%
rename from android/telephony/mbms/InternalStreamingManagerCallback.java
rename to android/telephony/mbms/InternalStreamingSessionCallback.java
index b52df8c..d782d12 100644
--- a/android/telephony/mbms/InternalStreamingManagerCallback.java
+++ b/android/telephony/mbms/InternalStreamingSessionCallback.java
@@ -18,25 +18,27 @@
 
 import android.os.Handler;
 import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
-import android.telephony.mbms.StreamingServiceInfo;
 
 import java.util.List;
 
 /** @hide */
-public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
+public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallback.Stub {
     private final Handler mHandler;
-    private final MbmsStreamingManagerCallback mAppCallback;
+    private final MbmsStreamingSessionCallback mAppCallback;
+    private volatile boolean mIsStopped = false;
 
-    public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback,
+    public InternalStreamingSessionCallback(MbmsStreamingSessionCallback appCallback,
             Handler handler) {
         mAppCallback = appCallback;
         mHandler = handler;
     }
 
     @Override
-    public void error(int errorCode, String message) throws RemoteException {
+    public void onError(final int errorCode, final String message) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -46,8 +48,12 @@
     }
 
     @Override
-    public void streamingServicesUpdated(List<StreamingServiceInfo> services)
+    public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services)
             throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -57,7 +63,11 @@
     }
 
     @Override
-    public void middlewareReady() throws RemoteException {
+    public void onMiddlewareReady() throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -69,4 +79,8 @@
     public Handler getHandler() {
         return mHandler;
     }
+
+    public void stop() {
+        mIsStopped = true;
+    }
 }
diff --git a/android/telephony/mbms/MbmsDownloadManagerCallback.java b/android/telephony/mbms/MbmsDownloadManagerCallback.java
deleted file mode 100644
index 17291d0..0000000
--- a/android/telephony/mbms/MbmsDownloadManagerCallback.java
+++ /dev/null
@@ -1,64 +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 android.telephony.mbms;
-
-import android.os.RemoteException;
-import android.telephony.MbmsDownloadManager;
-
-import java.util.List;
-
-/**
- * A Parcelable class with Cell-Broadcast service information.
- * @hide
- */
-public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub {
-
-    @Override
-    public void error(int errorCode, String message) throws RemoteException {
-        // default implementation empty
-    }
-
-    /**
-     * Called to indicate published File Services have changed.
-     *
-     * This will only be called after the application has requested
-     * a list of file services and specified a service class list
-     * of interest AND the results of a subsequent getFileServices
-     * call with the same service class list would return different
-     * results.
-     *
-     * @param services a List of FileServiceInfos
-     *
-     */
-    @Override
-    public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException {
-        // default implementation empty
-    }
-
-    /**
-     * Called to indicate that the middleware has been initialized and is ready.
-     *
-     * Before this method is called, calling any method on an instance of
-     * {@link android.telephony.MbmsDownloadManager} will result in an {@link MbmsException}
-     * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
-     * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
-     */
-    @Override
-    public void middlewareReady() throws RemoteException {
-        // default implementation empty
-    }
-}
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index ba7d120..61415b5 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -16,6 +16,7 @@
 
 package android.telephony.mbms;
 
+import android.annotation.SystemApi;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -24,8 +25,8 @@
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Bundle;
-import android.telephony.MbmsDownloadManager;
-import android.telephony.mbms.vendor.VendorIntents;
+import android.telephony.MbmsDownloadSession;
+import android.telephony.mbms.vendor.VendorUtils;
 import android.util.Log;
 
 import java.io.File;
@@ -35,52 +36,74 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
 
 /**
- * @hide
+ * The {@link BroadcastReceiver} responsible for handling intents sent from the middleware. Apps
+ * that wish to download using MBMS APIs should declare this class in their AndroidManifest.xml as
+ * follows:
+<pre>{@code
+<receiver
+    android:name="android.telephony.mbms.MbmsDownloadReceiver"
+    android:permission="android.permission.SEND_EMBMS_INTENTS"
+    android:enabled="true"
+    android:exported="true">
+</receiver>}</pre>
  */
 public class MbmsDownloadReceiver extends BroadcastReceiver {
+    /** @hide */
     public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token";
+    /** @hide */
     public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
 
     /**
-     * TODO: @SystemApi all these result codes
      * Indicates that the requested operation completed without error.
+     * @hide
      */
+    @SystemApi
     public static final int RESULT_OK = 0;
 
     /**
      * Indicates that the intent sent had an invalid action. This will be the result if
      * {@link Intent#getAction()} returns anything other than
-     * {@link VendorIntents#ACTION_DOWNLOAD_RESULT_INTERNAL},
-     * {@link VendorIntents#ACTION_FILE_DESCRIPTOR_REQUEST}, or
-     * {@link VendorIntents#ACTION_CLEANUP}.
+     * {@link VendorUtils#ACTION_DOWNLOAD_RESULT_INTERNAL},
+     * {@link VendorUtils#ACTION_FILE_DESCRIPTOR_REQUEST}, or
+     * {@link VendorUtils#ACTION_CLEANUP}.
      * This is a fatal result code and no result extras should be expected.
+     * @hide
      */
+    @SystemApi
     public static final int RESULT_INVALID_ACTION = 1;
 
     /**
      * Indicates that the intent was missing some required extras.
      * This is a fatal result code and no result extras should be expected.
+     * @hide
      */
+    @SystemApi
     public static final int RESULT_MALFORMED_INTENT = 2;
 
     /**
-     * Indicates that the supplied value for {@link VendorIntents#EXTRA_TEMP_FILE_ROOT}
+     * Indicates that the supplied value for {@link VendorUtils#EXTRA_TEMP_FILE_ROOT}
      * does not match what the app has stored.
      * This is a fatal result code and no result extras should be expected.
+     * @hide
      */
+    @SystemApi
     public static final int RESULT_BAD_TEMP_FILE_ROOT = 3;
 
     /**
      * Indicates that the manager was unable to move the completed download to its final location.
      * This is a fatal result code and no result extras should be expected.
+     * @hide
      */
+    @SystemApi
     public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4;
 
     /**
@@ -88,35 +111,48 @@
      * descriptors.
      * This is a non-fatal result code -- some file descriptors may still be generated, but there
      * is no guarantee that they will be the same number as requested.
+     * @hide
      */
+    @SystemApi
     public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5;
 
+    /**
+     * Indicates that the manager was unable to notify the app of the completed download.
+     * This is a fatal result code and no result extras should be expected.
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_APP_NOTIFICATION_ERROR = 6;
+
+
     private static final String LOG_TAG = "MbmsDownloadReceiver";
     private static final String TEMP_FILE_SUFFIX = ".embms.temp";
-    private static final int MAX_TEMP_FILE_RETRIES = 5;
+    private static final String TEMP_FILE_STAGING_LOCATION = "staged_completed_files";
 
+    private static final int MAX_TEMP_FILE_RETRIES = 5;
 
     private String mFileProviderAuthorityCache = null;
     private String mMiddlewarePackageNameCache = null;
 
+    /** @hide */
     @Override
     public void onReceive(Context context, Intent intent) {
         if (!verifyIntentContents(context, intent)) {
             setResultCode(RESULT_MALFORMED_INTENT);
             return;
         }
-        if (!Objects.equals(intent.getStringExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT),
+        if (!Objects.equals(intent.getStringExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT),
                 MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) {
             setResultCode(RESULT_BAD_TEMP_FILE_ROOT);
             return;
         }
 
-        if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
+        if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
             moveDownloadedFile(context, intent);
             cleanupPostMove(context, intent);
-        } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+        } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
             generateTempFiles(context, intent);
-        } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) {
+        } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) {
             cleanupTempFiles(context, intent);
         } else {
             setResultCode(RESULT_INVALID_ACTION);
@@ -124,30 +160,31 @@
     }
 
     private boolean verifyIntentContents(Context context, Intent intent) {
-        if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
+        if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
+            if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT)) {
                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(VendorIntents.EXTRA_REQUEST)) {
+            if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
                 Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
+            if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
                 Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_FILE_INFO)) {
+            if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO)) {
                 Log.w(LOG_TAG, "Download result did not include the associated file info. " +
                         "Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(VendorIntents.EXTRA_FINAL_URI)) {
+            if (!intent.hasExtra(VendorUtils.EXTRA_FINAL_URI)) {
                 Log.w(LOG_TAG, "Download result did not include the path to the final " +
                         "temp file. Ignoring.");
                 return false;
             }
-            DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
+            DownloadRequest request = intent.getParcelableExtra(
+                    MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
             String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
             File expectedTokenFile = new File(
                     MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
@@ -157,27 +194,27 @@
                         "Expected " + expectedTokenFile);
                 return false;
             }
-        } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
-            if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) {
-                Log.w(LOG_TAG, "Temp file request did not include the associated service info." +
+        } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+            if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) {
+                Log.w(LOG_TAG, "Temp file request did not include the associated service id." +
                         " Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
+            if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
                 Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
                 return false;
             }
-        } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) {
-            if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) {
-                Log.w(LOG_TAG, "Cleanup request did not include the associated service info." +
+        } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) {
+            if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) {
+                Log.w(LOG_TAG, "Cleanup request did not include the associated service id." +
                         " Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
+            if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
                 Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE)) {
+            if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE)) {
                 Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " +
                         "Ignoring.");
                 return false;
@@ -187,21 +224,26 @@
     }
 
     private void moveDownloadedFile(Context context, Intent intent) {
-        DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
+        DownloadRequest request = intent.getParcelableExtra(
+                MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
         Intent intentForApp = request.getIntentForApp();
+        if (intentForApp == null) {
+            Log.i(LOG_TAG, "Malformed app notification intent");
+            setResultCode(RESULT_APP_NOTIFICATION_ERROR);
+            return;
+        }
 
-        int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
-                MbmsDownloadManager.RESULT_CANCELLED);
-        intentForApp.putExtra(MbmsDownloadManager.EXTRA_RESULT, result);
+        int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
+                MbmsDownloadSession.RESULT_CANCELLED);
+        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
 
-        if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) {
+        if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {
             Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
             context.sendBroadcast(intentForApp);
             return;
         }
 
-        Uri destinationUri = request.getDestinationUri();
-        Uri finalTempFile = intent.getParcelableExtra(VendorIntents.EXTRA_FINAL_URI);
+        Uri finalTempFile = intent.getParcelableExtra(VendorUtils.EXTRA_FINAL_URI);
         if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
             Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
             setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
@@ -209,30 +251,37 @@
         }
 
         FileInfo completedFileInfo =
-                (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO);
-        String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo);
+                (FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO);
+        Path stagingDirectory = FileSystems.getDefault().getPath(
+                MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath(),
+                TEMP_FILE_STAGING_LOCATION);
 
-        Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath);
-        if (finalFileLocation == null) {
+        Uri stagedFileLocation;
+        try {
+            stagedFileLocation = stageTempFile(finalTempFile, stagingDirectory);
+        } catch (IOException e) {
             Log.w(LOG_TAG, "Failed to move temp file to final destination");
             setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
             return;
         }
-        intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation);
-        intentForApp.putExtra(MbmsDownloadManager.EXTRA_FILE_INFO, completedFileInfo);
+        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI,
+                stagedFileLocation);
+        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
+        intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
 
         context.sendBroadcast(intentForApp);
         setResultCode(RESULT_OK);
     }
 
     private void cleanupPostMove(Context context, Intent intent) {
-        DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
+        DownloadRequest request = intent.getParcelableExtra(
+                MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
         if (request == null) {
             Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
             return;
         }
 
-        List<Uri> tempFiles = intent.getParcelableExtra(VendorIntents.EXTRA_TEMP_LIST);
+        List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST);
         if (tempFiles == null) {
             return;
         }
@@ -246,16 +295,15 @@
     }
 
     private void generateTempFiles(Context context, Intent intent) {
-        FileServiceInfo serviceInfo =
-                intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO);
-        if (serviceInfo == null) {
-            Log.w(LOG_TAG, "Temp file request did not include the associated service info. " +
+        String serviceId = intent.getStringExtra(VendorUtils.EXTRA_SERVICE_ID);
+        if (serviceId == null) {
+            Log.w(LOG_TAG, "Temp file request did not include the associated service id. " +
                     "Ignoring.");
             setResultCode(RESULT_MALFORMED_INTENT);
             return;
         }
-        int fdCount = intent.getIntExtra(VendorIntents.EXTRA_FD_COUNT, 0);
-        List<Uri> pausedList = intent.getParcelableExtra(VendorIntents.EXTRA_PAUSED_LIST);
+        int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0);
+        List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST);
 
         if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
             Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -265,22 +313,20 @@
         }
 
         ArrayList<UriPathPair> freshTempFiles =
-                generateFreshTempFiles(context, serviceInfo, fdCount);
+                generateFreshTempFiles(context, serviceId, fdCount);
         ArrayList<UriPathPair> pausedFiles =
-                generateUrisForPausedFiles(context, serviceInfo, pausedList);
+                generateUrisForPausedFiles(context, serviceId, pausedList);
 
         Bundle result = new Bundle();
-        result.putParcelableArrayList(VendorIntents.EXTRA_FREE_URI_LIST, freshTempFiles);
-        result.putParcelableArrayList(VendorIntents.EXTRA_PAUSED_URI_LIST, pausedFiles);
+        result.putParcelableArrayList(VendorUtils.EXTRA_FREE_URI_LIST, freshTempFiles);
+        result.putParcelableArrayList(VendorUtils.EXTRA_PAUSED_URI_LIST, pausedFiles);
         setResultCode(RESULT_OK);
         setResultExtras(result);
     }
 
-    private ArrayList<UriPathPair> generateFreshTempFiles(Context context,
-            FileServiceInfo serviceInfo,
+    private ArrayList<UriPathPair> generateFreshTempFiles(Context context, String serviceId,
             int freshFdCount) {
-        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
-                serviceInfo.getServiceId());
+        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceId);
         if (!tempFileDir.exists()) {
             tempFileDir.mkdirs();
         }
@@ -324,14 +370,14 @@
     }
 
     private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context,
-            FileServiceInfo serviceInfo, List<Uri> pausedFiles) {
+            String serviceId, List<Uri> pausedFiles) {
         if (pausedFiles == null) {
             return new ArrayList<>(0);
         }
         ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
 
         for (Uri fileUri : pausedFiles) {
-            if (!verifyTempFilePath(context, serviceInfo.getServiceId(), fileUri)) {
+            if (!verifyTempFilePath(context, serviceId, fileUri)) {
                 Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
                 setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
                 continue;
@@ -353,12 +399,10 @@
     }
 
     private void cleanupTempFiles(Context context, Intent intent) {
-        FileServiceInfo serviceInfo =
-                intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO);
-        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
-                serviceInfo.getServiceId());
+        String serviceId = intent.getStringExtra(VendorUtils.EXTRA_SERVICE_ID);
+        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceId);
         final List<Uri> filesInUse =
-                intent.getParcelableArrayListExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE);
+                intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE);
         File[] filesToDelete = tempFileDir.listFiles(new FileFilter() {
             @Override
             public boolean accept(File file) {
@@ -385,63 +429,22 @@
         }
     }
 
-    private static String calculateDestinationFileRelativePath(DownloadRequest request,
-            FileInfo info) {
-        List<String> filePathComponents = info.getUri().getPathSegments();
-        List<String> requestPathComponents = request.getSourceUri().getPathSegments();
-        Iterator<String> filePathIter = filePathComponents.iterator();
-        Iterator<String> requestPathIter = requestPathComponents.iterator();
-
-        StringBuilder pathBuilder = new StringBuilder();
-        // Iterate through the segments of the carrier's URI to the file, along with the segments
-        // of the source URI specified in the download request. The relative path is calculated
-        // as the tail of the file's URI that does not match the path segments in the source URI.
-        while (filePathIter.hasNext()) {
-            String currFilePathComponent = filePathIter.next();
-            if (requestPathIter.hasNext()) {
-                String requestFilePathComponent = requestPathIter.next();
-                if (requestFilePathComponent.equals(currFilePathComponent)) {
-                    continue;
-                }
-            }
-            pathBuilder.append(currFilePathComponent);
-            pathBuilder.append('/');
-        }
-        // remove the trailing slash
-        if (pathBuilder.length() > 0) {
-            pathBuilder.deleteCharAt(pathBuilder.length() - 1);
-        }
-        return pathBuilder.toString();
-    }
-
     /*
-     * Moves a tempfile located at fromPath to a new location at toPath. If
-     * toPath is a directory, the destination file will be located at  relativePath
-     * underneath toPath.
+     * Moves a tempfile located at fromPath to a new location in the staging directory.
      */
-    private static Uri moveTempFile(Uri fromPath, Uri toPath, String relativePath) {
+    private static Uri stageTempFile(Uri fromPath, Path stagingDirectory) throws IOException {
         if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
             Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");
             return null;
         }
-        if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) {
-            Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme");
-            return null;
-        }
 
-        File fromFile = new File(fromPath.getSchemeSpecificPart());
-        File toFile = new File(toPath.getSchemeSpecificPart());
-        if (toFile.isDirectory()) {
-            toFile = new File(toFile, relativePath);
+        Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath());
+        if (!Files.isDirectory(stagingDirectory)) {
+            Files.createDirectory(stagingDirectory);
         }
-        toFile.getParentFile().mkdirs();
+        Path result = Files.move(fromFile, stagingDirectory.resolve(fromFile.getFileName()));
 
-        if (fromFile.renameTo(toFile)) {
-            return Uri.fromFile(toFile);
-        } else if (manualMove(fromFile, toFile)) {
-            return Uri.fromFile(toFile);
-        }
-        return null;
+        return Uri.fromFile(result.toFile());
     }
 
     private static boolean verifyTempFilePath(Context context, String serviceId,
@@ -493,7 +496,7 @@
     private String getMiddlewarePackageCached(Context context) {
         if (mMiddlewarePackageNameCache == null) {
             mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context,
-                    MbmsDownloadManager.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
+                    MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
         }
         return mMiddlewarePackageNameCache;
     }
diff --git a/android/telephony/mbms/MbmsDownloadSessionCallback.java b/android/telephony/mbms/MbmsDownloadSessionCallback.java
new file mode 100644
index 0000000..77dea6f
--- /dev/null
+++ b/android/telephony/mbms/MbmsDownloadSessionCallback.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.telephony.mbms;
+
+import android.telephony.MbmsDownloadSession;
+
+import java.util.List;
+
+/**
+ * A callback class that apps should use to receive information on file downloads over
+ * cell-broadcast.
+ */
+public class MbmsDownloadSessionCallback {
+
+    /**
+     * Indicates that the middleware has encountered an asynchronous error.
+     * @param errorCode Any error code listed in {@link MbmsErrors}
+     * @param message A message, intended for debugging purposes, describing the error in further
+     *                detail.
+     */
+    public void onError(int errorCode, String message) {
+        // default implementation empty
+    }
+
+    /**
+     * Called to indicate published File Services have changed.
+     *
+     * This will only be called after the application has requested a list of file services and
+     * specified a service class list of interest via
+     * {@link MbmsDownloadSession#requestUpdateFileServices(List)}. If there are subsequent calls to
+     * {@link MbmsDownloadSession#requestUpdateFileServices(List)},
+     * this method may not be called again if
+     * the list of service classes would remain the same.
+     *
+     * @param services The most recently updated list of available file services.
+     */
+    public void onFileServicesUpdated(List<FileServiceInfo> services) {
+        // default implementation empty
+    }
+
+    /**
+     * Called to indicate that the middleware has been initialized and is ready.
+     *
+     * Before this method is called, calling any method on an instance of
+     * {@link MbmsDownloadSession} will result in an {@link IllegalStateException}
+     * being thrown or {@link #onError(int, String)} being called with error code
+     * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+     */
+    public void onMiddlewareReady() {
+        // default implementation empty
+    }
+}
diff --git a/android/telephony/mbms/MbmsException.java b/android/telephony/mbms/MbmsErrors.java
similarity index 87%
rename from android/telephony/mbms/MbmsException.java
rename to android/telephony/mbms/MbmsErrors.java
index 6de5a18..af0af24 100644
--- a/android/telephony/mbms/MbmsException.java
+++ b/android/telephony/mbms/MbmsErrors.java
@@ -16,7 +16,9 @@
 
 package android.telephony.mbms;
 
-public class MbmsException extends Exception {
+import android.telephony.MbmsStreamingSession;
+
+public class MbmsErrors {
     /** Indicates that the operation was successful. */
     public static final int SUCCESS = 0;
 
@@ -30,8 +32,8 @@
 
     /**
      * Indicates that the app attempted to perform an operation on an instance of
-     * TODO: link android.telephony.MbmsDownloadManager or
-     * {@link android.telephony.MbmsStreamingManager} without being bound to the middleware.
+     * {@link android.telephony.MbmsDownloadSession} or
+     * {@link MbmsStreamingSession} without being bound to the middleware.
      */
     public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2;
 
@@ -46,8 +48,7 @@
         private InitializationErrors() {}
         /**
          * Indicates that the app tried to create more than one instance each of
-         * {@link android.telephony.MbmsStreamingManager} or
-         * TODO: link android.telephony.MbmsDownloadManager
+         * {@link MbmsStreamingSession} or {@link android.telephony.MbmsDownloadSession}.
          */
         public static final int ERROR_DUPLICATE_INITIALIZE = 101;
         /** Indicates that the app is not authorized to access media via MBMS.*/
@@ -64,8 +65,8 @@
         private GeneralErrors() {}
         /**
          * Indicates that the app attempted to perform an operation before receiving notification
-         * that the middleware is ready via {@link MbmsStreamingManagerCallback#onMiddlewareReady()}
-         * or TODO: link MbmsDownloadManagerCallback#middlewareReady
+         * that the middleware is ready via {@link MbmsStreamingSessionCallback#onMiddlewareReady()}
+         * or {@link MbmsDownloadSessionCallback#onMiddlewareReady()}.
          */
         public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;
         /**
@@ -107,7 +108,7 @@
 
         /**
          * Indicates that the app called
-         * {@link android.telephony.MbmsStreamingManager#startStreaming(
+         * {@link MbmsStreamingSession#startStreaming(
          * StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)}
          * more than once for the same {@link StreamingServiceInfo}.
          */
@@ -116,10 +117,9 @@
 
     /**
      * Indicates the errors that are applicable only to the file-download use-case
-     * TODO: unhide
-     * @hide
      */
     public static class DownloadErrors {
+        private DownloadErrors() { }
         /**
          * Indicates that the app is not allowed to change the temp file root at this time due to
          * outstanding download requests.
@@ -130,15 +130,5 @@
         public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402;
     }
 
-    private final int mErrorCode;
-
-    /** @hide */
-    public MbmsException(int errorCode) {
-        super();
-        mErrorCode = errorCode;
-    }
-
-    public int getErrorCode() {
-        return mErrorCode;
-    }
+    private MbmsErrors() {}
 }
diff --git a/android/telephony/mbms/MbmsStreamingManagerCallback.java b/android/telephony/mbms/MbmsStreamingSessionCallback.java
similarity index 75%
rename from android/telephony/mbms/MbmsStreamingManagerCallback.java
rename to android/telephony/mbms/MbmsStreamingSessionCallback.java
index b31ffa7..5c130a0 100644
--- a/android/telephony/mbms/MbmsStreamingManagerCallback.java
+++ b/android/telephony/mbms/MbmsStreamingSessionCallback.java
@@ -16,25 +16,26 @@
 
 package android.telephony.mbms;
 
+import android.annotation.Nullable;
 import android.content.Context;
-import android.os.RemoteException;
-import android.telephony.MbmsStreamingManager;
+import android.os.Handler;
+import android.telephony.MbmsStreamingSession;
 
 import java.util.List;
 
 /**
  * A callback class that is used to receive information from the middleware on MBMS streaming
  * services. An instance of this object should be passed into
- * {@link android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}.
+ * {@link MbmsStreamingSession#create(Context, MbmsStreamingSessionCallback, int, Handler)}.
  */
-public class MbmsStreamingManagerCallback {
+public class MbmsStreamingSessionCallback {
     /**
      * Called by the middleware when it has detected an error condition. The possible error codes
-     * are listed in {@link MbmsException}.
+     * are listed in {@link MbmsErrors}.
      * @param errorCode The error code.
      * @param message A human-readable message generated by the middleware for debugging purposes.
      */
-    public void onError(int errorCode, String message) {
+    public void onError(int errorCode, @Nullable String message) {
         // default implementation empty
     }
 
@@ -47,8 +48,7 @@
      * call with the same service class list would return different
      * results.
      *
-     * @param services a List of StreamingServiceInfos
-     *
+     * @param services The list of available services.
      */
     public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
         // default implementation empty
@@ -58,9 +58,9 @@
      * Called to indicate that the middleware has been initialized and is ready.
      *
      * Before this method is called, calling any method on an instance of
-     * {@link android.telephony.MbmsStreamingManager} will result in an {@link MbmsException}
-     * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
-     * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+     * {@link MbmsStreamingSession} will result in an {@link IllegalStateException} or an error
+     * delivered via {@link #onError(int, String)} with error code
+     * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.
      */
     public void onMiddlewareReady() {
         // default implementation empty
diff --git a/android/telephony/mbms/MbmsTempFileProvider.java b/android/telephony/mbms/MbmsTempFileProvider.java
index c4d033b..689becd 100644
--- a/android/telephony/mbms/MbmsTempFileProvider.java
+++ b/android/telephony/mbms/MbmsTempFileProvider.java
@@ -23,12 +23,11 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.telephony.MbmsDownloadSession;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -39,7 +38,6 @@
  * @hide
  */
 public class MbmsTempFileProvider extends ContentProvider {
-    public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
     public static final String TEMP_FILE_ROOT_PREF_FILE_NAME = "MbmsTempFileRootPrefs";
     public static final String TEMP_FILE_ROOT_PREF_NAME = "mbms_temp_file_root";
 
@@ -182,8 +180,8 @@
             if (storedTempFileRoot != null) {
                 return new File(storedTempFileRoot).getCanonicalFile();
             } else {
-                return new File(context.getFilesDir(), DEFAULT_TOP_LEVEL_TEMP_DIRECTORY)
-                        .getCanonicalFile();
+                return new File(context.getFilesDir(),
+                        MbmsDownloadSession.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile();
             }
         } catch (IOException e) {
             throw new RuntimeException("Unable to canonicalize temp file root path " + e);
diff --git a/android/telephony/mbms/MbmsUtils.java b/android/telephony/mbms/MbmsUtils.java
index 4b913f8..d38d8a7 100644
--- a/android/telephony/mbms/MbmsUtils.java
+++ b/android/telephony/mbms/MbmsUtils.java
@@ -68,19 +68,20 @@
         return downloadServices.get(0).serviceInfo;
     }
 
-    public static void startBinding(Context context, String serviceAction,
-            ServiceConnection serviceConnection) throws MbmsException {
+    public static int startBinding(Context context, String serviceAction,
+            ServiceConnection serviceConnection) {
         Intent bindIntent = new Intent();
         ServiceInfo mbmsServiceInfo =
                 MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);
 
         if (mbmsServiceInfo == null) {
-            throw new MbmsException(MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE);
+            return MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE;
         }
 
         bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo));
 
         context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
+        return MbmsErrors.SUCCESS;
     }
 
     /**
diff --git a/android/telephony/mbms/ServiceInfo.java b/android/telephony/mbms/ServiceInfo.java
index c01604b..9a01ed0 100644
--- a/android/telephony/mbms/ServiceInfo.java
+++ b/android/telephony/mbms/ServiceInfo.java
@@ -16,6 +16,8 @@
 
 package android.telephony.mbms;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -26,12 +28,13 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
 
 /**
  * Describes a cell-broadcast service. This class should not be instantiated directly -- use
- * {@link StreamingServiceInfo} or FileServiceInfo TODO: add link once that's unhidden
+ * {@link StreamingServiceInfo} or {@link FileServiceInfo}
  */
 public class ServiceInfo {
     // arbitrary limit on the number of locale -> name pairs we support
@@ -58,6 +61,13 @@
         if (newLocales.size() > MAP_LIMIT) {
             throw new RuntimeException("bad locales length " + newLocales.size());
         }
+
+        for (Locale l : newLocales) {
+            if (!newNames.containsKey(l)) {
+                throw new IllegalArgumentException("A name must be provided for each locale");
+            }
+        }
+
         names = new HashMap(newNames.size());
         names.putAll(newNames);
         className = newClassName;
@@ -114,16 +124,25 @@
     }
 
     /**
-     * User displayable names listed by language. Do not modify the map returned from this method.
+     * Get the user-displayable name for this cell-broadcast service corresponding to the
+     * provided {@link Locale}.
+     * @param locale The {@link Locale} in which you want the name of the service. This must be a
+     *               value from the list returned by {@link #getLocales()} -- an
+     *               {@link java.util.NoSuchElementException} may be thrown otherwise.
+     * @return The {@link CharSequence} providing the name of the service in the given
+     *         {@link Locale}
      */
-    public Map<Locale, String> getNames() {
-        return names;
+    public @NonNull CharSequence getNameForLocale(@NonNull Locale locale) {
+        if (!names.containsKey(locale)) {
+            throw new NoSuchElementException("Locale not supported");
+        }
+        return names.get(locale);
     }
 
     /**
      * The class name for this service - used to categorize and filter
      */
-    public String getClassName() {
+    public String getServiceClassName() {
         return className;
     }
 
diff --git a/android/telephony/mbms/StreamingService.java b/android/telephony/mbms/StreamingService.java
index 1d66bac..ec9134a 100644
--- a/android/telephony/mbms/StreamingService.java
+++ b/android/telephony/mbms/StreamingService.java
@@ -17,8 +17,10 @@
 package android.telephony.mbms;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.net.Uri;
 import android.os.RemoteException;
+import android.telephony.MbmsStreamingSession;
 import android.telephony.mbms.vendor.IMbmsStreamingService;
 import android.util.Log;
 
@@ -27,7 +29,7 @@
 
 /**
  * Class used to represent a single MBMS stream. After a stream has been started with
- * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
  * StreamingServiceCallback, android.os.Handler)},
  * this class is used to hold information about the stream and control it.
  */
@@ -63,7 +65,7 @@
 
     /**
      * State changed due to a call to {@link #stopStreaming()} or
-     * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+     * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
      * StreamingServiceCallback, android.os.Handler)}
      */
     public static final int REASON_BY_USER_REQUEST = 1;
@@ -101,6 +103,7 @@
     public final static int UNICAST_METHOD   = 2;
 
     private final int mSubscriptionId;
+    private final MbmsStreamingSession mParentSession;
     private final StreamingServiceInfo mServiceInfo;
     private final InternalStreamingServiceCallback mCallback;
 
@@ -111,25 +114,25 @@
      */
     public StreamingService(int subscriptionId,
             IMbmsStreamingService service,
+            MbmsStreamingSession session,
             StreamingServiceInfo streamingServiceInfo,
             InternalStreamingServiceCallback callback) {
         mSubscriptionId = subscriptionId;
+        mParentSession = session;
         mService = service;
         mServiceInfo = streamingServiceInfo;
         mCallback = callback;
     }
 
     /**
-     * Retreive the Uri used to play this stream.
+     * Retrieve the Uri used to play this stream.
      *
-     * This may throw a {@link MbmsException} with the error code
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
      *
-     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
-     *
-     * @return The {@link Uri} to pass to the streaming client.
+     * @return The {@link Uri} to pass to the streaming client, or {@code null} if an error
+     *         occurred.
      */
-    public Uri getPlaybackUri() throws MbmsException {
+    public @Nullable Uri getPlaybackUri() {
         if (mService == null) {
             throw new IllegalStateException("No streaming service attached");
         }
@@ -139,25 +142,26 @@
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService = null;
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+            mParentSession.onStreamingServiceStopped(this);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+            return null;
         }
     }
 
     /**
-     * Retreive the info for this StreamingService.
+     * Retrieve the {@link StreamingServiceInfo} corresponding to this stream.
      */
     public StreamingServiceInfo getInfo() {
         return mServiceInfo;
     }
 
     /**
-     * Stop streaming this service.
-     * This may throw a {@link MbmsException} with the error code
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+     * Stop streaming this service. Further operations on this object will fail with an
+     * {@link IllegalStateException}.
      *
-     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      */
-    public void stopStreaming() throws MbmsException {
+    public void stopStreaming() {
         if (mService == null) {
             throw new IllegalStateException("No streaming service attached");
         }
@@ -167,32 +171,22 @@
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService = null;
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+        } finally {
+            mParentSession.onStreamingServiceStopped(this);
         }
     }
 
-    /**
-     * Disposes of this stream. Further operations on this object will fail with an
-     * {@link IllegalStateException}.
-     *
-     * This may throw a {@link MbmsException} with the error code
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
-     * May also throw an {@link IllegalStateException}
-     */
-    public void dispose() throws MbmsException {
-        if (mService == null) {
-            throw new IllegalStateException("No streaming service attached");
-        }
+    /** @hide */
+    public InternalStreamingServiceCallback getCallback() {
+        return mCallback;
+    }
 
+    private void sendErrorToApp(int errorCode, String message) {
         try {
-            mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId());
+            mCallback.onError(errorCode, message);
         } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Remote process died");
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        } catch (IllegalArgumentException e) {
-            throw new IllegalStateException("StreamingService state inconsistent with middleware");
-        } finally {
-            mService = null;
+            // Ignore, should not happen locally.
         }
     }
 }
diff --git a/android/telephony/mbms/StreamingServiceCallback.java b/android/telephony/mbms/StreamingServiceCallback.java
index b72c715..0903824 100644
--- a/android/telephony/mbms/StreamingServiceCallback.java
+++ b/android/telephony/mbms/StreamingServiceCallback.java
@@ -16,6 +16,8 @@
 
 package android.telephony.mbms;
 
+import android.annotation.Nullable;
+
 /**
  * A callback class for use when the application is actively streaming content. The middleware
  * will provide updates on the status of the stream via this callback.
@@ -33,11 +35,11 @@
 
     /**
      * Called by the middleware when it has detected an error condition in this stream. The
-     * possible error codes are listed in {@link MbmsException}.
+     * possible error codes are listed in {@link MbmsErrors}.
      * @param errorCode The error code.
      * @param message A human-readable message generated by the middleware for debugging purposes.
      */
-    public void onError(int errorCode, String message) {
+    public void onError(int errorCode, @Nullable String message) {
         // default implementation empty
     }
 
diff --git a/android/telephony/mbms/UriPathPair.java b/android/telephony/mbms/UriPathPair.java
index 7acc270..187e9ee 100644
--- a/android/telephony/mbms/UriPathPair.java
+++ b/android/telephony/mbms/UriPathPair.java
@@ -16,13 +16,20 @@
 
 package android.telephony.mbms;
 
+import android.annotation.SystemApi;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.mbms.vendor.VendorUtils;
 
-/** @hide */
-public class UriPathPair implements Parcelable {
+/**
+ * Wrapper for a pair of {@link Uri}s that describe a temp file used by the middleware to
+ * download files via cell-broadcast.
+ * @hide
+ */
+@SystemApi
+public final class UriPathPair implements Parcelable {
     private final Uri mFilePathUri;
     private final Uri mContentUri;
 
@@ -40,7 +47,7 @@
     }
 
     /** @hide */
-    protected UriPathPair(Parcel in) {
+    private UriPathPair(Parcel in) {
         mFilePathUri = in.readParcelable(Uri.class.getClassLoader());
         mContentUri = in.readParcelable(Uri.class.getClassLoader());
     }
@@ -57,12 +64,23 @@
         }
     };
 
-    /** future systemapi */
+    /**
+     * Returns the file-path {@link Uri}. This has scheme {@code file} and points to the actual
+     * location on disk where the temp file resides. Use this when sending {@link Uri}s back to the
+     * app in the intents in {@link VendorUtils}.
+     * @return A {@code file} {@link Uri}.
+     */
     public Uri getFilePathUri() {
         return mFilePathUri;
     }
 
-    /** future systemapi */
+    /**
+     * Returns the content {@link Uri} that may be used with
+     * {@link ContentResolver#openFileDescriptor(Uri, String)} to obtain a
+     * {@link android.os.ParcelFileDescriptor} to a temp file to write to. This {@link Uri} will
+     * expire if the middleware process dies.
+     * @return A {@code content} {@link Uri}
+     */
     public Uri getContentUri() {
         return mContentUri;
     }
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 71713d0..d845a57 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -17,40 +17,50 @@
 package android.telephony.mbms.vendor;
 
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
-import android.telephony.mbms.DownloadProgressListener;
+import android.telephony.MbmsDownloadSession;
 import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStateCallback;
 import android.telephony.mbms.FileInfo;
 import android.telephony.mbms.FileServiceInfo;
-import android.telephony.mbms.IDownloadProgressListener;
-import android.telephony.mbms.IMbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.IDownloadStateCallback;
+import android.telephony.mbms.IMbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsErrors;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
- * Base class for MbmsDownloadService. The middleware should extend this base class rather than
- * the aidl stub for compatibility
+ * Base class for MbmsDownloadService. The middleware should return an instance of this object from
+ * its {@link android.app.Service#onBind(Intent)} method.
  * @hide
- * TODO: future systemapi
  */
+@SystemApi
 public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
+    private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
+    private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
+
     /**
      * Initialize the download service for this app and subId, registering the listener.
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
      * will be intercepted and passed to the app as
-     * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+     * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
      *
-     * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
-     * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
-     * {@link IMbmsDownloadManagerCallback#error(int, String)}.
+     * May return any value from {@link MbmsErrors.InitializationErrors}
+     * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
+     * {@link IMbmsDownloadSessionCallback#onError(int, String)}.
      *
      * @param callback The callback to use to communicate with the app.
      * @param subscriptionId The subscription ID to use.
      */
-    public int initialize(int subscriptionId, MbmsDownloadManagerCallback callback)
+    public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback)
             throws RemoteException {
         return 0;
     }
@@ -60,22 +70,42 @@
      * @hide
      */
     @Override
-    public final int initialize(int subscriptionId,
-            final IMbmsDownloadManagerCallback callback) throws RemoteException {
-        return initialize(subscriptionId, new MbmsDownloadManagerCallback() {
+    public final int initialize(final int subscriptionId,
+            final IMbmsDownloadSessionCallback callback) throws RemoteException {
+        final int uid = Binder.getCallingUid();
+        callback.asBinder().linkToDeath(new DeathRecipient() {
             @Override
-            public void error(int errorCode, String message) throws RemoteException {
-                callback.error(errorCode, message);
+            public void binderDied() {
+                onAppCallbackDied(uid, subscriptionId);
+            }
+        }, 0);
+
+        return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
+            @Override
+            public void onError(int errorCode, String message) {
+                try {
+                    callback.onError(errorCode, message);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
             }
 
             @Override
-            public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException {
-                callback.fileServicesUpdated(services);
+            public void onFileServicesUpdated(List<FileServiceInfo> services) {
+                try {
+                    callback.onFileServicesUpdated(services);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
             }
 
             @Override
-            public void middlewareReady() throws RemoteException {
-                callback.middlewareReady();
+            public void onMiddlewareReady() {
+                try {
+                    callback.onMiddlewareReady();
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
             }
         });
     }
@@ -83,7 +113,7 @@
     /**
      * Registers serviceClasses of interest with the appName/subId key.
      * Starts async fetching data on streaming services of matching classes to be reported
-     * later via {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
+     * later via {@link IMbmsDownloadSessionCallback#onFileServicesUpdated(List)}
      *
      * Note that subsequent calls with the same uid and subId will replace
      * the service class list.
@@ -94,11 +124,11 @@
      * @param serviceClasses The service classes that the app wishes to get info on. The strings
      *                       may contain arbitrary data as negotiated between the app and the
      *                       carrier.
-     * @return One of {@link MbmsException#SUCCESS} or
-     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
+     * @return One of {@link MbmsErrors#SUCCESS} or
+     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
      */
     @Override
-    public int getFileServices(int subscriptionId, List<String> serviceClasses)
+    public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)
             throws RemoteException {
         return 0;
     }
@@ -110,13 +140,13 @@
      *
      * If the calling app (as identified by the calling UID) currently has any pending download
      * requests that have not been canceled, the middleware must return
-     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
+     * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
      *
      * @param subscriptionId The subscription id the download is operating under.
      * @param rootDirectoryPath The path to the app's temp file root directory.
-     * @return {@link MbmsException#SUCCESS},
-     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
-     *         {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
+     * @return {@link MbmsErrors#SUCCESS},
+     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
+     *         {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
      */
     @Override
     public int setTempFileRootDirectory(int subscriptionId,
@@ -132,12 +162,32 @@
      * this is not the case, an {@link IllegalStateException} may be thrown.
      *
      * @param downloadRequest An object describing the set of files to be downloaded.
-     * @param listener A listener through which the middleware can provide progress updates to
-     *                 the app while both are still running.
-     * @return Any error from {@link android.telephony.mbms.MbmsException.GeneralErrors}
-     *         or {@link MbmsException#SUCCESS}
+     * @return Any error from {@link MbmsErrors.GeneralErrors}
+     *         or {@link MbmsErrors#SUCCESS}
      */
-    public int download(DownloadRequest downloadRequest, DownloadProgressListener listener) {
+    @Override
+    public int download(DownloadRequest downloadRequest) throws RemoteException {
+        return 0;
+    }
+
+    /**
+     * Registers a download state callbacks for the provided {@link DownloadRequest}.
+     *
+     * This method is called by the app when it wants to request updates on the progress or
+     * status of the download.
+     *
+     * If the middleware is not aware of a download having been requested with the provided
+     *
+     * {@link DownloadRequest} in the past,
+     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
+     * must be returned.
+     *
+     * @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
+     *                        for which progress updates are being requested.
+     * @param callback The callback object to use.
+     */
+    public int registerStateCallback(DownloadRequest downloadRequest,
+            DownloadStateCallback callback) throws RemoteException {
         return 0;
     }
 
@@ -146,24 +196,101 @@
      * @hide
      */
     @Override
-    public final int download(DownloadRequest downloadRequest, IDownloadProgressListener listener)
+    public final int registerStateCallback(
+            final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
             throws RemoteException {
-        return download(downloadRequest, new DownloadProgressListener() {
+        final int uid = Binder.getCallingUid();
+        DeathRecipient deathRecipient = new DeathRecipient() {
             @Override
-            public void progress(DownloadRequest request, FileInfo fileInfo, int
-                    currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
-                    fullDecodedSize) throws RemoteException {
-                listener.progress(request, fileInfo, currentDownloadSize, fullDownloadSize,
-                        currentDecodedSize, fullDecodedSize);
+            public void binderDied() {
+                onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+                mDownloadCallbackBinderMap.remove(callback.asBinder());
+                mDownloadCallbackDeathRecipients.remove(callback.asBinder());
             }
-        });
+        };
+        mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
+        callback.asBinder().linkToDeath(deathRecipient, 0);
+
+        DownloadStateCallback exposedCallback = new DownloadStateCallback() {
+            @Override
+            public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
+                    currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
+                    fullDecodedSize) {
+                try {
+                    callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+                            fullDownloadSize,
+                            currentDecodedSize, fullDecodedSize);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+                }
+            }
+
+            @Override
+            public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+                    @MbmsDownloadSession.DownloadStatus int state) {
+                try {
+                    callback.onStateUpdated(request, fileInfo, state);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+                }
+            }
+        };
+
+        mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+
+        return registerStateCallback(downloadRequest, exposedCallback);
     }
 
+    /**
+     * Un-registers a download state callbacks for the provided {@link DownloadRequest}.
+     *
+     * This method is called by the app when it no longer wants to request updates on the
+     * download.
+     *
+     * If the middleware is not aware of a download having been requested with the provided
+     * {@link DownloadRequest} in the past,
+     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
+     * must be returned.
+     *
+     * @param downloadRequest The {@link DownloadRequest} that was used to register the callback
+     * @param callback The callback object that
+     *                 {@link #registerStateCallback(DownloadRequest, DownloadStateCallback)}
+     *                 was called with.
+     */
+    public int unregisterStateCallback(DownloadRequest downloadRequest,
+            DownloadStateCallback callback) throws RemoteException {
+        return 0;
+    }
+
+    /**
+     * Actual AIDL implementation -- hides the callback AIDL from the API.
+     * @hide
+     */
+    @Override
+    public final int unregisterStateCallback(
+            final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
+            throws RemoteException {
+        DeathRecipient deathRecipient =
+                mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+        if (deathRecipient == null) {
+            throw new IllegalArgumentException("Unknown callback");
+        }
+
+        callback.asBinder().unlinkToDeath(deathRecipient, 0);
+
+        DownloadStateCallback exposedCallback =
+                mDownloadCallbackBinderMap.remove(callback.asBinder());
+        if (exposedCallback == null) {
+            throw new IllegalArgumentException("Unknown callback");
+        }
+
+        return unregisterStateCallback(downloadRequest, exposedCallback);
+    }
 
     /**
      * Returns a list of pending {@link DownloadRequest}s that originated from the calling
      * application, identified by its uid. A pending request is one that was issued via
-     * {@link #download(DownloadRequest, IDownloadProgressListener)} but not cancelled through
+     * {@link #download(DownloadRequest)} but not cancelled through
      * {@link #cancelDownload(DownloadRequest)}.
      * The middleware must return a non-null result synchronously or throw an exception
      * inheriting from {@link RuntimeException}.
@@ -179,13 +306,13 @@
      * Issues a request to cancel the specified download request.
      *
      * If the middleware is unable to cancel the request for whatever reason, it should return
-     * synchronously with an error. If this method returns {@link MbmsException#SUCCESS}, the app
+     * synchronously with an error. If this method returns {@link MbmsErrors#SUCCESS}, the app
      * will no longer be expecting any more file-completed intents from the middleware for this
      * {@link DownloadRequest}.
      * @param downloadRequest The request to cancel
-     * @return {@link MbmsException#SUCCESS},
-     *         {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
-     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+     * @return {@link MbmsErrors#SUCCESS},
+     *         {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
+     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
      */
     @Override
     public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException {
@@ -197,7 +324,7 @@
      *
      * If the middleware has not yet been properly initialized or if it has no records of the
      * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
-     * {@link android.telephony.MbmsDownloadManager#STATUS_UNKNOWN} must be returned.
+     * {@link MbmsDownloadSession#STATUS_UNKNOWN} must be returned.
      *
      * @param downloadRequest The download request to query.
      * @param fileInfo The particular file within the request to get information on.
@@ -217,7 +344,7 @@
      * In addition, current in-progress downloads must not be interrupted.
      *
      * If the middleware is not aware of the specified download request, return
-     * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
+     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
      *
      * @param downloadRequest The request to re-download files for.
      */
@@ -231,7 +358,7 @@
      * Signals that the app wishes to dispose of the session identified by the
      * {@code subscriptionId} argument and the caller's uid. No notification back to the
      * app is required for this operation, and the corresponding callback provided via
-     * {@link #initialize(int, IMbmsDownloadManagerCallback)} should no longer be used
+     * {@link #initialize(int, IMbmsDownloadSessionCallback)} should no longer be used
      * after this method has been called by the app.
      *
      * Any download requests issued by the app should remain in effect until the app calls
@@ -244,4 +371,12 @@
     @Override
     public void dispose(int subscriptionId) throws RemoteException {
     }
+
+    /**
+     * Indicates that the app identified by the given UID and subscription ID has died.
+     * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}.
+     * @param subscriptionId The subscription ID the app is using.
+     */
+    public void onAppCallbackDied(int uid, int subscriptionId) {
+    }
 }
diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 843e048..f8f370a 100644
--- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -18,13 +18,14 @@
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.Intent;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IMbmsStreamingSessionCallback;
 import android.telephony.mbms.IStreamingServiceCallback;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsStreamingSessionCallback;
 import android.telephony.mbms.StreamingService;
 import android.telephony.mbms.StreamingServiceCallback;
 import android.telephony.mbms.StreamingServiceInfo;
@@ -32,6 +33,8 @@
 import java.util.List;
 
 /**
+ * Base class for MBMS streaming services. The middleware should return an instance of this
+ * object from its {@link android.app.Service#onBind(Intent)} method.
  * @hide
  */
 @SystemApi
@@ -41,16 +44,16 @@
      *
      * May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which
      * will be intercepted and passed to the app as
-     * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+     * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
      *
-     * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
-     * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
-     * {@link IMbmsStreamingManagerCallback#error(int, String)}.
+     * May return any value from {@link MbmsErrors.InitializationErrors}
+     * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
+     * {@link IMbmsStreamingSessionCallback#onError(int, String)}.
      *
      * @param callback The callback to use to communicate with the app.
      * @param subscriptionId The subscription ID to use.
      */
-    public int initialize(MbmsStreamingManagerCallback callback, int subscriptionId)
+    public int initialize(MbmsStreamingSessionCallback callback, int subscriptionId)
             throws RemoteException {
         return 0;
     }
@@ -60,7 +63,7 @@
      * @hide
      */
     @Override
-    public final int initialize(final IMbmsStreamingManagerCallback callback,
+    public final int initialize(final IMbmsStreamingSessionCallback callback,
             final int subscriptionId) throws RemoteException {
         final int uid = Binder.getCallingUid();
         callback.asBinder().linkToDeath(new DeathRecipient() {
@@ -70,20 +73,20 @@
             }
         }, 0);
 
-        return initialize(new MbmsStreamingManagerCallback() {
+        return initialize(new MbmsStreamingSessionCallback() {
             @Override
-            public void onError(int errorCode, String message) {
+            public void onError(final int errorCode, final String message) {
                 try {
-                    callback.error(errorCode, message);
+                    callback.onError(errorCode, message);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
             }
 
             @Override
-            public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
+            public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services) {
                 try {
-                    callback.streamingServicesUpdated(services);
+                    callback.onStreamingServicesUpdated(services);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -92,7 +95,7 @@
             @Override
             public void onMiddlewareReady() {
                 try {
-                    callback.middlewareReady();
+                    callback.onMiddlewareReady();
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -104,7 +107,7 @@
     /**
      * Registers serviceClasses of interest with the appName/subId key.
      * Starts async fetching data on streaming services of matching classes to be reported
-     * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
+     * later via {@link IMbmsStreamingSessionCallback#onStreamingServicesUpdated(List)}
      *
      * Note that subsequent calls with the same uid and subId will replace
      * the service class list.
@@ -115,11 +118,11 @@
      * @param serviceClasses The service classes that the app wishes to get info on. The strings
      *                       may contain arbitrary data as negotiated between the app and the
      *                       carrier.
-     * @return {@link MbmsException#SUCCESS} or any of the errors in
-     * {@link android.telephony.mbms.MbmsException.GeneralErrors}
+     * @return {@link MbmsErrors#SUCCESS} or any of the errors in
+     * {@link MbmsErrors.GeneralErrors}
      */
     @Override
-    public int getStreamingServices(int subscriptionId,
+    public int requestUpdateStreamingServices(int subscriptionId,
             List<String> serviceClasses) throws RemoteException {
         return 0;
     }
@@ -127,14 +130,14 @@
     /**
      * Starts streaming on a particular service. This method may perform asynchronous work. When
      * the middleware is ready to send bits to the frontend, it should inform the app via
-     * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
+     * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
      * @param subscriptionId The subscription id to use.
      * @param serviceId The ID of the streaming service that the app has requested.
      * @param callback The callback object on which the app wishes to receive updates.
-     * @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors}
+     * @return Any error in {@link MbmsErrors.GeneralErrors}
      */
     public int startStreaming(int subscriptionId, String serviceId,
             StreamingServiceCallback callback) throws RemoteException {
@@ -147,8 +150,8 @@
      * @hide
      */
     @Override
-    public int startStreaming(int subscriptionId, String serviceId,
-            IStreamingServiceCallback callback) throws RemoteException {
+    public int startStreaming(final int subscriptionId, String serviceId,
+            final IStreamingServiceCallback callback) throws RemoteException {
         final int uid = Binder.getCallingUid();
         callback.asBinder().linkToDeath(new DeathRecipient() {
             @Override
@@ -159,19 +162,19 @@
 
         return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
             @Override
-            public void onError(int errorCode, String message) {
+            public void onError(final int errorCode, final String message) {
                 try {
-                    callback.error(errorCode, message);
+                    callback.onError(errorCode, message);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
             }
 
             @Override
-            public void onStreamStateUpdated(@StreamingService.StreamingState int state,
-                    @StreamingService.StreamingStateChangeReason int reason) {
+            public void onStreamStateUpdated(@StreamingService.StreamingState final int state,
+                    @StreamingService.StreamingStateChangeReason final int reason) {
                 try {
-                    callback.streamStateUpdated(state, reason);
+                    callback.onStreamStateUpdated(state, reason);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -180,25 +183,25 @@
             @Override
             public void onMediaDescriptionUpdated() {
                 try {
-                    callback.mediaDescriptionUpdated();
+                    callback.onMediaDescriptionUpdated();
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
             }
 
             @Override
-            public void onBroadcastSignalStrengthUpdated(int signalStrength) {
+            public void onBroadcastSignalStrengthUpdated(final int signalStrength) {
                 try {
-                    callback.broadcastSignalStrengthUpdated(signalStrength);
+                    callback.onBroadcastSignalStrengthUpdated(signalStrength);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
             }
 
             @Override
-            public void onStreamMethodUpdated(int methodType) {
+            public void onStreamMethodUpdated(final int methodType) {
                 try {
-                    callback.streamMethodUpdated(methodType);
+                    callback.onStreamMethodUpdated(methodType);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -225,7 +228,11 @@
     /**
      * Stop streaming the stream identified by {@code serviceId}. Notification of the resulting
      * stream state change should be reported to the app via
-     * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
+     * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
+     *
+     * In addition, the callback provided via
+     * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
+     * used after this method has called by the app.
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
@@ -238,27 +245,10 @@
     }
 
     /**
-     * Dispose of the stream identified by {@code serviceId} for the app identified by the
-     * {@code appName} and {@code subscriptionId} arguments along with the caller's uid.
-     * No notification back to the app is required for this operation, and the callback provided via
-     * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
-     * used after this method has called by the app.
-     *
-     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
-     *
-     * @param subscriptionId The subscription id to use.
-     * @param serviceId The ID of the streaming service that the app wishes to dispose of.
-     */
-    @Override
-    public void disposeStream(int subscriptionId, String serviceId)
-            throws RemoteException {
-    }
-
-    /**
      * Signals that the app wishes to dispose of the session identified by the
      * {@code subscriptionId} argument and the caller's uid. No notification back to the
      * app is required for this operation, and the corresponding callback provided via
-     * {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used
+     * {@link #initialize(IMbmsStreamingSessionCallback, int)} should no longer be used
      * after this method has been called by the app.
      *
      * May throw an {@link IllegalStateException}
diff --git a/android/telephony/mbms/vendor/VendorIntents.java b/android/telephony/mbms/vendor/VendorUtils.java
similarity index 76%
rename from android/telephony/mbms/vendor/VendorIntents.java
rename to android/telephony/mbms/vendor/VendorUtils.java
index 367c995..8fb27b2 100644
--- a/android/telephony/mbms/vendor/VendorIntents.java
+++ b/android/telephony/mbms/vendor/VendorUtils.java
@@ -16,29 +16,32 @@
 
 package android.telephony.mbms.vendor;
 
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
-import android.telephony.mbms.DownloadRequest;
+import android.telephony.MbmsDownloadSession;
 import android.telephony.mbms.MbmsDownloadReceiver;
 
 import java.io.File;
 import java.util.List;
 
 /**
+ * Contains constants and utility methods for MBMS Download middleware apps to communicate with
+ * frontend apps.
  * @hide
- * TODO: future systemapi
  */
-public class VendorIntents {
+@SystemApi
+public class VendorUtils {
 
     /**
      * The MBMS middleware should send this when a download of single file has completed or
      * failed. Mandatory extras are
-     * {@link android.telephony.MbmsDownloadManager#EXTRA_RESULT}
-     * {@link android.telephony.MbmsDownloadManager#EXTRA_FILE_INFO}
-     * {@link #EXTRA_REQUEST}
+     * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+     * {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
+     * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
      * {@link #EXTRA_TEMP_LIST}
      * {@link #EXTRA_FINAL_URI}
      */
@@ -49,7 +52,7 @@
      * The MBMS middleware should send this when it wishes to request {@code content://} URIs to
      * serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory
      * extras are
-     * {@link #EXTRA_REQUEST}
+     * {@link #EXTRA_SERVICE_ID}
      *
      * Optional extras are
      * {@link #EXTRA_FD_COUNT} (0 if not present)
@@ -118,48 +121,35 @@
             "android.telephony.mbms.extra.TEMP_FILES_IN_USE";
 
     /**
-     * Extra containing the {@link DownloadRequest} for which the download result or file
-     * descriptor request is for. Must not be null.
-     */
-    public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
-
-    /**
      * Extra containing a single {@link Uri} indicating the path to the temp file in which the
      * decoded downloaded file resides. Must not be null.
      */
     public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI";
 
     /**
-     * Extra containing an instance of {@link android.telephony.mbms.ServiceInfo}, used by
+     * Extra containing a String representing a service ID, used by
      * file-descriptor requests and cleanup requests to specify which service they want to
      * request temp files or clean up temp files for, respectively.
      */
-    public static final String EXTRA_SERVICE_INFO =
-            "android.telephony.mbms.extra.SERVICE_INFO";
+    public static final String EXTRA_SERVICE_ID =
+            "android.telephony.mbms.extra.SERVICE_ID";
 
     /**
      * Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that
      * the various intents from the middleware should be targeted towards.
-     * @param uid The uid of the frontend app.
+     * @param packageName The package name of the app.
      * @return The component name of the receiver that the middleware should send its intents to,
      * or null if the app didn't declare it in the manifest.
      */
-    public static ComponentName getAppReceiverFromUid(Context context, int uid) {
-        String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
-        if (packageNames == null) {
-            return null;
-        }
-
-        for (String packageName : packageNames) {
-            ComponentName candidate = new ComponentName(packageName,
-                    MbmsDownloadReceiver.class.getCanonicalName());
-            Intent queryIntent = new Intent();
-            queryIntent.setComponent(candidate);
-            List<ResolveInfo> receivers =
-                    context.getPackageManager().queryBroadcastReceivers(queryIntent, 0);
-            if (receivers != null && receivers.size() > 0) {
-                return candidate;
-            }
+    public static ComponentName getAppReceiverFromPackageName(Context context, String packageName) {
+        ComponentName candidate = new ComponentName(packageName,
+                MbmsDownloadReceiver.class.getCanonicalName());
+        Intent queryIntent = new Intent();
+        queryIntent.setComponent(candidate);
+        List<ResolveInfo> receivers =
+                context.getPackageManager().queryBroadcastReceivers(queryIntent, 0);
+        if (receivers != null && receivers.size() > 0) {
+            return candidate;
         }
         return null;
     }
diff --git a/android/text/InputType.java b/android/text/InputType.java
index 8967b70..f38482e 100644
--- a/android/text/InputType.java
+++ b/android/text/InputType.java
@@ -182,9 +182,9 @@
      * want the IME to correct typos.
      * Note the contrast with {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} and
      * {@link #TYPE_TEXT_FLAG_AUTO_COMPLETE}:
-     * {@code TYPE_TEXT_FLAG_NO_SUGGESTIONS} means the IME should never
+     * {@code TYPE_TEXT_FLAG_NO_SUGGESTIONS} means the IME does not need to
      * show an interface to display suggestions. Most IMEs will also take this to
-     * mean they should not try to auto-correct what the user is typing.
+     * mean they do not need to try to auto-correct what the user is typing.
      */
     public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000;
 
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 4015488..5208606 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,6 +20,10 @@
 import java.util.Map;
 
 /**
+ * BEGIN LAYOUTLIB CHANGE
+ * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
+ * END LAYOUTLIB CHANGE
+ *
  * A cache that holds strong references to a limited number of values. Each time
  * a value is accessed, it is moved to the head of a queue. When a value is
  * added to a full cache, the value at the end of that queue is evicted and may
@@ -87,8 +91,9 @@
 
     /**
      * Sets the size of the cache.
-     *
      * @param maxSize The new maximum size.
+     *
+     * @hide
      */
     public void resize(int maxSize) {
         if (maxSize <= 0) {
@@ -185,13 +190,10 @@
     }
 
     /**
-     * Remove the eldest entries until the total of remaining entries is at or
-     * below the requested size.
-     *
      * @param maxSize the maximum size of the cache before returning. May be -1
-     *            to evict even 0-sized elements.
+     *     to evict even 0-sized elements.
      */
-    public void trimToSize(int maxSize) {
+    private void trimToSize(int maxSize) {
         while (true) {
             K key;
             V value;
@@ -205,7 +207,16 @@
                     break;
                 }
 
-                Map.Entry<K, V> toEvict = map.eldest();
+                // BEGIN LAYOUTLIB CHANGE
+                // get the last item in the linked list.
+                // This is not efficient, the goal here is to minimize the changes
+                // compared to the platform version.
+                Map.Entry<K, V> toEvict = null;
+                for (Map.Entry<K, V> entry : map.entrySet()) {
+                    toEvict = entry;
+                }
+                // END LAYOUTLIB CHANGE
+
                 if (toEvict == null) {
                     break;
                 }
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index cac27af..ebb2af4 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,1209 +16,115 @@
 
 package android.view;
 
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
+import com.android.layoutlib.bridge.MockView;
 
 import android.content.Context;
-import android.content.res.CompatibilityInfo.Translator;
-import android.content.res.Configuration;
 import android.graphics.Canvas;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.SystemClock;
 import android.util.AttributeSet;
-import android.util.Log;
-
-import com.android.internal.view.SurfaceCallbackHelper;
-
-import java.util.ArrayList;
-import java.util.concurrent.locks.ReentrantLock;
 
 /**
- * Provides a dedicated drawing surface embedded inside of a view hierarchy.
- * You can control the format of this surface and, if you like, its size; the
- * SurfaceView takes care of placing the surface at the correct location on the
- * screen
+ * Mock version of the SurfaceView.
+ * Only non override public methods from the real SurfaceView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
  *
- * <p>The surface is Z ordered so that it is behind the window holding its
- * SurfaceView; the SurfaceView punches a hole in its window to allow its
- * surface to be displayed. The view hierarchy will take care of correctly
- * compositing with the Surface any siblings of the SurfaceView that would
- * normally appear on top of it. This can be used to place overlays such as
- * buttons on top of the Surface, though note however that it can have an
- * impact on performance since a full alpha-blended composite will be performed
- * each time the Surface changes.
+ * TODO: generate automatically.
  *
- * <p> The transparent region that makes the surface visible is based on the
- * layout positions in the view hierarchy. If the post-layout transform
- * properties are used to draw a sibling view on top of the SurfaceView, the
- * view may not be properly composited with the surface.
- *
- * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
- * which can be retrieved by calling {@link #getHolder}.
- *
- * <p>The Surface will be created for you while the SurfaceView's window is
- * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
- * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
- * Surface is created and destroyed as the window is shown and hidden.
- *
- * <p>One of the purposes of this class is to provide a surface in which a
- * secondary thread can render into the screen. If you are going to use it
- * this way, you need to be aware of some threading semantics:
- *
- * <ul>
- * <li> All SurfaceView and
- * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
- * from the thread running the SurfaceView's window (typically the main thread
- * of the application). They thus need to correctly synchronize with any
- * state that is also touched by the drawing thread.
- * <li> You must ensure that the drawing thread only touches the underlying
- * Surface while it is valid -- between
- * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
- * and
- * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
- * </ul>
- *
- * <p class="note"><strong>Note:</strong> Starting in platform version
- * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
- * updated synchronously with other View rendering. This means that translating
- * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
- * artifacts may occur on previous versions of the platform when its window is
- * positioned asynchronously.</p>
  */
-public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
-    private static final String TAG = "SurfaceView";
-    private static final boolean DEBUG = false;
-
-    final ArrayList<SurfaceHolder.Callback> mCallbacks
-            = new ArrayList<SurfaceHolder.Callback>();
-
-    final int[] mLocation = new int[2];
-
-    final ReentrantLock mSurfaceLock = new ReentrantLock();
-    final Surface mSurface = new Surface();       // Current surface in use
-    boolean mDrawingStopped = true;
-    // We use this to track if the application has produced a frame
-    // in to the Surface. Up until that point, we should be careful not to punch
-    // holes.
-    boolean mDrawFinished = false;
-
-    final Rect mScreenRect = new Rect();
-    SurfaceSession mSurfaceSession;
-
-    SurfaceControl mSurfaceControl;
-    // In the case of format changes we switch out the surface in-place
-    // we need to preserve the old one until the new one has drawn.
-    SurfaceControl mDeferredDestroySurfaceControl;
-    final Rect mTmpRect = new Rect();
-    final Configuration mConfiguration = new Configuration();
-
-    int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
-
-    boolean mIsCreating = false;
-    private volatile boolean mRtHandlingPositionUpdates = false;
-
-    private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
-            = new ViewTreeObserver.OnScrollChangedListener() {
-                    @Override
-                    public void onScrollChanged() {
-                        updateSurface();
-                    }
-            };
-
-    private final ViewTreeObserver.OnPreDrawListener mDrawListener =
-            new ViewTreeObserver.OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    // reposition ourselves where the surface is
-                    mHaveFrame = getWidth() > 0 && getHeight() > 0;
-                    updateSurface();
-                    return true;
-                }
-            };
-
-    boolean mRequestedVisible = false;
-    boolean mWindowVisibility = false;
-    boolean mLastWindowVisibility = false;
-    boolean mViewVisibility = false;
-    boolean mWindowStopped = false;
-
-    int mRequestedWidth = -1;
-    int mRequestedHeight = -1;
-    /* Set SurfaceView's format to 565 by default to maintain backward
-     * compatibility with applications assuming this format.
-     */
-    int mRequestedFormat = PixelFormat.RGB_565;
-
-    boolean mHaveFrame = false;
-    boolean mSurfaceCreated = false;
-    long mLastLockTime = 0;
-
-    boolean mVisible = false;
-    int mWindowSpaceLeft = -1;
-    int mWindowSpaceTop = -1;
-    int mSurfaceWidth = -1;
-    int mSurfaceHeight = -1;
-    int mFormat = -1;
-    final Rect mSurfaceFrame = new Rect();
-    int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
-    private Translator mTranslator;
-
-    private boolean mGlobalListenersAdded;
-    private boolean mAttachedToWindow;
-
-    private int mSurfaceFlags = SurfaceControl.HIDDEN;
-
-    private int mPendingReportDraws;
+public class SurfaceView extends MockView {
 
     public SurfaceView(Context context) {
         this(context, null);
     }
 
     public SurfaceView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
+        this(context, attrs , 0);
     }
 
-    public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
+    public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
     }
 
     public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mRenderNode.requestPositionUpdates(this);
-
-        setWillNotDraw(true);
     }
 
-    /**
-     * Return the SurfaceHolder providing access and control over this
-     * SurfaceView's underlying surface.
-     *
-     * @return SurfaceHolder The holder of the surface.
-     */
+    public boolean gatherTransparentRegion(Region region) {
+      return false;
+    }
+
+    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+    }
+
+    public void setZOrderOnTop(boolean onTop) {
+    }
+
+    public void setSecure(boolean isSecure) {
+    }
+
     public SurfaceHolder getHolder() {
         return mSurfaceHolder;
     }
 
-    private void updateRequestedVisibility() {
-        mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
-    }
-
-    /** @hide */
-    @Override
-    public void windowStopped(boolean stopped) {
-        mWindowStopped = stopped;
-        updateRequestedVisibility();
-        updateSurface();
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        getViewRootImpl().addWindowStoppedCallback(this);
-        mWindowStopped = false;
-
-        mViewVisibility = getVisibility() == VISIBLE;
-        updateRequestedVisibility();
-
-        mAttachedToWindow = true;
-        if (!mGlobalListenersAdded) {
-            ViewTreeObserver observer = getViewTreeObserver();
-            observer.addOnScrollChangedListener(mScrollChangedListener);
-            observer.addOnPreDrawListener(mDrawListener);
-            mGlobalListenersAdded = true;
-        }
-    }
-
-    @Override
-    protected void onWindowVisibilityChanged(int visibility) {
-        super.onWindowVisibilityChanged(visibility);
-        mWindowVisibility = visibility == VISIBLE;
-        updateRequestedVisibility();
-        updateSurface();
-    }
-
-    @Override
-    public void setVisibility(int visibility) {
-        super.setVisibility(visibility);
-        mViewVisibility = visibility == VISIBLE;
-        boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
-        if (newRequestedVisible != mRequestedVisible) {
-            // our base class (View) invalidates the layout only when
-            // we go from/to the GONE state. However, SurfaceView needs
-            // to request a re-layout when the visibility changes at all.
-            // This is needed because the transparent region is computed
-            // as part of the layout phase, and it changes (obviously) when
-            // the visibility changes.
-            requestLayout();
-        }
-        mRequestedVisible = newRequestedVisible;
-        updateSurface();
-    }
-
-    private void performDrawFinished() {
-        if (mPendingReportDraws > 0) {
-            mDrawFinished = true;
-            if (mAttachedToWindow) {
-                mParent.requestTransparentRegion(SurfaceView.this);
-                
-                notifyDrawFinished();
-                invalidate();
-            }
-        } else {
-            Log.e(TAG, System.identityHashCode(this) + "finished drawing"
-                    + " but no pending report draw (extra call"
-                    + " to draw completion runnable?)");
-        }
-    }
-
-    void notifyDrawFinished() {
-        ViewRootImpl viewRoot = getViewRootImpl();
-        if (viewRoot != null) {
-            viewRoot.pendingDrawFinished();
-        }
-        mPendingReportDraws--;
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        ViewRootImpl viewRoot = getViewRootImpl();
-        // It's possible to create a SurfaceView using the default constructor and never
-        // attach it to a view hierarchy, this is a common use case when dealing with
-        // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
-        // the lifecycle. Instead of attaching it to a view, he/she can just pass
-        // the SurfaceHolder forward, most live wallpapers do it.
-        if (viewRoot != null) {
-            viewRoot.removeWindowStoppedCallback(this);
-        }
-
-        mAttachedToWindow = false;
-        if (mGlobalListenersAdded) {
-            ViewTreeObserver observer = getViewTreeObserver();
-            observer.removeOnScrollChangedListener(mScrollChangedListener);
-            observer.removeOnPreDrawListener(mDrawListener);
-            mGlobalListenersAdded = false;
-        }
-
-        while (mPendingReportDraws > 0) {
-            notifyDrawFinished();
-        }
-
-        mRequestedVisible = false;
-
-        updateSurface();
-        if (mSurfaceControl != null) {
-            mSurfaceControl.destroy();
-        }
-        mSurfaceControl = null;
-
-        mHaveFrame = false;
-
-        super.onDetachedFromWindow();
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int width = mRequestedWidth >= 0
-                ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
-                : getDefaultSize(0, widthMeasureSpec);
-        int height = mRequestedHeight >= 0
-                ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
-                : getDefaultSize(0, heightMeasureSpec);
-        setMeasuredDimension(width, height);
-    }
-
-    /** @hide */
-    @Override
-    protected boolean setFrame(int left, int top, int right, int bottom) {
-        boolean result = super.setFrame(left, top, right, bottom);
-        updateSurface();
-        return result;
-    }
-
-    @Override
-    public boolean gatherTransparentRegion(Region region) {
-        if (isAboveParent() || !mDrawFinished) {
-            return super.gatherTransparentRegion(region);
-        }
-
-        boolean opaque = true;
-        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
-            // this view draws, remove it from the transparent region
-            opaque = super.gatherTransparentRegion(region);
-        } else if (region != null) {
-            int w = getWidth();
-            int h = getHeight();
-            if (w>0 && h>0) {
-                getLocationInWindow(mLocation);
-                // otherwise, punch a hole in the whole hierarchy
-                int l = mLocation[0];
-                int t = mLocation[1];
-                region.op(l, t, l+w, t+h, Region.Op.UNION);
-            }
-        }
-        if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
-            opaque = false;
-        }
-        return opaque;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (mDrawFinished && !isAboveParent()) {
-            // draw() is not called when SKIP_DRAW is set
-            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
-                // punch a whole in the view-hierarchy below us
-                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-            }
-        }
-        super.draw(canvas);
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        if (mDrawFinished && !isAboveParent()) {
-            // draw() is not called when SKIP_DRAW is set
-            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
-                // punch a whole in the view-hierarchy below us
-                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-            }
-        }
-        super.dispatchDraw(canvas);
-    }
-
-    /**
-     * Control whether the surface view's surface is placed on top of another
-     * regular surface view in the window (but still behind the window itself).
-     * This is typically used to place overlays on top of an underlying media
-     * surface view.
-     *
-     * <p>Note that this must be set before the surface view's containing
-     * window is attached to the window manager.
-     *
-     * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
-     */
-    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
-        mSubLayer = isMediaOverlay
-            ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
-    }
-
-    /**
-     * Control whether the surface view's surface is placed on top of its
-     * window.  Normally it is placed behind the window, to allow it to
-     * (for the most part) appear to composite with the views in the
-     * hierarchy.  By setting this, you cause it to be placed above the
-     * window.  This means that none of the contents of the window this
-     * SurfaceView is in will be visible on top of its surface.
-     *
-     * <p>Note that this must be set before the surface view's containing
-     * window is attached to the window manager.
-     *
-     * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
-     */
-    public void setZOrderOnTop(boolean onTop) {
-        if (onTop) {
-            mSubLayer = APPLICATION_PANEL_SUBLAYER;
-        } else {
-            mSubLayer = APPLICATION_MEDIA_SUBLAYER;
-        }
-    }
-
-    /**
-     * Control whether the surface view's content should be treated as secure,
-     * preventing it from appearing in screenshots or from being viewed on
-     * non-secure displays.
-     *
-     * <p>Note that this must be set before the surface view's containing
-     * window is attached to the window manager.
-     *
-     * <p>See {@link android.view.Display#FLAG_SECURE} for details.
-     *
-     * @param isSecure True if the surface view is secure.
-     */
-    public void setSecure(boolean isSecure) {
-        if (isSecure) {
-            mSurfaceFlags |= SurfaceControl.SECURE;
-        } else {
-            mSurfaceFlags &= ~SurfaceControl.SECURE;
-        }
-    }
-
-    private void updateOpaqueFlag() {
-        if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
-            mSurfaceFlags |= SurfaceControl.OPAQUE;
-        } else {
-            mSurfaceFlags &= ~SurfaceControl.OPAQUE;
-        }
-    }
-
-    private Rect getParentSurfaceInsets() {
-        final ViewRootImpl root = getViewRootImpl();
-        if (root == null) {
-            return null;
-        } else {
-            return root.mWindowAttributes.surfaceInsets;
-        }
-    }
-
-    /** @hide */
-    protected void updateSurface() {
-        if (!mHaveFrame) {
-            return;
-        }
-        ViewRootImpl viewRoot = getViewRootImpl();
-        if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
-            return;
-        }
-
-        mTranslator = viewRoot.mTranslator;
-        if (mTranslator != null) {
-            mSurface.setCompatibilityTranslator(mTranslator);
-        }
-
-        int myWidth = mRequestedWidth;
-        if (myWidth <= 0) myWidth = getWidth();
-        int myHeight = mRequestedHeight;
-        if (myHeight <= 0) myHeight = getHeight();
-
-        final boolean formatChanged = mFormat != mRequestedFormat;
-        final boolean visibleChanged = mVisible != mRequestedVisible;
-        final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
-                && mRequestedVisible;
-        final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
-        final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
-        boolean redrawNeeded = false;
-
-        if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
-            getLocationInWindow(mLocation);
-
-            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                    + "Changes: creating=" + creating
-                    + " format=" + formatChanged + " size=" + sizeChanged
-                    + " visible=" + visibleChanged
-                    + " left=" + (mWindowSpaceLeft != mLocation[0])
-                    + " top=" + (mWindowSpaceTop != mLocation[1]));
-
-            try {
-                final boolean visible = mVisible = mRequestedVisible;
-                mWindowSpaceLeft = mLocation[0];
-                mWindowSpaceTop = mLocation[1];
-                mSurfaceWidth = myWidth;
-                mSurfaceHeight = myHeight;
-                mFormat = mRequestedFormat;
-                mLastWindowVisibility = mWindowVisibility;
-
-                mScreenRect.left = mWindowSpaceLeft;
-                mScreenRect.top = mWindowSpaceTop;
-                mScreenRect.right = mWindowSpaceLeft + getWidth();
-                mScreenRect.bottom = mWindowSpaceTop + getHeight();
-                if (mTranslator != null) {
-                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
-                }
-
-                final Rect surfaceInsets = getParentSurfaceInsets();
-                mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-
-                if (creating) {
-                    mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
-                    mDeferredDestroySurfaceControl = mSurfaceControl;
-
-                    updateOpaqueFlag();
-                    mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
-                            "SurfaceView - " + viewRoot.getTitle().toString(),
-                            mSurfaceWidth, mSurfaceHeight, mFormat,
-                            mSurfaceFlags);
-                } else if (mSurfaceControl == null) {
-                    return;
-                }
-
-                boolean realSizeChanged = false;
-
-                mSurfaceLock.lock();
-                try {
-                    mDrawingStopped = !visible;
-
-                    if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                            + "Cur surface: " + mSurface);
-
-                    SurfaceControl.openTransaction();
-                    try {
-                        mSurfaceControl.setLayer(mSubLayer);
-                        if (mViewVisibility) {
-                            mSurfaceControl.show();
-                        } else {
-                            mSurfaceControl.hide();
-                        }
-
-                        // While creating the surface, we will set it's initial
-                        // geometry. Outside of that though, we should generally
-                        // leave it to the RenderThread.
-                        //
-                        // There is one more case when the buffer size changes we aren't yet
-                        // prepared to sync (as even following the transaction applying
-                        // we still need to latch a buffer).
-                        // b/28866173
-                        if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
-                            mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
-                            mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
-                                    0.0f, 0.0f,
-                                    mScreenRect.height() / (float) mSurfaceHeight);
-                        }
-                        if (sizeChanged) {
-                            mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
-                        }
-                    } finally {
-                        SurfaceControl.closeTransaction();
-                    }
-
-                    if (sizeChanged || creating) {
-                        redrawNeeded = true;
-                    }
-
-                    mSurfaceFrame.left = 0;
-                    mSurfaceFrame.top = 0;
-                    if (mTranslator == null) {
-                        mSurfaceFrame.right = mSurfaceWidth;
-                        mSurfaceFrame.bottom = mSurfaceHeight;
-                    } else {
-                        float appInvertedScale = mTranslator.applicationInvertedScale;
-                        mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
-                        mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
-                    }
-
-                    final int surfaceWidth = mSurfaceFrame.right;
-                    final int surfaceHeight = mSurfaceFrame.bottom;
-                    realSizeChanged = mLastSurfaceWidth != surfaceWidth
-                            || mLastSurfaceHeight != surfaceHeight;
-                    mLastSurfaceWidth = surfaceWidth;
-                    mLastSurfaceHeight = surfaceHeight;
-                } finally {
-                    mSurfaceLock.unlock();
-                }
-
-                try {
-                    redrawNeeded |= visible && !mDrawFinished;
-
-                    SurfaceHolder.Callback callbacks[] = null;
-
-                    final boolean surfaceChanged = creating;
-                    if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
-                        mSurfaceCreated = false;
-                        if (mSurface.isValid()) {
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "visibleChanged -- surfaceDestroyed");
-                            callbacks = getSurfaceCallbacks();
-                            for (SurfaceHolder.Callback c : callbacks) {
-                                c.surfaceDestroyed(mSurfaceHolder);
-                            }
-                            // Since Android N the same surface may be reused and given to us
-                            // again by the system server at a later point. However
-                            // as we didn't do this in previous releases, clients weren't
-                            // necessarily required to clean up properly in
-                            // surfaceDestroyed. This leads to problems for example when
-                            // clients don't destroy their EGL context, and try
-                            // and create a new one on the same surface following reuse.
-                            // Since there is no valid use of the surface in-between
-                            // surfaceDestroyed and surfaceCreated, we force a disconnect,
-                            // so the next connect will always work if we end up reusing
-                            // the surface.
-                            if (mSurface.isValid()) {
-                                mSurface.forceScopedDisconnect();
-                            }
-                        }
-                    }
-
-                    if (creating) {
-                        mSurface.copyFrom(mSurfaceControl);
-                    }
-
-                    if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
-                            < Build.VERSION_CODES.O) {
-                        // Some legacy applications use the underlying native {@link Surface} object
-                        // as a key to whether anything has changed. In these cases, updates to the
-                        // existing {@link Surface} will be ignored when the size changes.
-                        // Therefore, we must explicitly recreate the {@link Surface} in these
-                        // cases.
-                        mSurface.createFrom(mSurfaceControl);
-                    }
-
-                    if (visible && mSurface.isValid()) {
-                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
-                            mSurfaceCreated = true;
-                            mIsCreating = true;
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "visibleChanged -- surfaceCreated");
-                            if (callbacks == null) {
-                                callbacks = getSurfaceCallbacks();
-                            }
-                            for (SurfaceHolder.Callback c : callbacks) {
-                                c.surfaceCreated(mSurfaceHolder);
-                            }
-                        }
-                        if (creating || formatChanged || sizeChanged
-                                || visibleChanged || realSizeChanged) {
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "surfaceChanged -- format=" + mFormat
-                                    + " w=" + myWidth + " h=" + myHeight);
-                            if (callbacks == null) {
-                                callbacks = getSurfaceCallbacks();
-                            }
-                            for (SurfaceHolder.Callback c : callbacks) {
-                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
-                            }
-                        }
-                        if (redrawNeeded) {
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "surfaceRedrawNeeded");
-                            if (callbacks == null) {
-                                callbacks = getSurfaceCallbacks();
-                            }
-
-                            mPendingReportDraws++;
-                            viewRoot.drawPending();
-                            SurfaceCallbackHelper sch =
-                                    new SurfaceCallbackHelper(this::onDrawFinished);
-                            sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
-                        }
-                    }
-                } finally {
-                    mIsCreating = false;
-                    if (mSurfaceControl != null && !mSurfaceCreated) {
-                        mSurface.release();
-                        // If we are not in the stopped state, then the destruction of the Surface
-                        // represents a visual change we need to display, and we should go ahead
-                        // and destroy the SurfaceControl. However if we are in the stopped state,
-                        // we can just leave the Surface around so it can be a part of animations,
-                        // and we let the life-time be tied to the parent surface.
-                        if (!mWindowStopped) {
-                            mSurfaceControl.destroy();
-                            mSurfaceControl = null;
-                        }
-                    }
-                }
-            } catch (Exception ex) {
-                Log.e(TAG, "Exception configuring surface", ex);
-            }
-            if (DEBUG) Log.v(
-                TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
-                + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
-                + ", frame=" + mSurfaceFrame);
-        } else {
-            // Calculate the window position in case RT loses the window
-            // and we need to fallback to a UI-thread driven position update
-            getLocationInSurface(mLocation);
-            final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
-                    || mWindowSpaceTop != mLocation[1];
-            final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
-                    || getHeight() != mScreenRect.height();
-            if (positionChanged || layoutSizeChanged) { // Only the position has changed
-                mWindowSpaceLeft = mLocation[0];
-                mWindowSpaceTop = mLocation[1];
-                // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
-                // in view local space.
-                mLocation[0] = getWidth();
-                mLocation[1] = getHeight();
-
-                mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
-                        mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
-
-                if (mTranslator != null) {
-                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
-                }
-
-                if (mSurfaceControl == null) {
-                    return;
-                }
-
-                if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
-                    try {
-                        if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
-                                "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
-                                mScreenRect.left, mScreenRect.top,
-                                mScreenRect.right, mScreenRect.bottom));
-                        setParentSpaceRectangle(mScreenRect, -1);
-                    } catch (Exception ex) {
-                        Log.e(TAG, "Exception configuring surface", ex);
-                    }
-                }
-            }
-        }
-    }
-
-    private void onDrawFinished() {
-        if (DEBUG) {
-            Log.i(TAG, System.identityHashCode(this) + " "
-                    + "finishedDrawing");
-        }
-
-        if (mDeferredDestroySurfaceControl != null) {
-            mDeferredDestroySurfaceControl.destroy();
-            mDeferredDestroySurfaceControl = null;
-        }
-
-        runOnUiThread(() -> {
-            performDrawFinished();
-        });
-    }
-
-    private void setParentSpaceRectangle(Rect position, long frameNumber) {
-        ViewRootImpl viewRoot = getViewRootImpl();
-
-        SurfaceControl.openTransaction();
-        try {
-            if (frameNumber > 0) {
-                mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
-            }
-            mSurfaceControl.setPosition(position.left, position.top);
-            mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
-                    0.0f, 0.0f,
-                    position.height() / (float) mSurfaceHeight);
-        } finally {
-            SurfaceControl.closeTransaction();
-        }
-    }
-
-    private Rect mRTLastReportedPosition = new Rect();
-
-    /**
-     * Called by native by a Rendering Worker thread to update the window position
-     * @hide
-     */
-    public final void updateSurfacePosition_renderWorker(long frameNumber,
-            int left, int top, int right, int bottom) {
-        if (mSurfaceControl == null) {
-            return;
-        }
-
-        // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
-        // its 2nd frame if RenderThread is running slowly could potentially see
-        // this as false, enter the branch, get pre-empted, then this comes along
-        // and reports a new position, then the UI thread resumes and reports
-        // its position. This could therefore be de-sync'd in that interval, but
-        // the synchronization would violate the rule that RT must never block
-        // on the UI thread which would open up potential deadlocks. The risk of
-        // a single-frame desync is therefore preferable for now.
-        mRtHandlingPositionUpdates = true;
-        if (mRTLastReportedPosition.left == left
-                && mRTLastReportedPosition.top == top
-                && mRTLastReportedPosition.right == right
-                && mRTLastReportedPosition.bottom == bottom) {
-            return;
-        }
-        try {
-            if (DEBUG) {
-                Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
-                        "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
-                        frameNumber, left, top, right, bottom));
-            }
-            mRTLastReportedPosition.set(left, top, right, bottom);
-            setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
-            // Now overwrite mRTLastReportedPosition with our values
-        } catch (Exception ex) {
-            Log.e(TAG, "Exception from repositionChild", ex);
-        }
-    }
-
-    /**
-     * Called by native on RenderThread to notify that the view is no longer in the
-     * draw tree. UI thread is blocked at this point.
-     * @hide
-     */
-    public final void surfacePositionLost_uiRtSync(long frameNumber) {
-        if (DEBUG) {
-            Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
-                    System.identityHashCode(this), frameNumber));
-        }
-        mRTLastReportedPosition.setEmpty();
-
-        if (mSurfaceControl == null) {
-            return;
-        }
-        if (mRtHandlingPositionUpdates) {
-            mRtHandlingPositionUpdates = false;
-            // This callback will happen while the UI thread is blocked, so we can
-            // safely access other member variables at this time.
-            // So do what the UI thread would have done if RT wasn't handling position
-            // updates.
-            if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
-                try {
-                    if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
-                            "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
-                            mScreenRect.left, mScreenRect.top,
-                            mScreenRect.right, mScreenRect.bottom));
-                    setParentSpaceRectangle(mScreenRect, frameNumber);
-                } catch (Exception ex) {
-                    Log.e(TAG, "Exception configuring surface", ex);
-                }
-            }
-        }
-    }
-
-    private SurfaceHolder.Callback[] getSurfaceCallbacks() {
-        SurfaceHolder.Callback callbacks[];
-        synchronized (mCallbacks) {
-            callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
-            mCallbacks.toArray(callbacks);
-        }
-        return callbacks;
-    }
-
-    /**
-     * This method still exists only for compatibility reasons because some applications have relied
-     * on this method via reflection. See Issue 36345857 for details.
-     *
-     * @deprecated No platform code is using this method anymore.
-     * @hide
-     */
-    @Deprecated
-    public void setWindowType(int type) {
-        if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
-            throw new UnsupportedOperationException(
-                    "SurfaceView#setWindowType() has never been a public API.");
-        }
-
-        if (type == TYPE_APPLICATION_PANEL) {
-            Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
-                    + "just to make the SurfaceView to be placed on top of its window, you must "
-                    + "call setZOrderOnTop(true) instead.", new Throwable());
-            setZOrderOnTop(true);
-            return;
-        }
-        Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
-                + "type=" + type, new Throwable());
-    }
-
-    private void runOnUiThread(Runnable runnable) {
-        Handler handler = getHandler();
-        if (handler != null && handler.getLooper() != Looper.myLooper()) {
-            handler.post(runnable);
-        } else {
-            runnable.run();
-        }
-    }
-
-    /**
-     * Check to see if the surface has fixed size dimensions or if the surface's
-     * dimensions are dimensions are dependent on its current layout.
-     *
-     * @return true if the surface has dimensions that are fixed in size
-     * @hide
-     */
-    public boolean isFixedSize() {
-        return (mRequestedWidth != -1 || mRequestedHeight != -1);
-    }
-
-    private boolean isAboveParent() {
-        return mSubLayer >= 0;
-    }
-
-    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
-        private static final String LOG_TAG = "SurfaceHolder";
+    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
 
         @Override
         public boolean isCreating() {
-            return mIsCreating;
+            return false;
         }
 
         @Override
         public void addCallback(Callback callback) {
-            synchronized (mCallbacks) {
-                // This is a linear search, but in practice we'll
-                // have only a couple callbacks, so it doesn't matter.
-                if (mCallbacks.contains(callback) == false) {
-                    mCallbacks.add(callback);
-                }
-            }
         }
 
         @Override
         public void removeCallback(Callback callback) {
-            synchronized (mCallbacks) {
-                mCallbacks.remove(callback);
-            }
         }
 
         @Override
         public void setFixedSize(int width, int height) {
-            if (mRequestedWidth != width || mRequestedHeight != height) {
-                mRequestedWidth = width;
-                mRequestedHeight = height;
-                requestLayout();
-            }
         }
 
         @Override
         public void setSizeFromLayout() {
-            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
-                mRequestedWidth = mRequestedHeight = -1;
-                requestLayout();
-            }
         }
 
         @Override
         public void setFormat(int format) {
-            // for backward compatibility reason, OPAQUE always
-            // means 565 for SurfaceView
-            if (format == PixelFormat.OPAQUE)
-                format = PixelFormat.RGB_565;
-
-            mRequestedFormat = format;
-            if (mSurfaceControl != null) {
-                updateSurface();
-            }
         }
 
-        /**
-         * @deprecated setType is now ignored.
-         */
         @Override
-        @Deprecated
-        public void setType(int type) { }
+        public void setType(int type) {
+        }
 
         @Override
         public void setKeepScreenOn(boolean screenOn) {
-            runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
         }
 
-        /**
-         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
-         *
-         * After drawing into the provided {@link Canvas}, the caller must
-         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
-         *
-         * The caller must redraw the entire surface.
-         * @return A canvas for drawing into the surface.
-         */
         @Override
         public Canvas lockCanvas() {
-            return internalLockCanvas(null, false);
-        }
-
-        /**
-         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
-         *
-         * After drawing into the provided {@link Canvas}, the caller must
-         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
-         *
-         * @param inOutDirty A rectangle that represents the dirty region that the caller wants
-         * to redraw.  This function may choose to expand the dirty rectangle if for example
-         * the surface has been resized or if the previous contents of the surface were
-         * not available.  The caller must redraw the entire dirty region as represented
-         * by the contents of the inOutDirty rectangle upon return from this function.
-         * The caller may also pass <code>null</code> instead, in the case where the
-         * entire surface should be redrawn.
-         * @return A canvas for drawing into the surface.
-         */
-        @Override
-        public Canvas lockCanvas(Rect inOutDirty) {
-            return internalLockCanvas(inOutDirty, false);
-        }
-
-        @Override
-        public Canvas lockHardwareCanvas() {
-            return internalLockCanvas(null, true);
-        }
-
-        private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
-            mSurfaceLock.lock();
-
-            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
-                    + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
-
-            Canvas c = null;
-            if (!mDrawingStopped && mSurfaceControl != null) {
-                try {
-                    if (hardware) {
-                        c = mSurface.lockHardwareCanvas();
-                    } else {
-                        c = mSurface.lockCanvas(dirty);
-                    }
-                } catch (Exception e) {
-                    Log.e(LOG_TAG, "Exception locking surface", e);
-                }
-            }
-
-            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
-            if (c != null) {
-                mLastLockTime = SystemClock.uptimeMillis();
-                return c;
-            }
-
-            // If the Surface is not ready to be drawn, then return null,
-            // but throttle calls to this function so it isn't called more
-            // than every 100ms.
-            long now = SystemClock.uptimeMillis();
-            long nextTime = mLastLockTime + 100;
-            if (nextTime > now) {
-                try {
-                    Thread.sleep(nextTime-now);
-                } catch (InterruptedException e) {
-                }
-                now = SystemClock.uptimeMillis();
-            }
-            mLastLockTime = now;
-            mSurfaceLock.unlock();
-
             return null;
         }
 
-        /**
-         * Posts the new contents of the {@link Canvas} to the surface and
-         * releases the {@link Canvas}.
-         *
-         * @param canvas The canvas previously obtained from {@link #lockCanvas}.
-         */
+        @Override
+        public Canvas lockCanvas(Rect dirty) {
+            return null;
+        }
+
         @Override
         public void unlockCanvasAndPost(Canvas canvas) {
-            mSurface.unlockCanvasAndPost(canvas);
-            mSurfaceLock.unlock();
         }
 
         @Override
         public Surface getSurface() {
-            return mSurface;
+            return null;
         }
 
         @Override
         public Rect getSurfaceFrame() {
-            return mSurfaceFrame;
+            return null;
         }
     };
-
-    class SurfaceControlWithBackground extends SurfaceControl {
-        private SurfaceControl mBackgroundControl;
-        private boolean mOpaque = true;
-        public boolean mVisible = false;
-
-        public SurfaceControlWithBackground(SurfaceSession s,
-                        String name, int w, int h, int format, int flags)
-                       throws Exception {
-            super(s, name, w, h, format, flags);
-            mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
-                    PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
-            mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
-        }
-
-        @Override
-        public void setAlpha(float alpha) {
-            super.setAlpha(alpha);
-            mBackgroundControl.setAlpha(alpha);
-        }
-
-        @Override
-        public void setLayer(int zorder) {
-            super.setLayer(zorder);
-            // -3 is below all other child layers as SurfaceView never goes below -2
-            mBackgroundControl.setLayer(-3);
-        }
-
-        @Override
-        public void setPosition(float x, float y) {
-            super.setPosition(x, y);
-            mBackgroundControl.setPosition(x, y);
-        }
-
-        @Override
-        public void setSize(int w, int h) {
-            super.setSize(w, h);
-            mBackgroundControl.setSize(w, h);
-        }
-
-        @Override
-        public void setWindowCrop(Rect crop) {
-            super.setWindowCrop(crop);
-            mBackgroundControl.setWindowCrop(crop);
-        }
-
-        @Override
-        public void setFinalCrop(Rect crop) {
-            super.setFinalCrop(crop);
-            mBackgroundControl.setFinalCrop(crop);
-        }
-
-        @Override
-        public void setLayerStack(int layerStack) {
-            super.setLayerStack(layerStack);
-            mBackgroundControl.setLayerStack(layerStack);
-        }
-
-        @Override
-        public void setOpaque(boolean isOpaque) {
-            super.setOpaque(isOpaque);
-            mOpaque = isOpaque;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void setSecure(boolean isSecure) {
-            super.setSecure(isSecure);
-        }
-
-        @Override
-        public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
-            super.setMatrix(dsdx, dtdx, dsdy, dtdy);
-            mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
-        }
-
-        @Override
-        public void hide() {
-            super.hide();
-            mVisible = false;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void show() {
-            super.show();
-            mVisible = true;
-            updateBackgroundVisibility();
-        }
-
-        @Override
-        public void destroy() {
-            super.destroy();
-            mBackgroundControl.destroy();
-         }
-
-        @Override
-        public void release() {
-            super.release();
-            mBackgroundControl.release();
-        }
-
-        @Override
-        public void setTransparentRegionHint(Region region) {
-            super.setTransparentRegionHint(region);
-            mBackgroundControl.setTransparentRegionHint(region);
-        }
-
-        @Override
-        public void deferTransactionUntil(IBinder handle, long frame) {
-            super.deferTransactionUntil(handle, frame);
-            mBackgroundControl.deferTransactionUntil(handle, frame);
-        }
-
-        @Override
-        public void deferTransactionUntil(Surface barrier, long frame) {
-            super.deferTransactionUntil(barrier, frame);
-            mBackgroundControl.deferTransactionUntil(barrier, frame);
-        }
-
-        void updateBackgroundVisibility() {
-            if (mOpaque && mVisible) {
-                mBackgroundControl.show();
-            } else {
-                mBackgroundControl.hide();
-            }
-        }
-    }
 }
+
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 0b9bc57..11cb046 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,152 +16,46 @@
 
 package android.view.accessibility;
 
-import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
-
-import android.Manifest;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.annotation.SystemService;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.os.Binder;
 import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
 import android.view.IWindow;
 import android.view.View;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IntPair;
-
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
- * and provides facilities for querying the accessibility state of the system.
- * Accessibility events are generated when something notable happens in the user interface,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
+ * Such events are generated when something notable happens in the user interface,
  * for example an {@link android.app.Activity} starts, the focus or selection of a
  * {@link android.view.View} changes etc. Parties interested in handling accessibility
  * events implement and register an accessibility service which extends
- * {@link android.accessibilityservice.AccessibilityService}.
+ * {@code android.accessibilityservice.AccessibilityService}.
  *
  * @see AccessibilityEvent
- * @see AccessibilityNodeInfo
- * @see android.accessibilityservice.AccessibilityService
- * @see Context#getSystemService
- * @see Context#ACCESSIBILITY_SERVICE
+ * @see android.content.Context#getSystemService
  */
-@SystemService(Context.ACCESSIBILITY_SERVICE)
+@SuppressWarnings("UnusedDeclaration")
 public final class AccessibilityManager {
-    private static final boolean DEBUG = false;
 
-    private static final String LOG_TAG = "AccessibilityManager";
+    private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
 
-    /** @hide */
-    public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
-
-    /** @hide */
-    public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
-
-    /** @hide */
-    public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
-
-    /** @hide */
-    public static final int DALTONIZER_DISABLED = -1;
-
-    /** @hide */
-    public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
-
-    /** @hide */
-    public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
-
-    /** @hide */
-    public static final int AUTOCLICK_DELAY_DEFAULT = 600;
 
     /**
-     * Activity action: Launch UI to manage which accessibility service or feature is assigned
-     * to the navigation bar Accessibility button.
-     * <p>
-     * Input: Nothing.
-     * </p>
-     * <p>
-     * Output: Nothing.
-     * </p>
-     *
-     * @hide
-     */
-    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
-            "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
-
-    static final Object sInstanceSync = new Object();
-
-    private static AccessibilityManager sInstance;
-
-    private final Object mLock = new Object();
-
-    private IAccessibilityManager mService;
-
-    final int mUserId;
-
-    final Handler mHandler;
-
-    final Handler.Callback mCallback;
-
-    boolean mIsEnabled;
-
-    int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-
-    boolean mIsTouchExplorationEnabled;
-
-    boolean mIsHighTextContrastEnabled;
-
-    private final ArrayMap<AccessibilityStateChangeListener, Handler>
-            mAccessibilityStateChangeListeners = new ArrayMap<>();
-
-    private final ArrayMap<TouchExplorationStateChangeListener, Handler>
-            mTouchExplorationStateChangeListeners = new ArrayMap<>();
-
-    private final ArrayMap<HighTextContrastChangeListener, Handler>
-            mHighTextContrastStateChangeListeners = new ArrayMap<>();
-
-    private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
-            mServicesStateChangeListeners = new ArrayMap<>();
-
-    /**
-     * Map from a view's accessibility id to the list of request preparers set for that view
-     */
-    private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
-
-    /**
-     * Listener for the system accessibility state. To listen for changes to the
-     * accessibility state on the device, implement this interface and register
-     * it with the system by calling {@link #addAccessibilityStateChangeListener}.
+     * Listener for the accessibility state.
      */
     public interface AccessibilityStateChangeListener {
 
         /**
-         * Called when the accessibility enabled state changes.
+         * Called back on change in the accessibility state.
          *
          * @param enabled Whether accessibility is enabled.
          */
-        void onAccessibilityStateChanged(boolean enabled);
+        public void onAccessibilityStateChanged(boolean enabled);
     }
 
     /**
@@ -177,24 +71,7 @@
          *
          * @param enabled Whether touch exploration is enabled.
          */
-        void onTouchExplorationStateChanged(boolean enabled);
-    }
-
-    /**
-     * Listener for changes to the state of accessibility services. Changes include services being
-     * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
-     * {@see #addAccessibilityServicesStateChangeListener}.
-     *
-     * @hide
-     */
-    public interface AccessibilityServicesStateChangeListener {
-
-        /**
-         * Called when the state of accessibility services changes.
-         *
-         * @param manager The manager that is calling back
-         */
-        void onAccessibilityServicesStateChanged(AccessibilityManager manager);
+        public void onTouchExplorationStateChanged(boolean enabled);
     }
 
     /**
@@ -202,8 +79,6 @@
      * the high text contrast state on the device, implement this interface and
      * register it with the system by calling
      * {@link #addHighTextContrastStateChangeListener}.
-     *
-     * @hide
      */
     public interface HighTextContrastChangeListener {
 
@@ -212,72 +87,26 @@
          *
          * @param enabled Whether high text contrast is enabled.
          */
-        void onHighTextContrastStateChanged(boolean enabled);
+        public void onHighTextContrastStateChanged(boolean enabled);
     }
 
     private final IAccessibilityManagerClient.Stub mClient =
             new IAccessibilityManagerClient.Stub() {
-        @Override
-        public void setState(int state) {
-            // We do not want to change this immediately as the application may
-            // have already checked that accessibility is on and fired an event,
-            // that is now propagating up the view tree, Hence, if accessibility
-            // is now off an exception will be thrown. We want to have the exception
-            // enforcement to guard against apps that fire unnecessary accessibility
-            // events when accessibility is off.
-            mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
-        }
-
-        @Override
-        public void notifyServicesStateChanged() {
-            final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
-            synchronized (mLock) {
-                if (mServicesStateChangeListeners.isEmpty()) {
-                    return;
+                public void setState(int state) {
                 }
-                listeners = new ArrayMap<>(mServicesStateChangeListeners);
-            }
 
-            int numListeners = listeners.size();
-            for (int i = 0; i < numListeners; i++) {
-                final AccessibilityServicesStateChangeListener listener =
-                        mServicesStateChangeListeners.keyAt(i);
-                mServicesStateChangeListeners.valueAt(i).post(() -> listener
-                        .onAccessibilityServicesStateChanged(AccessibilityManager.this));
-            }
-        }
+                public void notifyServicesStateChanged() {
+                }
 
-        @Override
-        public void setRelevantEventTypes(int eventTypes) {
-            mRelevantEventTypes = eventTypes;
-        }
-    };
+                public void setRelevantEventTypes(int eventTypes) {
+                }
+            };
 
     /**
      * Get an AccessibilityManager instance (create one if necessary).
      *
-     * @param context Context in which this manager operates.
-     *
-     * @hide
      */
     public static AccessibilityManager getInstance(Context context) {
-        synchronized (sInstanceSync) {
-            if (sInstance == null) {
-                final int userId;
-                if (Binder.getCallingUid() == Process.SYSTEM_UID
-                        || context.checkCallingOrSelfPermission(
-                                Manifest.permission.INTERACT_ACROSS_USERS)
-                                        == PackageManager.PERMISSION_GRANTED
-                        || context.checkCallingOrSelfPermission(
-                                Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-                                        == PackageManager.PERMISSION_GRANTED) {
-                    userId = UserHandle.USER_CURRENT;
-                } else {
-                    userId = UserHandle.myUserId();
-                }
-                sInstance = new AccessibilityManager(context, null, userId);
-            }
-        }
         return sInstance;
     }
 
@@ -285,68 +114,21 @@
      * Create an instance.
      *
      * @param context A {@link Context}.
-     * @param service An interface to the backing service.
-     * @param userId User id under which to run.
-     *
-     * @hide
      */
     public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
-        // Constructor can't be chained because we can't create an instance of an inner class
-        // before calling another constructor.
-        mCallback = new MyCallback();
-        mHandler = new Handler(context.getMainLooper(), mCallback);
-        mUserId = userId;
-        synchronized (mLock) {
-            tryConnectToServiceLocked(service);
-        }
     }
 
-    /**
-     * Create an instance.
-     *
-     * @param handler The handler to use
-     * @param service An interface to the backing service.
-     * @param userId User id under which to run.
-     *
-     * @hide
-     */
-    public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
-        mCallback = new MyCallback();
-        mHandler = handler;
-        mUserId = userId;
-        synchronized (mLock) {
-            tryConnectToServiceLocked(service);
-        }
-    }
-
-    /**
-     * @hide
-     */
     public IAccessibilityManagerClient getClient() {
         return mClient;
     }
 
     /**
-     * @hide
-     */
-    @VisibleForTesting
-    public Handler.Callback getCallback() {
-        return mCallback;
-    }
-
-    /**
-     * Returns if the accessibility in the system is enabled.
+     * Returns if the {@link AccessibilityManager} is enabled.
      *
-     * @return True if accessibility is enabled, false otherwise.
+     * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
      */
     public boolean isEnabled() {
-        synchronized (mLock) {
-            IAccessibilityManager service = getServiceLocked();
-            if (service == null) {
-                return false;
-            }
-            return mIsEnabled;
-        }
+        return false;
     }
 
     /**
@@ -355,13 +137,7 @@
      * @return True if touch exploration is enabled, false otherwise.
      */
     public boolean isTouchExplorationEnabled() {
-        synchronized (mLock) {
-            IAccessibilityManager service = getServiceLocked();
-            if (service == null) {
-                return false;
-            }
-            return mIsTouchExplorationEnabled;
-        }
+        return true;
     }
 
     /**
@@ -371,169 +147,35 @@
      * doing its own rendering and does not rely on the platform rendering pipeline.
      * </p>
      *
-     * @return True if high text contrast is enabled, false otherwise.
-     *
-     * @hide
      */
     public boolean isHighTextContrastEnabled() {
-        synchronized (mLock) {
-            IAccessibilityManager service = getServiceLocked();
-            if (service == null) {
-                return false;
-            }
-            return mIsHighTextContrastEnabled;
-        }
+        return false;
     }
 
     /**
      * Sends an {@link AccessibilityEvent}.
-     *
-     * @param event The event to send.
-     *
-     * @throws IllegalStateException if accessibility is not enabled.
-     *
-     * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
-     * events is through calling
-     * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
-     * instead of this method to allow predecessors to augment/filter events sent by
-     * their descendants.
      */
     public void sendAccessibilityEvent(AccessibilityEvent event) {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-            if (!mIsEnabled) {
-                Looper myLooper = Looper.myLooper();
-                if (myLooper == Looper.getMainLooper()) {
-                    throw new IllegalStateException(
-                            "Accessibility off. Did you forget to check that?");
-                } else {
-                    // If we're not running on the thread with the main looper, it's possible for
-                    // the state of accessibility to change between checking isEnabled and
-                    // calling this method. So just log the error rather than throwing the
-                    // exception.
-                    Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
-                    return;
-                }
-            }
-            if ((event.getEventType() & mRelevantEventTypes) == 0) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
-                            + " that is not among "
-                            + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
-                }
-                return;
-            }
-            userId = mUserId;
-        }
-        try {
-            event.setEventTime(SystemClock.uptimeMillis());
-            // it is possible that this manager is in the same process as the service but
-            // client using it is called through Binder from another process. Example: MMS
-            // app adds a SMS notification and the NotificationManagerService calls this method
-            long identityToken = Binder.clearCallingIdentity();
-            service.sendAccessibilityEvent(event, userId);
-            Binder.restoreCallingIdentity(identityToken);
-            if (DEBUG) {
-                Log.i(LOG_TAG, event + " sent");
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error during sending " + event + " ", re);
-        } finally {
-            event.recycle();
-        }
     }
 
     /**
-     * Requests feedback interruption from all accessibility services.
+     * Requests interruption of the accessibility feedback from all accessibility services.
      */
     public void interrupt() {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-            if (!mIsEnabled) {
-                Looper myLooper = Looper.myLooper();
-                if (myLooper == Looper.getMainLooper()) {
-                    throw new IllegalStateException(
-                            "Accessibility off. Did you forget to check that?");
-                } else {
-                    // If we're not running on the thread with the main looper, it's possible for
-                    // the state of accessibility to change between checking isEnabled and
-                    // calling this method. So just log the error rather than throwing the
-                    // exception.
-                    Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
-                    return;
-                }
-            }
-            userId = mUserId;
-        }
-        try {
-            service.interrupt(userId);
-            if (DEBUG) {
-                Log.i(LOG_TAG, "Requested interrupt from all services");
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
-        }
     }
 
     /**
      * Returns the {@link ServiceInfo}s of the installed accessibility services.
      *
      * @return An unmodifiable list with {@link ServiceInfo}s.
-     *
-     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
      */
     @Deprecated
     public List<ServiceInfo> getAccessibilityServiceList() {
-        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
-        List<ServiceInfo> services = new ArrayList<>();
-        final int infoCount = infos.size();
-        for (int i = 0; i < infoCount; i++) {
-            AccessibilityServiceInfo info = infos.get(i);
-            services.add(info.getResolveInfo().serviceInfo);
-        }
-        return Collections.unmodifiableList(services);
+        return Collections.emptyList();
     }
 
-    /**
-     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
-     *
-     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
-     */
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return Collections.emptyList();
-            }
-            userId = mUserId;
-        }
-
-        List<AccessibilityServiceInfo> services = null;
-        try {
-            services = service.getInstalledAccessibilityServiceList(userId);
-            if (DEBUG) {
-                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
-        }
-        if (services != null) {
-            return Collections.unmodifiableList(services);
-        } else {
-            return Collections.emptyList();
-        }
+        return Collections.emptyList();
     }
 
     /**
@@ -548,48 +190,21 @@
      * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
      * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
      * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
-     * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
      */
     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
             int feedbackTypeFlags) {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return Collections.emptyList();
-            }
-            userId = mUserId;
-        }
-
-        List<AccessibilityServiceInfo> services = null;
-        try {
-            services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
-            if (DEBUG) {
-                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
-        }
-        if (services != null) {
-            return Collections.unmodifiableList(services);
-        } else {
-            return Collections.emptyList();
-        }
+        return Collections.emptyList();
     }
 
     /**
      * Registers an {@link AccessibilityStateChangeListener} for changes in
-     * the global accessibility state of the system. Equivalent to calling
-     * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
-     * with a null handler.
+     * the global accessibility state of the system.
      *
      * @param listener The listener.
-     * @return Always returns {@code true}.
+     * @return True if successfully registered.
      */
     public boolean addAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener) {
-        addAccessibilityStateChangeListener(listener, null);
+            AccessibilityStateChangeListener listener) {
         return true;
     }
 
@@ -603,40 +218,22 @@
      *                for a callback on the process's main handler.
      */
     public void addAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
-        synchronized (mLock) {
-            mAccessibilityStateChangeListeners
-                    .put(listener, (handler == null) ? mHandler : handler);
-        }
-    }
+            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
 
-    /**
-     * Unregisters an {@link AccessibilityStateChangeListener}.
-     *
-     * @param listener The listener.
-     * @return True if the listener was previously registered.
-     */
     public boolean removeAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener) {
-        synchronized (mLock) {
-            int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
-            mAccessibilityStateChangeListeners.remove(listener);
-            return (index >= 0);
-        }
+            AccessibilityStateChangeListener listener) {
+        return true;
     }
 
     /**
      * Registers a {@link TouchExplorationStateChangeListener} for changes in
-     * the global touch exploration state of the system. Equivalent to calling
-     * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
-     * with a null handler.
+     * the global touch exploration state of the system.
      *
      * @param listener The listener.
-     * @return Always returns {@code true}.
+     * @return True if successfully registered.
      */
     public boolean addTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        addTouchExplorationStateChangeListener(listener, null);
         return true;
     }
 
@@ -650,103 +247,17 @@
      *                for a callback on the process's main handler.
      */
     public void addTouchExplorationStateChangeListener(
-            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
-        synchronized (mLock) {
-            mTouchExplorationStateChangeListeners
-                    .put(listener, (handler == null) ? mHandler : handler);
-        }
-    }
+            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
 
     /**
      * Unregisters a {@link TouchExplorationStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if listener was previously registered.
+     * @return True if successfully unregistered.
      */
     public boolean removeTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        synchronized (mLock) {
-            int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
-            mTouchExplorationStateChangeListeners.remove(listener);
-            return (index >= 0);
-        }
-    }
-
-    /**
-     * Registers a {@link AccessibilityServicesStateChangeListener}.
-     *
-     * @param listener The listener.
-     * @param handler The handler on which the listener should be called back, or {@code null}
-     *                for a callback on the process's main handler.
-     * @hide
-     */
-    public void addAccessibilityServicesStateChangeListener(
-            @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
-        synchronized (mLock) {
-            mServicesStateChangeListeners
-                    .put(listener, (handler == null) ? mHandler : handler);
-        }
-    }
-
-    /**
-     * Unregisters a {@link AccessibilityServicesStateChangeListener}.
-     *
-     * @param listener The listener.
-     *
-     * @hide
-     */
-    public void removeAccessibilityServicesStateChangeListener(
-            @NonNull AccessibilityServicesStateChangeListener listener) {
-        // Final CopyOnWriteArrayList - no lock needed.
-        mServicesStateChangeListeners.remove(listener);
-    }
-
-    /**
-     * Registers a {@link AccessibilityRequestPreparer}.
-     */
-    public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
-        if (mRequestPreparerLists == null) {
-            mRequestPreparerLists = new SparseArray<>(1);
-        }
-        int id = preparer.getView().getAccessibilityViewId();
-        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
-        if (requestPreparerList == null) {
-            requestPreparerList = new ArrayList<>(1);
-            mRequestPreparerLists.put(id, requestPreparerList);
-        }
-        requestPreparerList.add(preparer);
-    }
-
-    /**
-     * Unregisters a {@link AccessibilityRequestPreparer}.
-     */
-    public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
-        if (mRequestPreparerLists == null) {
-            return;
-        }
-        int viewId = preparer.getView().getAccessibilityViewId();
-        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
-        if (requestPreparerList != null) {
-            requestPreparerList.remove(preparer);
-            if (requestPreparerList.isEmpty()) {
-                mRequestPreparerLists.remove(viewId);
-            }
-        }
-    }
-
-    /**
-     * Get the preparers that are registered for an accessibility ID
-     *
-     * @param id The ID of interest
-     * @return The list of preparers, or {@code null} if there are none.
-     *
-     * @hide
-     */
-    public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
-        if (mRequestPreparerLists == null) {
-            return null;
-        }
-        return mRequestPreparerLists.get(id);
+        return true;
     }
 
     /**
@@ -758,12 +269,7 @@
      * @hide
      */
     public void addHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
-        synchronized (mLock) {
-            mHighTextContrastStateChangeListeners
-                    .put(listener, (handler == null) ? mHandler : handler);
-        }
-    }
+            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
 
     /**
      * Unregisters a {@link HighTextContrastChangeListener}.
@@ -773,51 +279,7 @@
      * @hide
      */
     public void removeHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener) {
-        synchronized (mLock) {
-            mHighTextContrastStateChangeListeners.remove(listener);
-        }
-    }
-
-    /**
-     * Check if the accessibility volume stream is active.
-     *
-     * @return True if accessibility volume is active (i.e. some service has requested it). False
-     * otherwise.
-     * @hide
-     */
-    public boolean isAccessibilityVolumeStreamActive() {
-        List<AccessibilityServiceInfo> serviceInfos =
-                getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
-        for (int i = 0; i < serviceInfos.size(); i++) {
-            if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Report a fingerprint gesture to accessibility. Only available for the system process.
-     *
-     * @param keyCode The key code of the gesture
-     * @return {@code true} if accessibility consumes the event. {@code false} if not.
-     * @hide
-     */
-    public boolean sendFingerprintGesture(int keyCode) {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return false;
-            }
-        }
-        try {
-            return service.sendFingerprintGesture(keyCode);
-        } catch (RemoteException e) {
-            return false;
-        }
-    }
+            @NonNull HighTextContrastChangeListener listener) {}
 
     /**
      * Sets the current state and notifies listeners, if necessary.
@@ -825,314 +287,14 @@
      * @param stateFlags The state flags.
      */
     private void setStateLocked(int stateFlags) {
-        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-        final boolean touchExplorationEnabled =
-                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
-        final boolean highTextContrastEnabled =
-                (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
-
-        final boolean wasEnabled = mIsEnabled;
-        final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
-        final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
-
-        // Ensure listeners get current state from isZzzEnabled() calls.
-        mIsEnabled = enabled;
-        mIsTouchExplorationEnabled = touchExplorationEnabled;
-        mIsHighTextContrastEnabled = highTextContrastEnabled;
-
-        if (wasEnabled != enabled) {
-            notifyAccessibilityStateChanged();
-        }
-
-        if (wasTouchExplorationEnabled != touchExplorationEnabled) {
-            notifyTouchExplorationStateChanged();
-        }
-
-        if (wasHighTextContrastEnabled != highTextContrastEnabled) {
-            notifyHighTextContrastStateChanged();
-        }
     }
 
-    /**
-     * Find an installed service with the specified {@link ComponentName}.
-     *
-     * @param componentName The name to match to the service.
-     *
-     * @return The info corresponding to the installed service, or {@code null} if no such service
-     * is installed.
-     * @hide
-     */
-    public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
-            ComponentName componentName) {
-        final List<AccessibilityServiceInfo> installedServiceInfos =
-                getInstalledAccessibilityServiceList();
-        if ((installedServiceInfos == null) || (componentName == null)) {
-            return null;
-        }
-        for (int i = 0; i < installedServiceInfos.size(); i++) {
-            if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
-                return installedServiceInfos.get(i);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Adds an accessibility interaction connection interface for a given window.
-     * @param windowToken The window token to which a connection is added.
-     * @param connection The connection.
-     *
-     * @hide
-     */
     public int addAccessibilityInteractionConnection(IWindow windowToken,
             IAccessibilityInteractionConnection connection) {
-        final IAccessibilityManager service;
-        final int userId;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return View.NO_ID;
-            }
-            userId = mUserId;
-        }
-        try {
-            return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
-        }
         return View.NO_ID;
     }
 
-    /**
-     * Removed an accessibility interaction connection interface for a given window.
-     * @param windowToken The window token to which a connection is removed.
-     *
-     * @hide
-     */
     public void removeAccessibilityInteractionConnection(IWindow windowToken) {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.removeAccessibilityInteractionConnection(windowToken);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
-        }
     }
 
-    /**
-     * Perform the accessibility shortcut if the caller has permission.
-     *
-     * @hide
-     */
-    public void performAccessibilityShortcut() {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.performAccessibilityShortcut();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
-        }
-    }
-
-    /**
-     * Notifies that the accessibility button in the system's navigation area has been clicked
-     *
-     * @hide
-     */
-    public void notifyAccessibilityButtonClicked() {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.notifyAccessibilityButtonClicked();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
-        }
-    }
-
-    /**
-     * Notifies that the visibility of the accessibility button in the system's navigation area
-     * has changed.
-     *
-     * @param shown {@code true} if the accessibility button is visible within the system
-     *                  navigation area, {@code false} otherwise
-     * @hide
-     */
-    public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.notifyAccessibilityButtonVisibilityChanged(shown);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
-        }
-    }
-
-    /**
-     * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
-     * window. Intended for use by the System UI only.
-     *
-     * @param connection The connection to handle the actions. Set to {@code null} to avoid
-     * affecting the actions.
-     *
-     * @hide
-     */
-    public void setPictureInPictureActionReplacingConnection(
-            @Nullable IAccessibilityInteractionConnection connection) {
-        final IAccessibilityManager service;
-        synchronized (mLock) {
-            service = getServiceLocked();
-            if (service == null) {
-                return;
-            }
-        }
-        try {
-            service.setPictureInPictureActionReplacingConnection(connection);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
-        }
-    }
-
-    private IAccessibilityManager getServiceLocked() {
-        if (mService == null) {
-            tryConnectToServiceLocked(null);
-        }
-        return mService;
-    }
-
-    private void tryConnectToServiceLocked(IAccessibilityManager service) {
-        if (service == null) {
-            IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
-            if (iBinder == null) {
-                return;
-            }
-            service = IAccessibilityManager.Stub.asInterface(iBinder);
-        }
-
-        try {
-            final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
-            setStateLocked(IntPair.first(userStateAndRelevantEvents));
-            mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
-            mService = service;
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
-        }
-    }
-
-    /**
-     * Notifies the registered {@link AccessibilityStateChangeListener}s.
-     */
-    private void notifyAccessibilityStateChanged() {
-        final boolean isEnabled;
-        final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
-        synchronized (mLock) {
-            if (mAccessibilityStateChangeListeners.isEmpty()) {
-                return;
-            }
-            isEnabled = mIsEnabled;
-            listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
-        }
-
-        int numListeners = listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            final AccessibilityStateChangeListener listener =
-                    mAccessibilityStateChangeListeners.keyAt(i);
-            mAccessibilityStateChangeListeners.valueAt(i)
-                    .post(() -> listener.onAccessibilityStateChanged(isEnabled));
-        }
-    }
-
-    /**
-     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
-     */
-    private void notifyTouchExplorationStateChanged() {
-        final boolean isTouchExplorationEnabled;
-        final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
-        synchronized (mLock) {
-            if (mTouchExplorationStateChangeListeners.isEmpty()) {
-                return;
-            }
-            isTouchExplorationEnabled = mIsTouchExplorationEnabled;
-            listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
-        }
-
-        int numListeners = listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            final TouchExplorationStateChangeListener listener =
-                    mTouchExplorationStateChangeListeners.keyAt(i);
-            mTouchExplorationStateChangeListeners.valueAt(i)
-                    .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
-        }
-    }
-
-    /**
-     * Notifies the registered {@link HighTextContrastChangeListener}s.
-     */
-    private void notifyHighTextContrastStateChanged() {
-        final boolean isHighTextContrastEnabled;
-        final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
-        synchronized (mLock) {
-            if (mHighTextContrastStateChangeListeners.isEmpty()) {
-                return;
-            }
-            isHighTextContrastEnabled = mIsHighTextContrastEnabled;
-            listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
-        }
-
-        int numListeners = listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            final HighTextContrastChangeListener listener =
-                    mHighTextContrastStateChangeListeners.keyAt(i);
-            mHighTextContrastStateChangeListeners.valueAt(i)
-                    .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
-        }
-    }
-
-    /**
-     * Determines if the accessibility button within the system navigation area is supported.
-     *
-     * @return {@code true} if the accessibility button is supported on this device,
-     * {@code false} otherwise
-     */
-    public static boolean isAccessibilityButtonSupported() {
-        final Resources res = Resources.getSystem();
-        return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
-    }
-
-    private final class MyCallback implements Handler.Callback {
-        public static final int MSG_SET_STATE = 1;
-
-        @Override
-        public boolean handleMessage(Message message) {
-            switch (message.what) {
-                case MSG_SET_STATE: {
-                    // See comment at mClient
-                    final int state = message.arg1;
-                    synchronized (mLock) {
-                        setStateLocked(state);
-                    }
-                } break;
-            }
-            return true;
-        }
-    }
 }
diff --git a/android/view/inputmethod/ExtractedText.java b/android/view/inputmethod/ExtractedText.java
index 0c5d9e9..003f221 100644
--- a/android/view/inputmethod/ExtractedText.java
+++ b/android/view/inputmethod/ExtractedText.java
@@ -87,6 +87,11 @@
     public int flags;
 
     /**
+     * The hint that has been extracted.
+     */
+    public CharSequence hint;
+
+    /**
      * Used to package this object into a {@link Parcel}.
      *
      * @param dest The {@link Parcel} to be written.
@@ -100,6 +105,7 @@
         dest.writeInt(selectionStart);
         dest.writeInt(selectionEnd);
         dest.writeInt(this.flags);
+        TextUtils.writeToParcel(hint, dest, flags);
     }
 
     /**
@@ -107,17 +113,18 @@
      */
     public static final Parcelable.Creator<ExtractedText> CREATOR
             = new Parcelable.Creator<ExtractedText>() {
-        public ExtractedText createFromParcel(Parcel source) {
-            ExtractedText res = new ExtractedText();
-            res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            res.startOffset = source.readInt();
-            res.partialStartOffset = source.readInt();
-            res.partialEndOffset = source.readInt();
-            res.selectionStart = source.readInt();
-            res.selectionEnd = source.readInt();
-            res.flags = source.readInt();
-            return res;
-        }
+                public ExtractedText createFromParcel(Parcel source) {
+                    ExtractedText res = new ExtractedText();
+                    res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+                    res.startOffset = source.readInt();
+                    res.partialStartOffset = source.readInt();
+                    res.partialEndOffset = source.readInt();
+                    res.selectionStart = source.readInt();
+                    res.selectionEnd = source.readInt();
+                    res.flags = source.readInt();
+                    res.hint = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+                    return res;
+                }
 
         public ExtractedText[] newArray(int size) {
             return new ExtractedText[size];
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 77aea23..8d88ba6 100644
--- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -42,17 +42,16 @@
 //TODO: Do not allow any crashes from this class.
 public final class SmartSelectionEventTracker {
 
-    private static final String LOG_TAG = "SmartSelectionEventTracker";
+    private static final String LOG_TAG = "SmartSelectEventTracker";
     private static final boolean DEBUG_LOG_ENABLED = true;
 
-    private static final int START_EVENT_DELTA = MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS;
-    private static final int PREV_EVENT_DELTA = MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS;
-    private static final int ENTITY_TYPE = MetricsEvent.NOTIFICATION_TAG;
-    private static final int INDEX = MetricsEvent.NOTIFICATION_SHADE_INDEX;
-    private static final int TAG = MetricsEvent.FIELD_CLASS_NAME;
-    private static final int SMART_INDICES = MetricsEvent.FIELD_GESTURE_LENGTH;
-    private static final int EVENT_INDICES = MetricsEvent.FIELD_CONTEXT;
-    private static final int SESSION_ID = MetricsEvent.FIELD_INSTANT_APP_LAUNCH_TOKEN;
+    private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
+    private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
+    private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
+    private static final int VERSION_TAG = MetricsEvent.FIELD_SELECTION_VERSION_TAG;
+    private static final int SMART_INDICES = MetricsEvent.FIELD_SELECTION_SMART_RANGE;
+    private static final int EVENT_INDICES = MetricsEvent.FIELD_SELECTION_RANGE;
+    private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
 
     private static final String ZERO = "0";
     private static final String TEXTVIEW = "textview";
@@ -84,6 +83,7 @@
     private long mSessionStartTime;
     private long mLastEventTime;
     private boolean mSmartSelectionTriggered;
+    private String mVersionTag;
 
     public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
         mWidgetType = widgetType;
@@ -98,7 +98,8 @@
     public void logEvent(@NonNull SelectionEvent event) {
         Preconditions.checkNotNull(event);
 
-        if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null) {
+        if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null
+                && DEBUG_LOG_ENABLED) {
             Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
             return;
         }
@@ -114,6 +115,7 @@
             case SelectionEvent.EventType.SMART_SELECTION_SINGLE:  // fall through
             case SelectionEvent.EventType.SMART_SELECTION_MULTI:
                 mSmartSelectionTriggered = true;
+                mVersionTag = getVersionTag(event);
                 mSmartIndices[0] = event.mStart;
                 mSmartIndices[1] = event.mEnd;
                 break;
@@ -132,16 +134,15 @@
     }
 
     private void writeEvent(SelectionEvent event, long now) {
-        final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+        final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime;
+        final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
                 .setType(getLogType(event))
-                .setSubtype(event.mEventType)
+                .setSubtype(getLogSubType(event))
                 .setPackageName(mContext.getPackageName())
-                .setTimestamp(now)
                 .addTaggedData(START_EVENT_DELTA, now - mSessionStartTime)
-                .addTaggedData(PREV_EVENT_DELTA, now - mLastEventTime)
-                .addTaggedData(ENTITY_TYPE, event.mEntityType)
+                .addTaggedData(PREV_EVENT_DELTA, prevEventDelta)
                 .addTaggedData(INDEX, mIndex)
-                .addTaggedData(TAG, getTag(event))
+                .addTaggedData(VERSION_TAG, mVersionTag)
                 .addTaggedData(SMART_INDICES, getSmartDelta())
                 .addTaggedData(EVENT_INDICES, getEventDelta(event))
                 .addTaggedData(SESSION_ID, mSessionId);
@@ -168,42 +169,120 @@
         mSessionStartTime = 0;
         mLastEventTime = 0;
         mSmartSelectionTriggered = false;
+        mVersionTag = getVersionTag(null);
         mSessionId = null;
     }
 
-    private int getLogType(SelectionEvent event) {
+    private static int getLogType(SelectionEvent event) {
         switch (event.mEventType) {
-            case SelectionEvent.EventType.SELECTION_STARTED:  // fall through
-            case SelectionEvent.EventType.SMART_SELECTION_SINGLE:  // fall through
-            case SelectionEvent.EventType.SMART_SELECTION_MULTI:  // fall through
-            case SelectionEvent.EventType.AUTO_SELECTION:
-                return MetricsEvent.TYPE_OPEN;
+            case SelectionEvent.ActionType.OVERTYPE:
+                return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE;
+            case SelectionEvent.ActionType.COPY:
+                return MetricsEvent.ACTION_TEXT_SELECTION_COPY;
+            case SelectionEvent.ActionType.PASTE:
+                return MetricsEvent.ACTION_TEXT_SELECTION_PASTE;
+            case SelectionEvent.ActionType.CUT:
+                return MetricsEvent.ACTION_TEXT_SELECTION_CUT;
+            case SelectionEvent.ActionType.SHARE:
+                return MetricsEvent.ACTION_TEXT_SELECTION_SHARE;
+            case SelectionEvent.ActionType.SMART_SHARE:
+                return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
+            case SelectionEvent.ActionType.DRAG:
+                return MetricsEvent.ACTION_TEXT_SELECTION_DRAG;
             case SelectionEvent.ActionType.ABANDON:
-                return MetricsEvent.TYPE_CLOSE;
-        }
-        if (event.isActionType()) {
-            if (event.isTerminal() && mSmartSelectionTriggered) {
-                if (matchesSmartSelectionBounds(event)) {
-                    // Smart selection accepted.
-                    return MetricsEvent.TYPE_SUCCESS;
-                } else if (containsOriginalSelection(event)) {
-                    // Smart selection rejected.
-                    return MetricsEvent.TYPE_FAILURE;
-                }
-                // User changed the original selection entirely.
-            }
-            return MetricsEvent.TYPE_ACTION;
-        } else {
-            return MetricsEvent.TYPE_UPDATE;
+                return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON;
+            case SelectionEvent.ActionType.OTHER:
+                return MetricsEvent.ACTION_TEXT_SELECTION_OTHER;
+            case SelectionEvent.ActionType.SELECT_ALL:
+                return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL;
+            case SelectionEvent.ActionType.RESET:
+                return MetricsEvent.ACTION_TEXT_SELECTION_RESET;
+            case SelectionEvent.EventType.SELECTION_STARTED:
+                return MetricsEvent.ACTION_TEXT_SELECTION_START;
+            case SelectionEvent.EventType.SELECTION_MODIFIED:
+                return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY;
+            case SelectionEvent.EventType.SMART_SELECTION_SINGLE:
+                return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE;
+            case SelectionEvent.EventType.SMART_SELECTION_MULTI:
+                return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI;
+            case SelectionEvent.EventType.AUTO_SELECTION:
+                return MetricsEvent.ACTION_TEXT_SELECTION_AUTO;
+            default:
+                return MetricsEvent.VIEW_UNKNOWN;
         }
     }
 
-    private boolean matchesSmartSelectionBounds(SelectionEvent event) {
-        return event.mStart == mSmartIndices[0] && event.mEnd == mSmartIndices[1];
+    private static String getLogTypeString(int logType) {
+        switch (logType) {
+            case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE:
+                return "OVERTYPE";
+            case MetricsEvent.ACTION_TEXT_SELECTION_COPY:
+                return "COPY";
+            case MetricsEvent.ACTION_TEXT_SELECTION_PASTE:
+                return "PASTE";
+            case MetricsEvent.ACTION_TEXT_SELECTION_CUT:
+                return "CUT";
+            case MetricsEvent.ACTION_TEXT_SELECTION_SHARE:
+                return "SHARE";
+            case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
+                return "SMART_SHARE";
+            case MetricsEvent.ACTION_TEXT_SELECTION_DRAG:
+                return "DRAG";
+            case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON:
+                return "ABANDON";
+            case MetricsEvent.ACTION_TEXT_SELECTION_OTHER:
+                return "OTHER";
+            case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL:
+                return "SELECT_ALL";
+            case MetricsEvent.ACTION_TEXT_SELECTION_RESET:
+                return "RESET";
+            case MetricsEvent.ACTION_TEXT_SELECTION_START:
+                return "SELECTION_STARTED";
+            case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY:
+                return "SELECTION_MODIFIED";
+            case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE:
+                return "SMART_SELECTION_SINGLE";
+            case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI:
+                return "SMART_SELECTION_MULTI";
+            case MetricsEvent.ACTION_TEXT_SELECTION_AUTO:
+                return "AUTO_SELECTION";
+            default:
+                return UNKNOWN;
+        }
     }
 
-    private boolean containsOriginalSelection(SelectionEvent event) {
-        return event.mStart <= mOrigStart && event.mEnd > mOrigStart;
+    private static int getLogSubType(SelectionEvent event) {
+        switch (event.mEntityType) {
+            case TextClassifier.TYPE_OTHER:
+                return MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER;
+            case TextClassifier.TYPE_EMAIL:
+                return MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL;
+            case TextClassifier.TYPE_PHONE:
+                return MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE;
+            case TextClassifier.TYPE_ADDRESS:
+                return MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS;
+            case TextClassifier.TYPE_URL:
+                return MetricsEvent.TEXT_CLASSIFIER_TYPE_URL;
+            default:
+                return MetricsEvent.TEXT_CLASSIFIER_TYPE_UNKNOWN;
+        }
+    }
+
+    private static String getLogSubTypeString(int logSubType) {
+        switch (logSubType) {
+            case MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER:
+                return TextClassifier.TYPE_OTHER;
+            case MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL:
+                return TextClassifier.TYPE_EMAIL;
+            case MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE:
+                return TextClassifier.TYPE_PHONE;
+            case MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS:
+                return TextClassifier.TYPE_ADDRESS;
+            case MetricsEvent.TEXT_CLASSIFIER_TYPE_URL:
+                return TextClassifier.TYPE_URL;
+            default:
+                return TextClassifier.TYPE_UNKNOWN;
+        }
     }
 
     private int getSmartDelta() {
@@ -211,8 +290,9 @@
             return (clamp(mSmartIndices[0] - mOrigStart) << 16)
                     | (clamp(mSmartIndices[1] - mOrigStart) & 0xffff);
         }
-        // If no smart selection, return start selection indices (i.e. [0, 1])
-        return /* (0 << 16) | */ (1 & 0xffff);
+        // If the smart selection model was not run, return invalid selection indices [0,0]. This
+        // allows us to tell from the terminal event alone whether the model was run.
+        return 0;
     }
 
     private int getEventDelta(SelectionEvent event) {
@@ -220,7 +300,7 @@
                 | (clamp(event.mEnd - mOrigStart) & 0xffff);
     }
 
-    private String getTag(SelectionEvent event) {
+    private String getVersionTag(@Nullable SelectionEvent event) {
         final String widgetType;
         switch (mWidgetType) {
             case WidgetType.TEXTVIEW:
@@ -238,7 +318,9 @@
             default:
                 widgetType = UNKNOWN;
         }
-        final String version = Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
+        final String version = event == null
+                ? SelectionEvent.NO_VERSION_TAG
+                : Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
         return String.format("%s/%s", widgetType, version);
     }
 
@@ -253,66 +335,17 @@
     private static void debugLog(LogMaker log) {
         if (!DEBUG_LOG_ENABLED) return;
 
-        final String tag = Objects.toString(log.getTaggedData(TAG), "tag");
+        final String tag = Objects.toString(log.getTaggedData(VERSION_TAG), "tag");
         final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
-
-        final String event;
-        switch (log.getSubtype()) {
-            case SelectionEvent.ActionType.OVERTYPE:
-                event = "OVERTYPE";
-                break;
-            case SelectionEvent.ActionType.COPY:
-                event = "COPY";
-                break;
-            case SelectionEvent.ActionType.PASTE:
-                event = "PASTE";
-                break;
-            case SelectionEvent.ActionType.CUT:
-                event = "CUT";
-                break;
-            case SelectionEvent.ActionType.SHARE:
-                event = "SHARE";
-                break;
-            case SelectionEvent.ActionType.SMART_SHARE:
-                event = "SMART_SHARE";
-                break;
-            case SelectionEvent.ActionType.DRAG:
-                event = "DRAG";
-                break;
-            case SelectionEvent.ActionType.ABANDON:
-                event = "ABANDON";
-                break;
-            case SelectionEvent.ActionType.OTHER:
-                event = "OTHER";
-                break;
-            case SelectionEvent.ActionType.SELECT_ALL:
-                event = "SELECT_ALL";
-                break;
-            case SelectionEvent.ActionType.RESET:
-                event = "RESET";
-                break;
-            case SelectionEvent.EventType.SELECTION_STARTED:
-                String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
-                sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
-                Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
-                event = "SELECTION_STARTED";
-                break;
-            case SelectionEvent.EventType.SELECTION_MODIFIED:
-                event = "SELECTION_MODIFIED";
-                break;
-            case SelectionEvent.EventType.SMART_SELECTION_SINGLE:
-                event = "SMART_SELECTION_SINGLE";
-                break;
-            case SelectionEvent.EventType.SMART_SELECTION_MULTI:
-                event = "SMART_SELECTION_MULTI";
-                break;
-            case SelectionEvent.EventType.AUTO_SELECTION:
-                event = "AUTO_SELECTION";
-                break;
-            default:
-                event = "UNKNOWN";
+        if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
+            String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
+            sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
+            Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
         }
 
+        final String type = getLogTypeString(log.getType());
+        final String subType = getLogSubTypeString(log.getSubtype());
+
         final int smartIndices = Integer.parseInt(
                 Objects.toString(log.getTaggedData(SMART_INDICES), ZERO));
         final int smartStart = (short) ((smartIndices & 0xffff0000) >> 16);
@@ -323,11 +356,8 @@
         final int eventStart = (short) ((eventIndices & 0xffff0000) >> 16);
         final int eventEnd = (short) (eventIndices & 0xffff);
 
-        final String entity = Objects.toString(
-                log.getTaggedData(ENTITY_TYPE), TextClassifier.TYPE_UNKNOWN);
-
-        Log.d(LOG_TAG, String.format("%2d: %s, context=%d,%d - old=%d,%d [%s] (%s)",
-                index, event, eventStart, eventEnd, smartStart, smartEnd, entity, tag));
+        Log.d(LOG_TAG, String.format("%2d: %s/%s, context=%d,%d - old=%d,%d (%s)",
+                index, type, subType, eventStart, eventEnd, smartStart, smartEnd, tag));
     }
 
     /**
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index f368c74..8e1f218 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,213 +1,58 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * 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
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package android.view.textservice;
 
-import android.annotation.SystemService;
-import android.content.Context;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.Log;
 import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
 
-import com.android.internal.textservice.ITextServicesManager;
-
 import java.util.Locale;
 
 /**
- * System API to the overall text services, which arbitrates interaction between applications
- * and text services.
- *
- * The user can change the current text services in Settings. And also applications can specify
- * the target text services.
- *
- * <h3>Architecture Overview</h3>
- *
- * <p>There are three primary parties involved in the text services
- * framework (TSF) architecture:</p>
- *
- * <ul>
- * <li> The <strong>text services manager</strong> as expressed by this class
- * is the central point of the system that manages interaction between all
- * other parts.  It is expressed as the client-side API here which exists
- * in each application context and communicates with a global system service
- * that manages the interaction across all processes.
- * <li> A <strong>text service</strong> implements a particular
- * interaction model allowing the client application to retrieve information of text.
- * The system binds to the current text service that is in use, causing it to be created and run.
- * <li> Multiple <strong>client applications</strong> arbitrate with the text service
- * manager for connections to text services.
- * </ul>
- *
- * <h3>Text services sessions</h3>
- * <ul>
- * <li>The <strong>spell checker session</strong> is one of the text services.
- * {@link android.view.textservice.SpellCheckerSession}</li>
- * </ul>
- *
+ * A stub class of TextServicesManager for Layout-Lib.
  */
-@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
 public final class TextServicesManager {
-    private static final String TAG = TextServicesManager.class.getSimpleName();
-    private static final boolean DBG = false;
-
-    private static TextServicesManager sInstance;
-
-    private final ITextServicesManager mService;
-
-    private TextServicesManager() throws ServiceNotFoundException {
-        mService = ITextServicesManager.Stub.asInterface(
-                ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
-    }
+    private static final TextServicesManager sInstance = new TextServicesManager();
+    private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
 
     /**
      * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
      * @hide
      */
     public static TextServicesManager getInstance() {
-        synchronized (TextServicesManager.class) {
-            if (sInstance == null) {
-                try {
-                    sInstance = new TextServicesManager();
-                } catch (ServiceNotFoundException e) {
-                    throw new IllegalStateException(e);
-                }
-            }
-            return sInstance;
-        }
+        return sInstance;
     }
 
-    /**
-     * Returns the language component of a given locale string.
-     */
-    private static String parseLanguageFromLocaleString(String locale) {
-        final int idx = locale.indexOf('_');
-        if (idx < 0) {
-            return locale;
-        } else {
-            return locale.substring(0, idx);
-        }
-    }
-
-    /**
-     * Get a spell checker session for the specified spell checker
-     * @param locale the locale for the spell checker. If {@code locale} is null and
-     * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
-     * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
-     * the locale specified in Settings will be returned only when it is same as {@code locale}.
-     * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
-     * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
-     * selected.
-     * @param listener a spell checker session lister for getting results from a spell checker.
-     * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
-     * languages in settings will be returned.
-     * @return the spell checker session of the spell checker
-     */
     public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
             SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
-        if (listener == null) {
-            throw new NullPointerException();
-        }
-        if (!referToSpellCheckerLanguageSettings && locale == null) {
-            throw new IllegalArgumentException("Locale should not be null if you don't refer"
-                    + " settings.");
-        }
-
-        if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
-            return null;
-        }
-
-        final SpellCheckerInfo sci;
-        try {
-            sci = mService.getCurrentSpellChecker(null);
-        } catch (RemoteException e) {
-            return null;
-        }
-        if (sci == null) {
-            return null;
-        }
-        SpellCheckerSubtype subtypeInUse = null;
-        if (referToSpellCheckerLanguageSettings) {
-            subtypeInUse = getCurrentSpellCheckerSubtype(true);
-            if (subtypeInUse == null) {
-                return null;
-            }
-            if (locale != null) {
-                final String subtypeLocale = subtypeInUse.getLocale();
-                final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
-                if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
-                    return null;
-                }
-            }
-        } else {
-            final String localeStr = locale.toString();
-            for (int i = 0; i < sci.getSubtypeCount(); ++i) {
-                final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
-                final String tempSubtypeLocale = subtype.getLocale();
-                final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
-                if (tempSubtypeLocale.equals(localeStr)) {
-                    subtypeInUse = subtype;
-                    break;
-                } else if (tempSubtypeLanguage.length() >= 2 &&
-                        locale.getLanguage().equals(tempSubtypeLanguage)) {
-                    subtypeInUse = subtype;
-                }
-            }
-        }
-        if (subtypeInUse == null) {
-            return null;
-        }
-        final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
-        try {
-            mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
-                    session.getTextServicesSessionListener(),
-                    session.getSpellCheckerSessionListener(), bundle);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        return session;
+        return null;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo[] getEnabledSpellCheckers() {
-        try {
-            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
-            if (DBG) {
-                Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
-            }
-            return retval;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return EMPTY_SPELL_CHECKER_INFO;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo getCurrentSpellChecker() {
-        try {
-            // Passing null as a locale for ICS
-            return mService.getCurrentSpellChecker(null);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return null;
     }
 
     /**
@@ -215,22 +60,13 @@
      */
     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
             boolean allowImplicitlySelectedSubtype) {
-        try {
-            // Passing null as a locale until we support multiple enabled spell checker subtypes.
-            return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return null;
     }
 
     /**
      * @hide
      */
     public boolean isSpellCheckerEnabled() {
-        try {
-            return mService.isSpellCheckerEnabled();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return false;
     }
 }
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 419b7b2..202f204 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,3016 +16,223 @@
 
 package android.webkit;
 
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.Widget;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Picture;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.StrictMode;
-import android.print.PrintDocumentAdapter;
-import android.security.KeyChain;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.DragEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ViewHierarchyEncoder;
-import android.view.ViewStructure;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.autofill.AutofillValue;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.textclassifier.TextClassifier;
-import android.widget.AbsoluteLayout;
+import com.android.layoutlib.bridge.MockView;
 
-import java.io.BufferedWriter;
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Map;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Picture;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
 
 /**
- * <p>A View that displays web pages. This class is the basis upon which you
- * can roll your own web browser or simply display some online content within your Activity.
- * It uses the WebKit rendering engine to display
- * web pages and includes methods to navigate forward and backward
- * through a history, zoom in and out, perform text searches and more.
- *
- * <p>Note that, in order for your Activity to access the Internet and load web pages
- * in a WebView, you must add the {@code INTERNET} permissions to your
- * Android Manifest file:
- *
- * <pre>
- * {@code <uses-permission android:name="android.permission.INTERNET" />}
- * </pre>
- *
- * <p>This must be a child of the <a
- * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
- * element.
- *
- * <p>For more information, read
- * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>.
- *
- * <h3>Basic usage</h3>
- *
- * <p>By default, a WebView provides no browser-like widgets, does not
- * enable JavaScript and web page errors are ignored. If your goal is only
- * to display some HTML as a part of your UI, this is probably fine;
- * the user won't need to interact with the web page beyond reading
- * it, and the web page won't need to interact with the user. If you
- * actually want a full-blown web browser, then you probably want to
- * invoke the Browser application with a URL Intent rather than show it
- * with a WebView. For example:
- * <pre>
- * Uri uri = Uri.parse("https://www.example.com");
- * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- * startActivity(intent);
- * </pre>
- * <p>See {@link android.content.Intent} for more information.
- *
- * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
- * or set the entire Activity window as a WebView during {@link
- * android.app.Activity#onCreate(Bundle) onCreate()}:
- *
- * <pre class="prettyprint">
- * WebView webview = new WebView(this);
- * setContentView(webview);
- * </pre>
- *
- * <p>Then load the desired web page:
- *
- * <pre>
- * // Simplest usage: note that an exception will NOT be thrown
- * // if there is an error loading this page (see below).
- * webview.loadUrl("https://example.com/");
- *
- * // OR, you can also load from an HTML string:
- * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
- * webview.loadData(summary, "text/html", null);
- * // ... although note that there are restrictions on what this HTML can do.
- * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
- * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
- * </pre>
- *
- * <p>A WebView has several customization points where you can add your
- * own behavior. These are:
- *
- * <ul>
- *   <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
- *       This class is called when something that might impact a
- *       browser UI happens, for instance, progress updates and
- *       JavaScript alerts are sent here (see <a
- * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
- *   </li>
- *   <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
- *       It will be called when things happen that impact the
- *       rendering of the content, eg, errors or form submissions. You
- *       can also intercept URL loading here (via {@link
- * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
- * shouldOverrideUrlLoading()}).</li>
- *   <li>Modifying the {@link android.webkit.WebSettings}, such as
- * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
- * setJavaScriptEnabled()}. </li>
- *   <li>Injecting Java objects into the WebView using the
- *       {@link android.webkit.WebView#addJavascriptInterface} method. This
- *       method allows you to inject Java objects into a page's JavaScript
- *       context, so that they can be accessed by JavaScript in the page.</li>
- * </ul>
- *
- * <p>Here's a more complicated example, showing error handling,
- *    settings, and progress notification:
- *
- * <pre class="prettyprint">
- * // Let's display the progress in the activity title bar, like the
- * // browser app does.
- * getWindow().requestFeature(Window.FEATURE_PROGRESS);
- *
- * webview.getSettings().setJavaScriptEnabled(true);
- *
- * final Activity activity = this;
- * webview.setWebChromeClient(new WebChromeClient() {
- *   public void onProgressChanged(WebView view, int progress) {
- *     // Activities and WebViews measure progress with different scales.
- *     // The progress meter will automatically disappear when we reach 100%
- *     activity.setProgress(progress * 1000);
- *   }
- * });
- * webview.setWebViewClient(new WebViewClient() {
- *   public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
- *     Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
- *   }
- * });
- *
- * webview.loadUrl("https://developer.android.com/");
- * </pre>
- *
- * <h3>Zoom</h3>
- *
- * <p>To enable the built-in zoom, set
- * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
- * (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
- *
- * <p>NOTE: Using zoom if either the height or width is set to
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
- * and should be avoided.
- *
- * <h3>Cookie and window management</h3>
- *
- * <p>For obvious security reasons, your application has its own
- * cache, cookie store etc.&mdash;it does not share the Browser
- * application's data.
- *
- * <p>By default, requests by the HTML to open new windows are
- * ignored. This is {@code true} whether they be opened by JavaScript or by
- * the target attribute on a link. You can customize your
- * {@link WebChromeClient} to provide your own behavior for opening multiple windows,
- * and render them in whatever manner you want.
- *
- * <p>The standard behavior for an Activity is to be destroyed and
- * recreated when the device orientation or any other configuration changes. This will cause
- * the WebView to reload the current page. If you don't want that, you
- * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
- * changes, and then just leave the WebView alone. It'll automatically
- * re-orient itself as appropriate. Read <a
- * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
- * more information about how to handle configuration changes during runtime.
- *
- *
- * <h3>Building web pages to support different screen densities</h3>
- *
- * <p>The screen density of a device is based on the screen resolution. A screen with low density
- * has fewer available pixels per inch, where a screen with high density
- * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
- * screen is important because, other things being equal, a UI element (such as a button) whose
- * height and width are defined in terms of screen pixels will appear larger on the lower density
- * screen and smaller on the higher density screen.
- * For simplicity, Android collapses all actual screen densities into three generalized densities:
- * high, medium, and low.
- * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
- * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
- * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
- * are bigger).
- * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS,
- * and meta tag features to help you (as a web developer) target screens with different screen
- * densities.
- * <p>Here's a summary of the features you can use to handle different screen densities:
- * <ul>
- * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
- * default scaling factor used for the current device. For example, if the value of {@code
- * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
- * and default scaling is not applied to the web page; if the value is "1.5", then the device is
- * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
- * value is "0.75", then the device is considered a low density device (ldpi) and the content is
- * scaled 0.75x.</li>
- * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
- * densities for which this style sheet is to be used. The corresponding value should be either
- * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
- * density, or high density screens, respectively. For example:
- * <pre>
- * &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;</pre>
- * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
- * which is the high density pixel ratio.
- * </li>
- * </ul>
- *
- * <h3>HTML5 Video support</h3>
- *
- * <p>In order to support inline HTML5 video in your application you need to have hardware
- * acceleration turned on.
- *
- * <h3>Full screen support</h3>
- *
- * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
- * {@link android.webkit.WebChromeClient} and implement both
- * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
- * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
- * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
- * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
- * is loading.
- *
- * <h3>HTML5 Geolocation API support</h3>
- *
- * <p>For applications targeting Android N and later releases
- * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on
- * secure origins such as https. For such applications requests to geolocation api on non-secure
- * origins are automatically denied without invoking the corresponding
- * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)}
- * method.
- *
- * <h3>Layout size</h3>
- * <p>
- * It is recommended to set the WebView layout height to a fixed value or to
- * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
- * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
- * for the height none of the WebView's parents should use a
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
- * incorrect sizing of the views.
- *
- * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
- * enables the following behaviors:
- * <ul>
- * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
- * relative to the HTML body may not be sized correctly. </li>
- * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
- * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
- * </ul>
- *
- * <p>
- * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
- * supported. If such a width is used the WebView will attempt to use the width of the parent
- * instead.
- *
- * <h3>Metrics</h3>
- *
- * <p>
- * WebView may upload anonymous diagnostic data to Google when the user has consented. This data
- * helps Google improve WebView. Data is collected on a per-app basis for each app which has
- * instantiated a WebView. An individual app can opt out of this feature by putting the following
- * tag in its manifest:
- * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
- *            android:value="true" /&gt;
- * </pre>
- * <p>
- * Data will only be uploaded for a given app if the user has consented AND the app has not opted
- * out.
- *
- * <h3>Safe Browsing</h3>
- *
- * <p>
- * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
- * user to allow them to navigate back safely or proceed to the malicious page.
- * <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest:
- * <p>
- * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- *            android:value="true" /&gt;
- * </pre>
+ * Mock version of the WebView.
+ * Only non override public methods from the real WebView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ * 
+ * TODO: generate automatically.
  *
  */
-// Implementation notes.
-// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
-// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons.
-// Methods are delegated to the provider implementation: all public API methods introduced in this
-// file are fully delegated, whereas public and protected methods from the View base classes are
-// only delegated where a specific need exists for them to do so.
-@Widget
-public class WebView extends AbsoluteLayout
-        implements ViewTreeObserver.OnGlobalFocusChangeListener,
-        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
-
-    private static final String LOGTAG = "WebView";
-
-    // Throwing an exception for incorrect thread usage if the
-    // build target is JB MR2 or newer. Defaults to false, and is
-    // set in the WebView constructor.
-    private static volatile boolean sEnforceThreadChecking = false;
+public class WebView extends MockView {
 
     /**
-     *  Transportation object for returning WebView across thread boundaries.
-     */
-    public class WebViewTransport {
-        private WebView mWebview;
-
-        /**
-         * Sets the WebView to the transportation object.
-         *
-         * @param webview the WebView to transport
-         */
-        public synchronized void setWebView(WebView webview) {
-            mWebview = webview;
-        }
-
-        /**
-         * Gets the WebView object.
-         *
-         * @return the transported WebView object
-         */
-        public synchronized WebView getWebView() {
-            return mWebview;
-        }
-    }
-
-    /**
-     * URI scheme for telephone number.
-     */
-    public static final String SCHEME_TEL = "tel:";
-    /**
-     * URI scheme for email address.
-     */
-    public static final String SCHEME_MAILTO = "mailto:";
-    /**
-     * URI scheme for map address.
-     */
-    public static final String SCHEME_GEO = "geo:0,0?q=";
-
-    /**
-     * Interface to listen for find results.
-     */
-    public interface FindListener {
-        /**
-         * Notifies the listener about progress made by a find operation.
-         *
-         * @param activeMatchOrdinal the zero-based ordinal of the currently selected match
-         * @param numberOfMatches how many matches have been found
-         * @param isDoneCounting whether the find operation has actually completed. The listener
-         *                       may be notified multiple times while the
-         *                       operation is underway, and the numberOfMatches
-         *                       value should not be considered final unless
-         *                       isDoneCounting is {@code true}.
-         */
-        public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
-            boolean isDoneCounting);
-    }
-
-    /**
-     * Callback interface supplied to {@link #postVisualStateCallback} for receiving
-     * notifications about the visual state.
-     */
-    public static abstract class VisualStateCallback {
-        /**
-         * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}.
-         *
-         * @param requestId The identifier passed to {@link #postVisualStateCallback} when this
-         *                  callback was posted.
-         */
-        public abstract void onComplete(long requestId);
-    }
-
-    /**
-     * Interface to listen for new pictures as they change.
-     *
-     * @deprecated This interface is now obsolete.
-     */
-    @Deprecated
-    public interface PictureListener {
-        /**
-         * Used to provide notification that the WebView's picture has changed.
-         * See {@link WebView#capturePicture} for details of the picture.
-         *
-         * @param view the WebView that owns the picture
-         * @param picture the new picture. Applications targeting
-         *     {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
-         *     will always receive a {@code null} Picture.
-         * @deprecated Deprecated due to internal changes.
-         */
-        @Deprecated
-        public void onNewPicture(WebView view, Picture picture);
-    }
-
-    public static class HitTestResult {
-        /**
-         * Default HitTestResult, where the target is unknown.
-         */
-        public static final int UNKNOWN_TYPE = 0;
-        /**
-         * @deprecated This type is no longer used.
-         */
-        @Deprecated
-        public static final int ANCHOR_TYPE = 1;
-        /**
-         * HitTestResult for hitting a phone number.
-         */
-        public static final int PHONE_TYPE = 2;
-        /**
-         * HitTestResult for hitting a map address.
-         */
-        public static final int GEO_TYPE = 3;
-        /**
-         * HitTestResult for hitting an email address.
-         */
-        public static final int EMAIL_TYPE = 4;
-        /**
-         * HitTestResult for hitting an HTML::img tag.
-         */
-        public static final int IMAGE_TYPE = 5;
-        /**
-         * @deprecated This type is no longer used.
-         */
-        @Deprecated
-        public static final int IMAGE_ANCHOR_TYPE = 6;
-        /**
-         * HitTestResult for hitting a HTML::a tag with src=http.
-         */
-        public static final int SRC_ANCHOR_TYPE = 7;
-        /**
-         * HitTestResult for hitting a HTML::a tag with src=http + HTML::img.
-         */
-        public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
-        /**
-         * HitTestResult for hitting an edit text area.
-         */
-        public static final int EDIT_TEXT_TYPE = 9;
-
-        private int mType;
-        private String mExtra;
-
-        /**
-         * @hide Only for use by WebViewProvider implementations
-         */
-        @SystemApi
-        public HitTestResult() {
-            mType = UNKNOWN_TYPE;
-        }
-
-        /**
-         * @hide Only for use by WebViewProvider implementations
-         */
-        @SystemApi
-        public void setType(int type) {
-            mType = type;
-        }
-
-        /**
-         * @hide Only for use by WebViewProvider implementations
-         */
-        @SystemApi
-        public void setExtra(String extra) {
-            mExtra = extra;
-        }
-
-        /**
-         * Gets the type of the hit test result. See the XXX_TYPE constants
-         * defined in this class.
-         *
-         * @return the type of the hit test result
-         */
-        public int getType() {
-            return mType;
-        }
-
-        /**
-         * Gets additional type-dependant information about the result. See
-         * {@link WebView#getHitTestResult()} for details. May either be {@code null}
-         * or contain extra information about this result.
-         *
-         * @return additional type-dependant information about the result
-         */
-        public String getExtra() {
-            return mExtra;
-        }
-    }
-
-    /**
-     * Constructs a new WebView with a Context object.
-     *
-     * @param context a Context object used to access application assets
+     * Construct a new WebView with a Context object.
+     * @param context A Context object used to access application assets.
      */
     public WebView(Context context) {
         this(context, null);
     }
 
     /**
-     * Constructs a new WebView with layout parameters.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
+     * Construct a new WebView with layout parameters.
+     * @param context A Context object used to access application assets.
+     * @param attrs An AttributeSet passed to our parent.
      */
     public WebView(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.webViewStyle);
     }
 
     /**
-     * Constructs a new WebView with layout parameters and a default style.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
-     * @param defStyleAttr an attribute in the current theme that contains a
-     *        reference to a style resource that supplies default values for
-     *        the view. Can be 0 to not look for defaults.
+     * Construct a new WebView with layout parameters and a default style.
+     * @param context A Context object used to access application assets.
+     * @param attrs An AttributeSet passed to our parent.
+     * @param defStyle The default style resource ID.
      */
-    public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
+    public WebView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
     }
-
-    /**
-     * Constructs a new WebView with layout parameters and a default style.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
-     * @param defStyleAttr an attribute in the current theme that contains a
-     *        reference to a style resource that supplies default values for
-     *        the view. Can be 0 to not look for defaults.
-     * @param defStyleRes a resource identifier of a style resource that
-     *        supplies default values for the view, used only if
-     *        defStyleAttr is 0 or can not be found in the theme. Can be 0
-     *        to not look for defaults.
-     */
-    public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        this(context, attrs, defStyleAttr, defStyleRes, null, false);
-    }
-
-    /**
-     * Constructs a new WebView with layout parameters and a default style.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
-     * @param defStyleAttr an attribute in the current theme that contains a
-     *        reference to a style resource that supplies default values for
-     *        the view. Can be 0 to not look for defaults.
-     * @param privateBrowsing whether this WebView will be initialized in
-     *                        private mode
-     *
-     * @deprecated Private browsing is no longer supported directly via
-     * WebView and will be removed in a future release. Prefer using
-     * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
-     * and {@link WebStorage} for fine-grained control of privacy data.
-     */
-    @Deprecated
-    public WebView(Context context, AttributeSet attrs, int defStyleAttr,
-            boolean privateBrowsing) {
-        this(context, attrs, defStyleAttr, 0, null, privateBrowsing);
-    }
-
-    /**
-     * Constructs a new WebView with layout parameters, a default style and a set
-     * of custom JavaScript interfaces to be added to this WebView at initialization
-     * time. This guarantees that these interfaces will be available when the JS
-     * context is initialized.
-     *
-     * @param context a Context object used to access application assets
-     * @param attrs an AttributeSet passed to our parent
-     * @param defStyleAttr an attribute in the current theme that contains a
-     *        reference to a style resource that supplies default values for
-     *        the view. Can be 0 to not look for defaults.
-     * @param javaScriptInterfaces a Map of interface names, as keys, and
-     *                             object implementing those interfaces, as
-     *                             values
-     * @param privateBrowsing whether this WebView will be initialized in
-     *                        private mode
-     * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to
-     *       be added synchronously, before a subsequent loadUrl call takes effect.
-     */
-    protected WebView(Context context, AttributeSet attrs, int defStyleAttr,
-            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
-        this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
-    }
-
-    /**
-     * @hide
-     */
-    @SuppressWarnings("deprecation")  // for super() call into deprecated base class constructor.
-    protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
-            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-
-        // WebView is important by default, unless app developer overrode attribute.
-        if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
-            setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
-        }
-
-        if (context == null) {
-            throw new IllegalArgumentException("Invalid context argument");
-        }
-        sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
-                Build.VERSION_CODES.JELLY_BEAN_MR2;
-        checkThread();
-
-        ensureProviderCreated();
-        mProvider.init(javaScriptInterfaces, privateBrowsing);
-        // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
-        CookieSyncManager.setGetInstanceIsAllowed();
-    }
-
-    /**
-     * Specifies whether the horizontal scrollbar has overlay style.
-     *
-     * @deprecated This method has no effect.
-     * @param overlay {@code true} if horizontal scrollbar should have overlay style
-     */
-    @Deprecated
+    
+    // START FAKE PUBLIC METHODS
+    
     public void setHorizontalScrollbarOverlay(boolean overlay) {
     }
 
-    /**
-     * Specifies whether the vertical scrollbar has overlay style.
-     *
-     * @deprecated This method has no effect.
-     * @param overlay {@code true} if vertical scrollbar should have overlay style
-     */
-    @Deprecated
     public void setVerticalScrollbarOverlay(boolean overlay) {
     }
 
-    /**
-     * Gets whether horizontal scrollbar has overlay style.
-     *
-     * @deprecated This method is now obsolete.
-     * @return {@code true}
-     */
-    @Deprecated
     public boolean overlayHorizontalScrollbar() {
-        // The old implementation defaulted to true, so return true for consistency
-        return true;
-    }
-
-    /**
-     * Gets whether vertical scrollbar has overlay style.
-     *
-     * @deprecated This method is now obsolete.
-     * @return {@code false}
-     */
-    @Deprecated
-    public boolean overlayVerticalScrollbar() {
-        // The old implementation defaulted to false, so return false for consistency
         return false;
     }
 
-    /**
-     * Gets the visible height (in pixels) of the embedded title bar (if any).
-     *
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public int getVisibleTitleHeight() {
-        checkThread();
-        return mProvider.getVisibleTitleHeight();
+    public boolean overlayVerticalScrollbar() {
+        return false;
     }
 
-    /**
-     * Gets the SSL certificate for the main top-level page or {@code null} if there is
-     * no certificate (the site is not secure).
-     *
-     * @return the SSL certificate for the main top-level page
-     */
-    public SslCertificate getCertificate() {
-        checkThread();
-        return mProvider.getCertificate();
-    }
-
-    /**
-     * Sets the SSL certificate for the main top-level page.
-     *
-     * @deprecated Calling this function has no useful effect, and will be
-     * ignored in future releases.
-     */
-    @Deprecated
-    public void setCertificate(SslCertificate certificate) {
-        checkThread();
-        mProvider.setCertificate(certificate);
-    }
-
-    //-------------------------------------------------------------------------
-    // Methods called by activity
-    //-------------------------------------------------------------------------
-
-    /**
-     * Sets a username and password pair for the specified host. This data is
-     * used by the WebView to autocomplete username and password fields in web
-     * forms. Note that this is unrelated to the credentials used for HTTP
-     * authentication.
-     *
-     * @param host the host that required the credentials
-     * @param username the username for the given host
-     * @param password the password for the given host
-     * @see WebViewDatabase#clearUsernamePassword
-     * @see WebViewDatabase#hasUsernamePassword
-     * @deprecated Saving passwords in WebView will not be supported in future versions.
-     */
-    @Deprecated
     public void savePassword(String host, String username, String password) {
-        checkThread();
-        mProvider.savePassword(host, username, password);
     }
 
-    /**
-     * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase}
-     * instance.
-     *
-     * @param host the host to which the credentials apply
-     * @param realm the realm to which the credentials apply
-     * @param username the username
-     * @param password the password
-     * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead
-     */
-    @Deprecated
     public void setHttpAuthUsernamePassword(String host, String realm,
             String username, String password) {
-        checkThread();
-        mProvider.setHttpAuthUsernamePassword(host, realm, username, password);
     }
 
-    /**
-     * Retrieves HTTP authentication credentials for a given host and realm from the {@link
-     * WebViewDatabase} instance.
-     * @param host the host to which the credentials apply
-     * @param realm the realm to which the credentials apply
-     * @return the credentials as a String array, if found. The first element
-     *         is the username and the second element is the password. {@code null} if
-     *         no credentials are found.
-     * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead
-     */
-    @Deprecated
     public String[] getHttpAuthUsernamePassword(String host, String realm) {
-        checkThread();
-        return mProvider.getHttpAuthUsernamePassword(host, realm);
+        return null;
     }
 
-    /**
-     * Destroys the internal state of this WebView. This method should be called
-     * after this WebView has been removed from the view system. No other
-     * methods may be called on this WebView after destroy.
-     */
     public void destroy() {
-        checkThread();
-        mProvider.destroy();
     }
 
-    /**
-     * Enables platform notifications of data state and proxy changes.
-     * Notifications are enabled by default.
-     *
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
     public static void enablePlatformNotifications() {
-        // noop
     }
 
-    /**
-     * Disables platform notifications of data state and proxy changes.
-     * Notifications are enabled by default.
-     *
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
     public static void disablePlatformNotifications() {
-        // noop
     }
 
-    /**
-     * Used only by internal tests to free up memory.
-     *
-     * @hide
-     */
-    public static void freeMemoryForTests() {
-        getFactory().getStatics().freeMemoryForTests();
-    }
-
-    /**
-     * Informs WebView of the network state. This is used to set
-     * the JavaScript property window.navigator.isOnline and
-     * generates the online/offline event as specified in HTML5, sec. 5.7.7
-     *
-     * @param networkUp a boolean indicating if network is available
-     */
-    public void setNetworkAvailable(boolean networkUp) {
-        checkThread();
-        mProvider.setNetworkAvailable(networkUp);
-    }
-
-    /**
-     * Saves the state of this WebView used in
-     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
-     * method no longer stores the display data for this WebView. The previous
-     * behavior could potentially leak files if {@link #restoreState} was never
-     * called.
-     *
-     * @param outState the Bundle to store this WebView's state
-     * @return the same copy of the back/forward list used to save the state. If
-     *         saveState fails, the returned list will be {@code null}.
-     */
-    public WebBackForwardList saveState(Bundle outState) {
-        checkThread();
-        return mProvider.saveState(outState);
-    }
-
-    /**
-     * Saves the current display data to the Bundle given. Used in conjunction
-     * with {@link #saveState}.
-     * @param b a Bundle to store the display data
-     * @param dest the file to store the serialized picture data. Will be
-     *             overwritten with this WebView's picture data.
-     * @return {@code true} if the picture was successfully saved
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public boolean savePicture(Bundle b, final File dest) {
-        checkThread();
-        return mProvider.savePicture(b, dest);
-    }
-
-    /**
-     * Restores the display data that was saved in {@link #savePicture}. Used in
-     * conjunction with {@link #restoreState}. Note that this will not work if
-     * this WebView is hardware accelerated.
-     *
-     * @param b a Bundle containing the saved display data
-     * @param src the file where the picture data was stored
-     * @return {@code true} if the picture was successfully restored
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public boolean restorePicture(Bundle b, File src) {
-        checkThread();
-        return mProvider.restorePicture(b, src);
-    }
-
-    /**
-     * Restores the state of this WebView from the given Bundle. This method is
-     * intended for use in {@link android.app.Activity#onRestoreInstanceState}
-     * and should be called to restore the state of this WebView. If
-     * it is called after this WebView has had a chance to build state (load
-     * pages, create a back/forward list, etc.) there may be undesirable
-     * side-effects. Please note that this method no longer restores the
-     * display data for this WebView.
-     *
-     * @param inState the incoming Bundle of state
-     * @return the restored back/forward list or {@code null} if restoreState failed
-     */
-    public WebBackForwardList restoreState(Bundle inState) {
-        checkThread();
-        return mProvider.restoreState(inState);
-    }
-
-    /**
-     * Loads the given URL with the specified additional HTTP headers.
-     * <p>
-     * Also see compatibility note on {@link #evaluateJavascript}.
-     *
-     * @param url the URL of the resource to load
-     * @param additionalHttpHeaders the additional headers to be used in the
-     *            HTTP request for this URL, specified as a map from name to
-     *            value. Note that if this map contains any of the headers
-     *            that are set by default by this WebView, such as those
-     *            controlling caching, accept types or the User-Agent, their
-     *            values may be overridden by this WebView's defaults.
-     */
-    public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
-        checkThread();
-        mProvider.loadUrl(url, additionalHttpHeaders);
-    }
-
-    /**
-     * Loads the given URL.
-     * <p>
-     * Also see compatibility note on {@link #evaluateJavascript}.
-     *
-     * @param url the URL of the resource to load
-     */
     public void loadUrl(String url) {
-        checkThread();
-        mProvider.loadUrl(url);
     }
 
-    /**
-     * Loads the URL with postData using "POST" method into this WebView. If url
-     * is not a network URL, it will be loaded with {@link #loadUrl(String)}
-     * instead, ignoring the postData param.
-     *
-     * @param url the URL of the resource to load
-     * @param postData the data will be passed to "POST" request, which must be
-     *     be "application/x-www-form-urlencoded" encoded.
-     */
-    public void postUrl(String url, byte[] postData) {
-        checkThread();
-        if (URLUtil.isNetworkUrl(url)) {
-            mProvider.postUrl(url, postData);
-        } else {
-            mProvider.loadUrl(url);
-        }
-    }
-
-    /**
-     * Loads the given data into this WebView using a 'data' scheme URL.
-     * <p>
-     * Note that JavaScript's same origin policy means that script running in a
-     * page loaded using this method will be unable to access content loaded
-     * using any scheme other than 'data', including 'http(s)'. To avoid this
-     * restriction, use {@link
-     * #loadDataWithBaseURL(String,String,String,String,String)
-     * loadDataWithBaseURL()} with an appropriate base URL.
-     * <p>
-     * The encoding parameter specifies whether the data is base64 or URL
-     * encoded. If the data is base64 encoded, the value of the encoding
-     * parameter must be 'base64'. For all other values of the parameter,
-     * including {@code null}, it is assumed that the data uses ASCII encoding for
-     * octets inside the range of safe URL characters and use the standard %xx
-     * hex encoding of URLs for octets outside that range. For example, '#',
-     * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
-     * <p>
-     * The 'data' scheme URL formed by this method uses the default US-ASCII
-     * charset. If you need need to set a different charset, you should form a
-     * 'data' scheme URL which explicitly specifies a charset parameter in the
-     * mediatype portion of the URL and call {@link #loadUrl(String)} instead.
-     * Note that the charset obtained from the mediatype portion of a data URL
-     * always overrides that specified in the HTML or XML document itself.
-     *
-     * @param data a String of data in the given encoding
-     * @param mimeType the MIME type of the data, e.g. 'text/html'
-     * @param encoding the encoding of the data
-     */
     public void loadData(String data, String mimeType, String encoding) {
-        checkThread();
-        mProvider.loadData(data, mimeType, encoding);
     }
 
-    /**
-     * Loads the given data into this WebView, using baseUrl as the base URL for
-     * the content. The base URL is used both to resolve relative URLs and when
-     * applying JavaScript's same origin policy. The historyUrl is used for the
-     * history entry.
-     * <p>
-     * Note that content specified in this way can access local device files
-     * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
-     * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
-     * <p>
-     * If the base URL uses the data scheme, this method is equivalent to
-     * calling {@link #loadData(String,String,String) loadData()} and the
-     * historyUrl is ignored, and the data will be treated as part of a data: URL.
-     * If the base URL uses any other scheme, then the data will be loaded into
-     * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded
-     * entities in the string will not be decoded.
-     * <p>
-     * Note that the baseUrl is sent in the 'Referer' HTTP header when
-     * requesting subresources (images, etc.) of the page loaded using this method.
-     *
-     * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to
-     *                'about:blank'.
-     * @param data a String of data in the given encoding
-     * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
-     *                 defaults to 'text/html'.
-     * @param encoding the encoding of the data
-     * @param historyUrl the URL to use as the history entry. If {@code null} defaults
-     *                   to 'about:blank'. If non-null, this must be a valid URL.
-     */
     public void loadDataWithBaseURL(String baseUrl, String data,
-            String mimeType, String encoding, String historyUrl) {
-        checkThread();
-        mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
+            String mimeType, String encoding, String failUrl) {
     }
 
-    /**
-     * Asynchronously evaluates JavaScript in the context of the currently displayed page.
-     * If non-null, |resultCallback| will be invoked with any result returned from that
-     * execution. This method must be called on the UI thread and the callback will
-     * be made on the UI thread.
-     * <p>
-     * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or
-     * later, JavaScript state from an empty WebView is no longer persisted across navigations like
-     * {@link #loadUrl(String)}. For example, global variables and functions defined before calling
-     * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use
-     * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations.
-     *
-     * @param script the JavaScript to execute.
-     * @param resultCallback A callback to be invoked when the script execution
-     *                       completes with the result of the execution (if any).
-     *                       May be {@code null} if no notification of the result is required.
-     */
-    public void evaluateJavascript(String script, ValueCallback<String> resultCallback) {
-        checkThread();
-        mProvider.evaluateJavaScript(script, resultCallback);
-    }
-
-    /**
-     * Saves the current view as a web archive.
-     *
-     * @param filename the filename where the archive should be placed
-     */
-    public void saveWebArchive(String filename) {
-        checkThread();
-        mProvider.saveWebArchive(filename);
-    }
-
-    /**
-     * Saves the current view as a web archive.
-     *
-     * @param basename the filename where the archive should be placed
-     * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename
-     *                 is assumed to be a directory in which a filename will be
-     *                 chosen according to the URL of the current page.
-     * @param callback called after the web archive has been saved. The
-     *                 parameter for onReceiveValue will either be the filename
-     *                 under which the file was saved, or {@code null} if saving the
-     *                 file failed.
-     */
-    public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
-        checkThread();
-        mProvider.saveWebArchive(basename, autoname, callback);
-    }
-
-    /**
-     * Stops the current load.
-     */
     public void stopLoading() {
-        checkThread();
-        mProvider.stopLoading();
     }
 
-    /**
-     * Reloads the current URL.
-     */
     public void reload() {
-        checkThread();
-        mProvider.reload();
     }
 
-    /**
-     * Gets whether this WebView has a back history item.
-     *
-     * @return {@code true} iff this WebView has a back history item
-     */
     public boolean canGoBack() {
-        checkThread();
-        return mProvider.canGoBack();
+        return false;
     }
 
-    /**
-     * Goes back in the history of this WebView.
-     */
     public void goBack() {
-        checkThread();
-        mProvider.goBack();
     }
 
-    /**
-     * Gets whether this WebView has a forward history item.
-     *
-     * @return {@code true} iff this WebView has a forward history item
-     */
     public boolean canGoForward() {
-        checkThread();
-        return mProvider.canGoForward();
+        return false;
     }
 
-    /**
-     * Goes forward in the history of this WebView.
-     */
     public void goForward() {
-        checkThread();
-        mProvider.goForward();
     }
 
-    /**
-     * Gets whether the page can go back or forward the given
-     * number of steps.
-     *
-     * @param steps the negative or positive number of steps to move the
-     *              history
-     */
     public boolean canGoBackOrForward(int steps) {
-        checkThread();
-        return mProvider.canGoBackOrForward(steps);
+        return false;
     }
 
-    /**
-     * Goes to the history item that is the number of steps away from
-     * the current item. Steps is negative if backward and positive
-     * if forward.
-     *
-     * @param steps the number of steps to take back or forward in the back
-     *              forward list
-     */
     public void goBackOrForward(int steps) {
-        checkThread();
-        mProvider.goBackOrForward(steps);
     }
 
-    /**
-     * Gets whether private browsing is enabled in this WebView.
-     */
-    public boolean isPrivateBrowsingEnabled() {
-        checkThread();
-        return mProvider.isPrivateBrowsingEnabled();
-    }
-
-    /**
-     * Scrolls the contents of this WebView up by half the view size.
-     *
-     * @param top {@code true} to jump to the top of the page
-     * @return {@code true} if the page was scrolled
-     */
     public boolean pageUp(boolean top) {
-        checkThread();
-        return mProvider.pageUp(top);
+        return false;
     }
-
-    /**
-     * Scrolls the contents of this WebView down by half the page size.
-     *
-     * @param bottom {@code true} to jump to bottom of page
-     * @return {@code true} if the page was scrolled
-     */
+    
     public boolean pageDown(boolean bottom) {
-        checkThread();
-        return mProvider.pageDown(bottom);
+        return false;
     }
 
-    /**
-     * Posts a {@link VisualStateCallback}, which will be called when
-     * the current state of the WebView is ready to be drawn.
-     *
-     * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not
-     * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The
-     * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of
-     * the DOM at the current time are ready to be drawn the next time the {@link WebView}
-     * draws.
-     *
-     * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the
-     * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also
-     * contain updates applied after the callback was posted.
-     *
-     * <p>The state of the DOM covered by this API includes the following:
-     * <ul>
-     * <li>primitive HTML elements (div, img, span, etc..)</li>
-     * <li>images</li>
-     * <li>CSS animations</li>
-     * <li>WebGL</li>
-     * <li>canvas</li>
-     * </ul>
-     * It does not include the state of:
-     * <ul>
-     * <li>the video tag</li>
-     * </ul>
-     *
-     * <p>To guarantee that the {@link WebView} will successfully render the first frame
-     * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions
-     * must be met:
-     * <ul>
-     * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then
-     * the {@link WebView} must be attached to the view hierarchy.</li>
-     * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE}
-     * then the {@link WebView} must be attached to the view hierarchy and must be made
-     * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li>
-     * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the
-     * {@link WebView} must be attached to the view hierarchy and its
-     * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed
-     * values and must be made {@link View#VISIBLE VISIBLE} from the
-     * {@link VisualStateCallback#onComplete} method.</li>
-     * </ul>
-     *
-     * <p>When using this API it is also recommended to enable pre-rasterization if the {@link
-     * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for
-     * more details and do consider its caveats.
-     *
-     * @param requestId An id that will be returned in the callback to allow callers to match
-     *                  requests with callbacks.
-     * @param callback  The callback to be invoked.
-     */
-    public void postVisualStateCallback(long requestId, VisualStateCallback callback) {
-        checkThread();
-        mProvider.insertVisualStateCallback(requestId, callback);
-    }
-
-    /**
-     * Clears this WebView so that onDraw() will draw nothing but white background,
-     * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
-     * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
-     *             and release page resources (including any running JavaScript).
-     */
-    @Deprecated
     public void clearView() {
-        checkThread();
-        mProvider.clearView();
     }
-
-    /**
-     * Gets a new picture that captures the current contents of this WebView.
-     * The picture is of the entire document being displayed, and is not
-     * limited to the area currently displayed by this WebView. Also, the
-     * picture is a static copy and is unaffected by later changes to the
-     * content being displayed.
-     * <p>
-     * Note that due to internal changes, for API levels between
-     * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and
-     * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
-     * picture does not include fixed position elements or scrollable divs.
-     * <p>
-     * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture
-     * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve
-     * additional conversion at a cost in memory and performance. Also the
-     * {@link android.graphics.Picture#createFromStream} and
-     * {@link android.graphics.Picture#writeToStream} methods are not supported on the
-     * returned object.
-     *
-     * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or
-     * {@link #saveWebArchive} to save the content to a file.
-     *
-     * @return a picture that captures the current contents of this WebView
-     */
-    @Deprecated
+    
     public Picture capturePicture() {
-        checkThread();
-        return mProvider.capturePicture();
+        return null;
     }
 
-    /**
-     * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
-     *             to provide a print document name.
-     */
-    @Deprecated
-    public PrintDocumentAdapter createPrintDocumentAdapter() {
-        checkThread();
-        return mProvider.createPrintDocumentAdapter("default");
-    }
-
-    /**
-     * Creates a PrintDocumentAdapter that provides the content of this WebView for printing.
-     *
-     * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot
-     * be drawn during the conversion process - any such draws are undefined. It is recommended
-     * to use a dedicated off screen WebView for the printing. If necessary, an application may
-     * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
-     * wrapped around the object returned and observing the onStart and onFinish methods. See
-     * {@link android.print.PrintDocumentAdapter} for more information.
-     *
-     * @param documentName  The user-facing name of the printed document. See
-     *                      {@link android.print.PrintDocumentInfo}
-     */
-    public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
-        checkThread();
-        return mProvider.createPrintDocumentAdapter(documentName);
-    }
-
-    /**
-     * Gets the current scale of this WebView.
-     *
-     * @return the current scale
-     *
-     * @deprecated This method is prone to inaccuracy due to race conditions
-     * between the web rendering and UI threads; prefer
-     * {@link WebViewClient#onScaleChanged}.
-     */
-    @Deprecated
-    @ViewDebug.ExportedProperty(category = "webview")
     public float getScale() {
-        checkThread();
-        return mProvider.getScale();
+        return 0;
     }
 
-    /**
-     * Sets the initial scale for this WebView. 0 means default.
-     * The behavior for the default scale depends on the state of
-     * {@link WebSettings#getUseWideViewPort()} and
-     * {@link WebSettings#getLoadWithOverviewMode()}.
-     * If the content fits into the WebView control by width, then
-     * the zoom is set to 100%. For wide content, the behavior
-     * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}.
-     * If its value is {@code true}, the content will be zoomed out to be fit
-     * by width into the WebView control, otherwise not.
-     *
-     * If initial scale is greater than 0, WebView starts with this value
-     * as initial scale.
-     * Please note that unlike the scale properties in the viewport meta tag,
-     * this method doesn't take the screen density into account.
-     *
-     * @param scaleInPercent the initial scale in percent
-     */
     public void setInitialScale(int scaleInPercent) {
-        checkThread();
-        mProvider.setInitialScale(scaleInPercent);
     }
 
-    /**
-     * Invokes the graphical zoom picker widget for this WebView. This will
-     * result in the zoom widget appearing on the screen to control the zoom
-     * level of this WebView.
-     */
     public void invokeZoomPicker() {
-        checkThread();
-        mProvider.invokeZoomPicker();
     }
 
-    /**
-     * Gets a HitTestResult based on the current cursor node. If a HTML::a
-     * tag is found and the anchor has a non-JavaScript URL, the HitTestResult
-     * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field.
-     * If the anchor does not have a URL or if it is a JavaScript URL, the type
-     * will be UNKNOWN_TYPE and the URL has to be retrieved through
-     * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
-     * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in
-     * the "extra" field. A type of
-     * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as
-     * a child node. If a phone number is found, the HitTestResult type is set
-     * to PHONE_TYPE and the phone number is set in the "extra" field of
-     * HitTestResult. If a map address is found, the HitTestResult type is set
-     * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
-     * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
-     * and the email is set in the "extra" field of HitTestResult. Otherwise,
-     * HitTestResult type is set to UNKNOWN_TYPE.
-     */
-    public HitTestResult getHitTestResult() {
-        checkThread();
-        return mProvider.getHitTestResult();
-    }
-
-    /**
-     * Requests the anchor or image element URL at the last tapped point.
-     * If hrefMsg is {@code null}, this method returns immediately and does not
-     * dispatch hrefMsg to its target. If the tapped point hits an image,
-     * an anchor, or an image in an anchor, the message associates
-     * strings in named keys in its data. The value paired with the key
-     * may be an empty string.
-     *
-     * @param hrefMsg the message to be dispatched with the result of the
-     *                request. The message data contains three keys. "url"
-     *                returns the anchor's href attribute. "title" returns the
-     *                anchor's text. "src" returns the image's src attribute.
-     */
     public void requestFocusNodeHref(Message hrefMsg) {
-        checkThread();
-        mProvider.requestFocusNodeHref(hrefMsg);
     }
 
-    /**
-     * Requests the URL of the image last touched by the user. msg will be sent
-     * to its target with a String representing the URL as its object.
-     *
-     * @param msg the message to be dispatched with the result of the request
-     *            as the data member with "url" as key. The result can be {@code null}.
-     */
     public void requestImageRef(Message msg) {
-        checkThread();
-        mProvider.requestImageRef(msg);
     }
 
-    /**
-     * Gets the URL for the current page. This is not always the same as the URL
-     * passed to WebViewClient.onPageStarted because although the load for
-     * that URL has begun, the current page may not have changed.
-     *
-     * @return the URL for the current page
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
     public String getUrl() {
-        checkThread();
-        return mProvider.getUrl();
+        return null;
     }
 
-    /**
-     * Gets the original URL for the current page. This is not always the same
-     * as the URL passed to WebViewClient.onPageStarted because although the
-     * load for that URL has begun, the current page may not have changed.
-     * Also, there may have been redirects resulting in a different URL to that
-     * originally requested.
-     *
-     * @return the URL that was originally requested for the current page
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
-    public String getOriginalUrl() {
-        checkThread();
-        return mProvider.getOriginalUrl();
-    }
-
-    /**
-     * Gets the title for the current page. This is the title of the current page
-     * until WebViewClient.onReceivedTitle is called.
-     *
-     * @return the title for the current page
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
     public String getTitle() {
-        checkThread();
-        return mProvider.getTitle();
+        return null;
     }
 
-    /**
-     * Gets the favicon for the current page. This is the favicon of the current
-     * page until WebViewClient.onReceivedIcon is called.
-     *
-     * @return the favicon for the current page
-     */
     public Bitmap getFavicon() {
-        checkThread();
-        return mProvider.getFavicon();
+        return null;
     }
 
-    /**
-     * Gets the touch icon URL for the apple-touch-icon <link> element, or
-     * a URL on this site's server pointing to the standard location of a
-     * touch icon.
-     *
-     * @hide
-     */
-    public String getTouchIconUrl() {
-        return mProvider.getTouchIconUrl();
-    }
-
-    /**
-     * Gets the progress for the current page.
-     *
-     * @return the progress for the current page between 0 and 100
-     */
     public int getProgress() {
-        checkThread();
-        return mProvider.getProgress();
+        return 0;
     }
-
-    /**
-     * Gets the height of the HTML content.
-     *
-     * @return the height of the HTML content
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
+    
     public int getContentHeight() {
-        checkThread();
-        return mProvider.getContentHeight();
+        return 0;
     }
 
-    /**
-     * Gets the width of the HTML content.
-     *
-     * @return the width of the HTML content
-     * @hide
-     */
-    @ViewDebug.ExportedProperty(category = "webview")
-    public int getContentWidth() {
-        return mProvider.getContentWidth();
-    }
-
-    /**
-     * Pauses all layout, parsing, and JavaScript timers for all WebViews. This
-     * is a global requests, not restricted to just this WebView. This can be
-     * useful if the application has been paused.
-     */
     public void pauseTimers() {
-        checkThread();
-        mProvider.pauseTimers();
     }
 
-    /**
-     * Resumes all layout, parsing, and JavaScript timers for all WebViews.
-     * This will resume dispatching all timers.
-     */
     public void resumeTimers() {
-        checkThread();
-        mProvider.resumeTimers();
     }
 
-    /**
-     * Does a best-effort attempt to pause any processing that can be paused
-     * safely, such as animations and geolocation. Note that this call
-     * does not pause JavaScript. To pause JavaScript globally, use
-     * {@link #pauseTimers}.
-     *
-     * To resume WebView, call {@link #onResume}.
-     */
-    public void onPause() {
-        checkThread();
-        mProvider.onPause();
+    public void clearCache() {
     }
 
-    /**
-     * Resumes a WebView after a previous call to {@link #onPause}.
-     */
-    public void onResume() {
-        checkThread();
-        mProvider.onResume();
-    }
-
-    /**
-     * Gets whether this WebView is paused, meaning onPause() was called.
-     * Calling onResume() sets the paused state back to {@code false}.
-     *
-     * @hide
-     */
-    public boolean isPaused() {
-        return mProvider.isPaused();
-    }
-
-    /**
-     * Informs this WebView that memory is low so that it can free any available
-     * memory.
-     * @deprecated Memory caches are automatically dropped when no longer needed, and in response
-     *             to system memory pressure.
-     */
-    @Deprecated
-    public void freeMemory() {
-        checkThread();
-        mProvider.freeMemory();
-    }
-
-    /**
-     * Clears the resource cache. Note that the cache is per-application, so
-     * this will clear the cache for all WebViews used.
-     *
-     * @param includeDiskFiles if {@code false}, only the RAM cache is cleared
-     */
-    public void clearCache(boolean includeDiskFiles) {
-        checkThread();
-        mProvider.clearCache(includeDiskFiles);
-    }
-
-    /**
-     * Removes the autocomplete popup from the currently focused form field, if
-     * present. Note this only affects the display of the autocomplete popup,
-     * it does not remove any saved form data from this WebView's store. To do
-     * that, use {@link WebViewDatabase#clearFormData}.
-     */
     public void clearFormData() {
-        checkThread();
-        mProvider.clearFormData();
     }
 
-    /**
-     * Tells this WebView to clear its internal back/forward list.
-     */
     public void clearHistory() {
-        checkThread();
-        mProvider.clearHistory();
     }
 
-    /**
-     * Clears the SSL preferences table stored in response to proceeding with
-     * SSL certificate errors.
-     */
     public void clearSslPreferences() {
-        checkThread();
-        mProvider.clearSslPreferences();
     }
 
-    /**
-     * Clears the client certificate preferences stored in response
-     * to proceeding/cancelling client cert requests. Note that WebView
-     * automatically clears these preferences when it receives a
-     * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are
-     * shared by all the WebViews that are created by the embedder application.
-     *
-     * @param onCleared  A runnable to be invoked when client certs are cleared.
-     *                   The embedder can pass {@code null} if not interested in the
-     *                   callback. The runnable will be called in UI thread.
-     */
-    public static void clearClientCertPreferences(Runnable onCleared) {
-        getFactory().getStatics().clearClientCertPreferences(onCleared);
-    }
-
-    /**
-     * Starts Safe Browsing initialization.
-     * <p>
-     * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
-     * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
-     * devices {@code callback} will receive {@code false}.
-     * <p>
-     * This does not enable the Safe Browsing feature itself, and should only be called if Safe
-     * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
-     * prepares resources used for Safe Browsing.
-     * <p>
-     * This should be called with the Application Context (and will always use the Application
-     * context to do its work regardless).
-     *
-     * @param context Application Context.
-     * @param callback will be called on the UI thread with {@code true} if initialization is
-     * successful, {@code false} otherwise.
-     */
-    public static void startSafeBrowsing(Context context, ValueCallback<Boolean> callback) {
-        getFactory().getStatics().initSafeBrowsing(context, callback);
-    }
-
-    /**
-     * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
-     * global for all the WebViews.
-     * <p>
-     * Each rule should take one of these:
-     * <table>
-     * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
-     * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
-     * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
-     * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
-     * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
-     * </table>
-     * <p>
-     * All other rules, including wildcards, are invalid.
-     *
-     * @param urls the list of URLs
-     * @param callback will be called with {@code true} if URLs are successfully added to the
-     * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will
-     * be run on the UI thread
-     */
-    public static void setSafeBrowsingWhitelist(@NonNull List<String> urls,
-            @Nullable ValueCallback<Boolean> callback) {
-        getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback);
-    }
-
-    /**
-     * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
-     *
-     * @return the url pointing to a privacy policy document which can be displayed to users.
-     */
-    @NonNull
-    public static Uri getSafeBrowsingPrivacyPolicyUrl() {
-        return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
-    }
-
-    /**
-     * Gets the WebBackForwardList for this WebView. This contains the
-     * back/forward list for use in querying each item in the history stack.
-     * This is a copy of the private WebBackForwardList so it contains only a
-     * snapshot of the current state. Multiple calls to this method may return
-     * different objects. The object returned from this method will not be
-     * updated to reflect any new state.
-     */
-    public WebBackForwardList copyBackForwardList() {
-        checkThread();
-        return mProvider.copyBackForwardList();
-
-    }
-
-    /**
-     * Registers the listener to be notified as find-on-page operations
-     * progress. This will replace the current listener.
-     *
-     * @param listener an implementation of {@link FindListener}
-     */
-    public void setFindListener(FindListener listener) {
-        checkThread();
-        setupFindListenerIfNeeded();
-        mFindListener.mUserFindListener = listener;
-    }
-
-    /**
-     * Highlights and scrolls to the next match found by
-     * {@link #findAllAsync}, wrapping around page boundaries as necessary.
-     * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
-     * has not been called yet, or if {@link #clearMatches} has been called since the
-     * last find operation, this function does nothing.
-     *
-     * @param forward the direction to search
-     * @see #setFindListener
-     */
-    public void findNext(boolean forward) {
-        checkThread();
-        mProvider.findNext(forward);
-    }
-
-    /**
-     * Finds all instances of find on the page and highlights them.
-     * Notifies any registered {@link FindListener}.
-     *
-     * @param find the string to find
-     * @return the number of occurrences of the String "find" that were found
-     * @deprecated {@link #findAllAsync} is preferred.
-     * @see #setFindListener
-     */
-    @Deprecated
-    public int findAll(String find) {
-        checkThread();
-        StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
-        return mProvider.findAll(find);
-    }
-
-    /**
-     * Finds all instances of find on the page and highlights them,
-     * asynchronously. Notifies any registered {@link FindListener}.
-     * Successive calls to this will cancel any pending searches.
-     *
-     * @param find the string to find.
-     * @see #setFindListener
-     */
-    public void findAllAsync(String find) {
-        checkThread();
-        mProvider.findAllAsync(find);
-    }
-
-    /**
-     * Starts an ActionMode for finding text in this WebView.  Only works if this
-     * WebView is attached to the view system.
-     *
-     * @param text if non-null, will be the initial text to search for.
-     *             Otherwise, the last String searched for in this WebView will
-     *             be used to start.
-     * @param showIme if {@code true}, show the IME, assuming the user will begin typing.
-     *                If {@code false} and text is non-null, perform a find all.
-     * @return {@code true} if the find dialog is shown, {@code false} otherwise
-     * @deprecated This method does not work reliably on all Android versions;
-     *             implementing a custom find dialog using WebView.findAllAsync()
-     *             provides a more robust solution.
-     */
-    @Deprecated
-    public boolean showFindDialog(String text, boolean showIme) {
-        checkThread();
-        return mProvider.showFindDialog(text, showIme);
-    }
-
-    /**
-     * Gets the first substring consisting of the address of a physical
-     * location. Currently, only addresses in the United States are detected,
-     * and consist of:
-     * <ul>
-     *   <li>a house number</li>
-     *   <li>a street name</li>
-     *   <li>a street type (Road, Circle, etc), either spelled out or
-     *       abbreviated</li>
-     *   <li>a city name</li>
-     *   <li>a state or territory, either spelled out or two-letter abbr</li>
-     *   <li>an optional 5 digit or 9 digit zip code</li>
-     * </ul>
-     * All names must be correctly capitalized, and the zip code, if present,
-     * must be valid for the state. The street type must be a standard USPS
-     * spelling or abbreviation. The state or territory must also be spelled
-     * or abbreviated using USPS standards. The house number may not exceed
-     * five digits.
-     *
-     * @param addr the string to search for addresses
-     * @return the address, or if no address is found, {@code null}
-     */
     public static String findAddress(String addr) {
-        // TODO: Rewrite this in Java so it is not needed to start up chromium
-        // Could also be deprecated
-        return getFactory().getStatics().findAddress(addr);
+        return null;
     }
 
-    /**
-     * For apps targeting the L release, WebView has a new default behavior that reduces
-     * memory footprint and increases performance by intelligently choosing
-     * the portion of the HTML document that needs to be drawn. These
-     * optimizations are transparent to the developers. However, under certain
-     * circumstances, an App developer may want to disable them:
-     * <ol>
-     *   <li>When an app uses {@link #onDraw} to do own drawing and accesses portions
-     *       of the page that is way outside the visible portion of the page.</li>
-     *   <li>When an app uses {@link #capturePicture} to capture a very large HTML document.
-     *       Note that capturePicture is a deprecated API.</li>
-     * </ol>
-     * Enabling drawing the entire HTML document has a significant performance
-     * cost. This method should be called before any WebViews are created.
-     */
-    public static void enableSlowWholeDocumentDraw() {
-        getFactory().getStatics().enableSlowWholeDocumentDraw();
-    }
-
-    /**
-     * Clears the highlighting surrounding text matches created by
-     * {@link #findAllAsync}.
-     */
-    public void clearMatches() {
-        checkThread();
-        mProvider.clearMatches();
-    }
-
-    /**
-     * Queries the document to see if it contains any image references. The
-     * message object will be dispatched with arg1 being set to 1 if images
-     * were found and 0 if the document does not reference any images.
-     *
-     * @param response the message that will be dispatched with the result
-     */
     public void documentHasImages(Message response) {
-        checkThread();
-        mProvider.documentHasImages(response);
     }
 
-    /**
-     * Sets the WebViewClient that will receive various notifications and
-     * requests. This will replace the current handler.
-     *
-     * @param client an implementation of WebViewClient
-     * @see #getWebViewClient
-     */
     public void setWebViewClient(WebViewClient client) {
-        checkThread();
-        mProvider.setWebViewClient(client);
     }
 
-    /**
-     * Gets the WebViewClient.
-     *
-     * @return the WebViewClient, or a default client if not yet set
-     * @see #setWebViewClient
-     */
-    public WebViewClient getWebViewClient() {
-        checkThread();
-        return mProvider.getWebViewClient();
-    }
-
-    /**
-     * Registers the interface to be used when content can not be handled by
-     * the rendering engine, and should be downloaded instead. This will replace
-     * the current handler.
-     *
-     * @param listener an implementation of DownloadListener
-     */
     public void setDownloadListener(DownloadListener listener) {
-        checkThread();
-        mProvider.setDownloadListener(listener);
     }
 
-    /**
-     * Sets the chrome handler. This is an implementation of WebChromeClient for
-     * use in handling JavaScript dialogs, favicons, titles, and the progress.
-     * This will replace the current handler.
-     *
-     * @param client an implementation of WebChromeClient
-     * @see #getWebChromeClient
-     */
     public void setWebChromeClient(WebChromeClient client) {
-        checkThread();
-        mProvider.setWebChromeClient(client);
     }
 
-    /**
-     * Gets the chrome handler.
-     *
-     * @return the WebChromeClient, or {@code null} if not yet set
-     * @see #setWebChromeClient
-     */
-    public WebChromeClient getWebChromeClient() {
-        checkThread();
-        return mProvider.getWebChromeClient();
+    public void addJavascriptInterface(Object obj, String interfaceName) {
     }
 
-    /**
-     * Sets the Picture listener. This is an interface used to receive
-     * notifications of a new Picture.
-     *
-     * @param listener an implementation of WebView.PictureListener
-     * @deprecated This method is now obsolete.
-     */
-    @Deprecated
-    public void setPictureListener(PictureListener listener) {
-        checkThread();
-        mProvider.setPictureListener(listener);
-    }
-
-    /**
-     * Injects the supplied Java object into this WebView. The object is
-     * injected into the JavaScript context of the main frame, using the
-     * supplied name. This allows the Java object's methods to be
-     * accessed from JavaScript. For applications targeted to API
-     * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     * and above, only public methods that are annotated with
-     * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
-     * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
-     * all public methods (including the inherited ones) can be accessed, see the
-     * important security note below for implications.
-     * <p> Note that injected objects will not appear in JavaScript until the page is next
-     * (re)loaded. JavaScript should be enabled before injecting the object. For example:
-     * <pre>
-     * class JsObject {
-     *    {@literal @}JavascriptInterface
-     *    public String toString() { return "injectedObject"; }
-     * }
-     * webview.getSettings().setJavaScriptEnabled(true);
-     * webView.addJavascriptInterface(new JsObject(), "injectedObject");
-     * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
-     * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
-     * <p>
-     * <strong>IMPORTANT:</strong>
-     * <ul>
-     * <li> This method can be used to allow JavaScript to control the host
-     * application. This is a powerful feature, but also presents a security
-     * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier.
-     * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
-     * are still vulnerable if the app runs on a device running Android earlier than 4.2.
-     * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     * and to ensure the method is called only when running on Android 4.2 or later.
-     * With these older versions, JavaScript could use reflection to access an
-     * injected object's public fields. Use of this method in a WebView
-     * containing untrusted content could allow an attacker to manipulate the
-     * host application in unintended ways, executing Java code with the
-     * permissions of the host application. Use extreme care when using this
-     * method in a WebView which could contain untrusted content.</li>
-     * <li> JavaScript interacts with Java object on a private, background
-     * thread of this WebView. Care is therefore required to maintain thread
-     * safety.
-     * </li>
-     * <li> The Java object's fields are not accessible.</li>
-     * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
-     * and above, methods of injected Java objects are enumerable from
-     * JavaScript.</li>
-     * </ul>
-     *
-     * @param object the Java object to inject into this WebView's JavaScript
-     *               context. {@code null} values are ignored.
-     * @param name the name used to expose the object in JavaScript
-     */
-    public void addJavascriptInterface(Object object, String name) {
-        checkThread();
-        mProvider.addJavascriptInterface(object, name);
-    }
-
-    /**
-     * Removes a previously injected Java object from this WebView. Note that
-     * the removal will not be reflected in JavaScript until the page is next
-     * (re)loaded. See {@link #addJavascriptInterface}.
-     *
-     * @param name the name used to expose the object in JavaScript
-     */
-    public void removeJavascriptInterface(String name) {
-        checkThread();
-        mProvider.removeJavascriptInterface(name);
-    }
-
-    /**
-     * Creates a message channel to communicate with JS and returns the message
-     * ports that represent the endpoints of this message channel. The HTML5 message
-     * channel functionality is described
-     * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
-     * </a>
-     *
-     * <p>The returned message channels are entangled and already in started state.
-     *
-     * @return the two message ports that form the message channel.
-     */
-    public WebMessagePort[] createWebMessageChannel() {
-        checkThread();
-        return mProvider.createWebMessageChannel();
-    }
-
-    /**
-     * Post a message to main frame. The embedded application can restrict the
-     * messages to a certain target origin. See
-     * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
-     * HTML5 spec</a> for how target origin can be used.
-     * <p>
-     * A target origin can be set as a wildcard ("*"). However this is not recommended.
-     * See the page above for security issues.
-     *
-     * @param message the WebMessage
-     * @param targetOrigin the target origin.
-     */
-    public void postWebMessage(WebMessage message, Uri targetOrigin) {
-        checkThread();
-        mProvider.postMessageToMainFrame(message, targetOrigin);
-    }
-
-    /**
-     * Gets the WebSettings object used to control the settings for this
-     * WebView.
-     *
-     * @return a WebSettings object that can be used to control this WebView's
-     *         settings
-     */
-    public WebSettings getSettings() {
-        checkThread();
-        return mProvider.getSettings();
-    }
-
-    /**
-     * Enables debugging of web contents (HTML / CSS / JavaScript)
-     * loaded into any WebViews of this application. This flag can be enabled
-     * in order to facilitate debugging of web layouts and JavaScript
-     * code running inside WebViews. Please refer to WebView documentation
-     * for the debugging guide.
-     *
-     * The default is {@code false}.
-     *
-     * @param enabled whether to enable web contents debugging
-     */
-    public static void setWebContentsDebuggingEnabled(boolean enabled) {
-        getFactory().getStatics().setWebContentsDebuggingEnabled(enabled);
-    }
-
-    /**
-     * Gets the list of currently loaded plugins.
-     *
-     * @return the list of currently loaded plugins
-     * @deprecated This was used for Gears, which has been deprecated.
-     * @hide
-     */
-    @Deprecated
-    public static synchronized PluginList getPluginList() {
-        return new PluginList();
-    }
-
-    /**
-     * @deprecated This was used for Gears, which has been deprecated.
-     * @hide
-     */
-    @Deprecated
-    public void refreshPlugins(boolean reloadOpenPages) {
-        checkThread();
-    }
-
-    /**
-     * Puts this WebView into text selection mode. Do not rely on this
-     * functionality; it will be deprecated in the future.
-     *
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public void emulateShiftHeld() {
-        checkThread();
-    }
-
-    /**
-     * @deprecated WebView no longer needs to implement
-     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
-     */
-    @Override
-    // Cannot add @hide as this can always be accessed via the interface.
-    @Deprecated
-    public void onChildViewAdded(View parent, View child) {}
-
-    /**
-     * @deprecated WebView no longer needs to implement
-     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
-     */
-    @Override
-    // Cannot add @hide as this can always be accessed via the interface.
-    @Deprecated
-    public void onChildViewRemoved(View p, View child) {}
-
-    /**
-     * @deprecated WebView should not have implemented
-     * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now.
-     */
-    @Override
-    // Cannot add @hide as this can always be accessed via the interface.
-    @Deprecated
-    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
-    }
-
-    /**
-     * @deprecated Only the default case, {@code true}, will be supported in a future version.
-     */
-    @Deprecated
-    public void setMapTrackballToArrowKeys(boolean setMap) {
-        checkThread();
-        mProvider.setMapTrackballToArrowKeys(setMap);
-    }
-
-
-    public void flingScroll(int vx, int vy) {
-        checkThread();
-        mProvider.flingScroll(vx, vy);
-    }
-
-    /**
-     * Gets the zoom controls for this WebView, as a separate View. The caller
-     * is responsible for inserting this View into the layout hierarchy.
-     * <p/>
-     * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced
-     * built-in zoom mechanisms for the WebView, as opposed to these separate
-     * zoom controls. The built-in mechanisms are preferred and can be enabled
-     * using {@link WebSettings#setBuiltInZoomControls}.
-     *
-     * @deprecated the built-in zoom mechanisms are preferred
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
-     */
-    @Deprecated
     public View getZoomControls() {
-        checkThread();
-        return mProvider.getZoomControls();
+        return null;
     }
 
-    /**
-     * Gets whether this WebView can be zoomed in.
-     *
-     * @return {@code true} if this WebView can be zoomed in
-     *
-     * @deprecated This method is prone to inaccuracy due to race conditions
-     * between the web rendering and UI threads; prefer
-     * {@link WebViewClient#onScaleChanged}.
-     */
-    @Deprecated
-    public boolean canZoomIn() {
-        checkThread();
-        return mProvider.canZoomIn();
-    }
-
-    /**
-     * Gets whether this WebView can be zoomed out.
-     *
-     * @return {@code true} if this WebView can be zoomed out
-     *
-     * @deprecated This method is prone to inaccuracy due to race conditions
-     * between the web rendering and UI threads; prefer
-     * {@link WebViewClient#onScaleChanged}.
-     */
-    @Deprecated
-    public boolean canZoomOut() {
-        checkThread();
-        return mProvider.canZoomOut();
-    }
-
-    /**
-     * Performs a zoom operation in this WebView.
-     *
-     * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's
-     * zoom limits. This value must be in the range 0.01 to 100.0 inclusive.
-     */
-    public void zoomBy(float zoomFactor) {
-        checkThread();
-        if (zoomFactor < 0.01)
-            throw new IllegalArgumentException("zoomFactor must be greater than 0.01.");
-        if (zoomFactor > 100.0)
-            throw new IllegalArgumentException("zoomFactor must be less than 100.");
-        mProvider.zoomBy(zoomFactor);
-    }
-
-    /**
-     * Performs zoom in in this WebView.
-     *
-     * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes
-     */
     public boolean zoomIn() {
-        checkThread();
-        return mProvider.zoomIn();
+        return false;
     }
 
-    /**
-     * Performs zoom out in this WebView.
-     *
-     * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
-     */
     public boolean zoomOut() {
-        checkThread();
-        return mProvider.zoomOut();
-    }
-
-    /**
-     * @deprecated This method is now obsolete.
-     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
-     */
-    @Deprecated
-    public void debugDump() {
-        checkThread();
-    }
-
-    /**
-     * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)}
-     * @hide
-     */
-    @Override
-    public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
-        mProvider.dumpViewHierarchyWithProperties(out, level);
-    }
-
-    /**
-     * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)}
-     * @hide
-     */
-    @Override
-    public View findHierarchyView(String className, int hashCode) {
-        return mProvider.findHierarchyView(className, hashCode);
-    }
-
-    /** @hide */
-    @IntDef({
-        RENDERER_PRIORITY_WAIVED,
-        RENDERER_PRIORITY_BOUND,
-        RENDERER_PRIORITY_IMPORTANT
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RendererPriority {}
-
-    /**
-     * The renderer associated with this WebView is bound with
-     * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
-     * {@link WebView} renderers will be strong targets for out of memory
-     * killing.
-     *
-     * Use with {@link #setRendererPriorityPolicy}.
-     */
-    public static final int RENDERER_PRIORITY_WAIVED = 0;
-    /**
-     * The renderer associated with this WebView is bound with
-     * the default priority for services.
-     *
-     * Use with {@link #setRendererPriorityPolicy}.
-     */
-    public static final int RENDERER_PRIORITY_BOUND = 1;
-    /**
-     * The renderer associated with this WebView is bound with
-     * {@link Context#BIND_IMPORTANT}.
-     *
-     * Use with {@link #setRendererPriorityPolicy}.
-     */
-    public static final int RENDERER_PRIORITY_IMPORTANT = 2;
-
-    /**
-     * Set the renderer priority policy for this {@link WebView}. The
-     * priority policy will be used to determine whether an out of
-     * process renderer should be considered to be a target for OOM
-     * killing.
-     *
-     * Because a renderer can be associated with more than one
-     * WebView, the final priority it is computed as the maximum of
-     * any attached WebViews. When a WebView is destroyed it will
-     * cease to be considerered when calculating the renderer
-     * priority. Once no WebViews remain associated with the renderer,
-     * the priority of the renderer will be reduced to
-     * {@link #RENDERER_PRIORITY_WAIVED}.
-     *
-     * The default policy is to set the priority to
-     * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
-     * and this should not be changed unless the caller also handles
-     * renderer crashes with
-     * {@link WebViewClient#onRenderProcessGone}. Any other setting
-     * will result in WebView renderers being killed by the system
-     * more aggressively than the application.
-     *
-     * @param rendererRequestedPriority the minimum priority at which
-     *        this WebView desires the renderer process to be bound.
-     * @param waivedWhenNotVisible if {@code true}, this flag specifies that
-     *        when this WebView is not visible, it will be treated as
-     *        if it had requested a priority of
-     *        {@link #RENDERER_PRIORITY_WAIVED}.
-     */
-    public void setRendererPriorityPolicy(
-            @RendererPriority int rendererRequestedPriority,
-            boolean waivedWhenNotVisible) {
-        mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
-    }
-
-    /**
-     * Get the requested renderer priority for this WebView.
-     *
-     * @return the requested renderer priority policy.
-     */
-    @RendererPriority
-    public int getRendererRequestedPriority() {
-        return mProvider.getRendererRequestedPriority();
-    }
-
-    /**
-     * Return whether this WebView requests a priority of
-     * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
-     *
-     * @return whether this WebView requests a priority of
-     * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
-     */
-    public boolean getRendererPriorityWaivedWhenNotVisible() {
-        return mProvider.getRendererPriorityWaivedWhenNotVisible();
-    }
-
-    /**
-     * Sets the {@link TextClassifier} for this WebView.
-     */
-    public void setTextClassifier(@Nullable TextClassifier textClassifier) {
-        mProvider.setTextClassifier(textClassifier);
-    }
-
-    /**
-     * Returns the {@link TextClassifier} used by this WebView.
-     * If no TextClassifier has been set, this WebView uses the default set by the system.
-     */
-    @NonNull
-    public TextClassifier getTextClassifier() {
-        return mProvider.getTextClassifier();
-    }
-
-    //-------------------------------------------------------------------------
-    // Interface for WebView providers
-    //-------------------------------------------------------------------------
-
-    /**
-     * Gets the WebViewProvider. Used by providers to obtain the underlying
-     * implementation, e.g. when the application responds to
-     * WebViewClient.onCreateWindow() request.
-     *
-     * @hide WebViewProvider is not public API.
-     */
-    @SystemApi
-    public WebViewProvider getWebViewProvider() {
-        return mProvider;
-    }
-
-    /**
-     * Callback interface, allows the provider implementation to access non-public methods
-     * and fields, and make super-class calls in this WebView instance.
-     * @hide Only for use by WebViewProvider implementations
-     */
-    @SystemApi
-    public class PrivateAccess {
-        // ---- Access to super-class methods ----
-        public int super_getScrollBarStyle() {
-            return WebView.super.getScrollBarStyle();
-        }
-
-        public void super_scrollTo(int scrollX, int scrollY) {
-            WebView.super.scrollTo(scrollX, scrollY);
-        }
-
-        public void super_computeScroll() {
-            WebView.super.computeScroll();
-        }
-
-        public boolean super_onHoverEvent(MotionEvent event) {
-            return WebView.super.onHoverEvent(event);
-        }
-
-        public boolean super_performAccessibilityAction(int action, Bundle arguments) {
-            return WebView.super.performAccessibilityActionInternal(action, arguments);
-        }
-
-        public boolean super_performLongClick() {
-            return WebView.super.performLongClick();
-        }
-
-        public boolean super_setFrame(int left, int top, int right, int bottom) {
-            return WebView.super.setFrame(left, top, right, bottom);
-        }
-
-        public boolean super_dispatchKeyEvent(KeyEvent event) {
-            return WebView.super.dispatchKeyEvent(event);
-        }
-
-        public boolean super_onGenericMotionEvent(MotionEvent event) {
-            return WebView.super.onGenericMotionEvent(event);
-        }
-
-        public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) {
-            return WebView.super.requestFocus(direction, previouslyFocusedRect);
-        }
-
-        public void super_setLayoutParams(ViewGroup.LayoutParams params) {
-            WebView.super.setLayoutParams(params);
-        }
-
-        public void super_startActivityForResult(Intent intent, int requestCode) {
-            WebView.super.startActivityForResult(intent, requestCode);
-        }
-
-        // ---- Access to non-public methods ----
-        public void overScrollBy(int deltaX, int deltaY,
-                int scrollX, int scrollY,
-                int scrollRangeX, int scrollRangeY,
-                int maxOverScrollX, int maxOverScrollY,
-                boolean isTouchEvent) {
-            WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
-                    maxOverScrollX, maxOverScrollY, isTouchEvent);
-        }
-
-        public void awakenScrollBars(int duration) {
-            WebView.this.awakenScrollBars(duration);
-        }
-
-        public void awakenScrollBars(int duration, boolean invalidate) {
-            WebView.this.awakenScrollBars(duration, invalidate);
-        }
-
-        public float getVerticalScrollFactor() {
-            return WebView.this.getVerticalScrollFactor();
-        }
-
-        public float getHorizontalScrollFactor() {
-            return WebView.this.getHorizontalScrollFactor();
-        }
-
-        public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
-            WebView.this.setMeasuredDimension(measuredWidth, measuredHeight);
-        }
-
-        public void onScrollChanged(int l, int t, int oldl, int oldt) {
-            WebView.this.onScrollChanged(l, t, oldl, oldt);
-        }
-
-        public int getHorizontalScrollbarHeight() {
-            return WebView.this.getHorizontalScrollbarHeight();
-        }
-
-        public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
-                int l, int t, int r, int b) {
-            WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
-        }
-
-        // ---- Access to (non-public) fields ----
-        /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */
-        public void setScrollXRaw(int scrollX) {
-            WebView.this.mScrollX = scrollX;
-        }
-
-        /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */
-        public void setScrollYRaw(int scrollY) {
-            WebView.this.mScrollY = scrollY;
-        }
-
-    }
-
-    //-------------------------------------------------------------------------
-    // Package-private internal stuff
-    //-------------------------------------------------------------------------
-
-    // Only used by android.webkit.FindActionModeCallback.
-    void setFindDialogFindListener(FindListener listener) {
-        checkThread();
-        setupFindListenerIfNeeded();
-        mFindListener.mFindDialogFindListener = listener;
-    }
-
-    // Only used by android.webkit.FindActionModeCallback.
-    void notifyFindDialogDismissed() {
-        checkThread();
-        mProvider.notifyFindDialogDismissed();
-    }
-
-    //-------------------------------------------------------------------------
-    // Private internal stuff
-    //-------------------------------------------------------------------------
-
-    private WebViewProvider mProvider;
-
-    /**
-     * In addition to the FindListener that the user may set via the WebView.setFindListener
-     * API, FindActionModeCallback will register it's own FindListener. We keep them separate
-     * via this class so that the two FindListeners can potentially exist at once.
-     */
-    private class FindListenerDistributor implements FindListener {
-        private FindListener mFindDialogFindListener;
-        private FindListener mUserFindListener;
-
-        @Override
-        public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
-                boolean isDoneCounting) {
-            if (mFindDialogFindListener != null) {
-                mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
-                        isDoneCounting);
-            }
-
-            if (mUserFindListener != null) {
-                mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
-                        isDoneCounting);
-            }
-        }
-    }
-    private FindListenerDistributor mFindListener;
-
-    private void setupFindListenerIfNeeded() {
-        if (mFindListener == null) {
-            mFindListener = new FindListenerDistributor();
-            mProvider.setFindListener(mFindListener);
-        }
-    }
-
-    private void ensureProviderCreated() {
-        checkThread();
-        if (mProvider == null) {
-            // As this can get called during the base class constructor chain, pass the minimum
-            // number of dependencies here; the rest are deferred to init().
-            mProvider = getFactory().createWebView(this, new PrivateAccess());
-        }
-    }
-
-    private static WebViewFactoryProvider getFactory() {
-        return WebViewFactory.getProvider();
-    }
-
-    private final Looper mWebViewThread = Looper.myLooper();
-
-    private void checkThread() {
-        // Ignore mWebViewThread == null because this can be called during in the super class
-        // constructor, before this class's own constructor has even started.
-        if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
-            Throwable throwable = new Throwable(
-                    "A WebView method was called on thread '" +
-                    Thread.currentThread().getName() + "'. " +
-                    "All WebView methods must be called on the same thread. " +
-                    "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
-                    ", FYI main Looper is " + Looper.getMainLooper() + ")");
-            Log.w(LOGTAG, Log.getStackTraceString(throwable));
-            StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
-
-            if (sEnforceThreadChecking) {
-                throw new RuntimeException(throwable);
-            }
-        }
-    }
-
-    //-------------------------------------------------------------------------
-    // Override View methods
-    //-------------------------------------------------------------------------
-
-    // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures
-    // there's a corresponding override (or better, caller) for each of them in here.
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mProvider.getViewDelegate().onAttachedToWindow();
-    }
-
-    /** @hide */
-    @Override
-    protected void onDetachedFromWindowInternal() {
-        mProvider.getViewDelegate().onDetachedFromWindow();
-        super.onDetachedFromWindowInternal();
-    }
-
-    /** @hide */
-    @Override
-    public void onMovedToDisplay(int displayId, Configuration config) {
-        mProvider.getViewDelegate().onMovedToDisplay(displayId, config);
-    }
-
-    @Override
-    public void setLayoutParams(ViewGroup.LayoutParams params) {
-        mProvider.getViewDelegate().setLayoutParams(params);
-    }
-
-    @Override
-    public void setOverScrollMode(int mode) {
-        super.setOverScrollMode(mode);
-        // This method may be called in the constructor chain, before the WebView provider is
-        // created.
-        ensureProviderCreated();
-        mProvider.getViewDelegate().setOverScrollMode(mode);
-    }
-
-    @Override
-    public void setScrollBarStyle(int style) {
-        mProvider.getViewDelegate().setScrollBarStyle(style);
-        super.setScrollBarStyle(style);
-    }
-
-    @Override
-    protected int computeHorizontalScrollRange() {
-        return mProvider.getScrollDelegate().computeHorizontalScrollRange();
-    }
-
-    @Override
-    protected int computeHorizontalScrollOffset() {
-        return mProvider.getScrollDelegate().computeHorizontalScrollOffset();
-    }
-
-    @Override
-    protected int computeVerticalScrollRange() {
-        return mProvider.getScrollDelegate().computeVerticalScrollRange();
-    }
-
-    @Override
-    protected int computeVerticalScrollOffset() {
-        return mProvider.getScrollDelegate().computeVerticalScrollOffset();
-    }
-
-    @Override
-    protected int computeVerticalScrollExtent() {
-        return mProvider.getScrollDelegate().computeVerticalScrollExtent();
-    }
-
-    @Override
-    public void computeScroll() {
-        mProvider.getScrollDelegate().computeScroll();
-    }
-
-    @Override
-    public boolean onHoverEvent(MotionEvent event) {
-        return mProvider.getViewDelegate().onHoverEvent(event);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        return mProvider.getViewDelegate().onTouchEvent(event);
-    }
-
-    @Override
-    public boolean onGenericMotionEvent(MotionEvent event) {
-        return mProvider.getViewDelegate().onGenericMotionEvent(event);
-    }
-
-    @Override
-    public boolean onTrackballEvent(MotionEvent event) {
-        return mProvider.getViewDelegate().onTrackballEvent(event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyDown(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyUp(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event);
-    }
-
-    /*
-    TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not
-    to be delegating them too.
-
-    @Override
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyPreIme(keyCode, event);
-    }
-    @Override
-    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyLongPress(keyCode, event);
-    }
-    @Override
-    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
-        return mProvider.getViewDelegate().onKeyShortcut(keyCode, event);
-    }
-    */
-
-    @Override
-    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
-        AccessibilityNodeProvider provider =
-                mProvider.getViewDelegate().getAccessibilityNodeProvider();
-        return provider == null ? super.getAccessibilityNodeProvider() : provider;
-    }
-
-    @Deprecated
-    @Override
-    public boolean shouldDelayChildPressedState() {
-        return mProvider.getViewDelegate().shouldDelayChildPressedState();
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        return WebView.class.getName();
-    }
-
-    @Override
-    public void onProvideVirtualStructure(ViewStructure structure) {
-        mProvider.getViewDelegate().onProvideVirtualStructure(structure);
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
-     * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
-     * understood by the {@link android.service.autofill.AutofillService} implementations:
-     *
-     * <ol>
-     *   <li>Only the HTML nodes inside a {@code FORM} are generated.
-     *   <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the
-     *   node representing the WebView.
-     *   <li>If a web page has multiple {@code FORM}s, only the data for the current form is
-     *   represented&mdash;if the user taps a field from another form, then the current autofill
-     *   context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and
-     *   a new context is created for that {@code FORM}.
-     *   <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in
-     *   the view structure until the user taps a field from a {@code FORM} inside the
-     *   {@code IFRAME}, in which case it would be treated the same way as multiple forms described
-     *   above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the
-     *   {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node.
-     *   <li>If the Android SDK provides a similar View, then should be set with the
-     *   fully-qualified class name of such view.
-     *   <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
-     *       {@link ViewStructure#setAutofillHints(String[])}.
-     *   <li>The {@code type} attribute of {@code INPUT} tags maps to
-     *       {@link ViewStructure#setInputType(int)}.
-     *   <li>The {@code value} attribute of {@code INPUT} tags maps to
-     *       {@link ViewStructure#setText(CharSequence)}.
-     *   <li>If the view is editalbe, the {@link ViewStructure#setAutofillType(int)} and
-     *   {@link ViewStructure#setAutofillValue(AutofillValue)} must be set.
-     *   <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
-     *   <li>Other HTML attributes can be represented through
-     *   {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}.
-     * </ol>
-     *
-     * <p>It should also call {@code structure.setDataIsSensitive(false)} for fields whose value
-     * were not dynamically changed (for example, through Javascript).
-     *
-     * <p>Example1: an HTML form with 2 fields for username and password.
-     *
-     * <pre class="prettyprint">
-     *    &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
-     *    &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
-     * </pre>
-     *
-     * <p>Would map to:
-     *
-     * <pre class="prettyprint">
-     *     int index = structure.addChildCount(2);
-     *     ViewStructure username = structure.newChild(index);
-     *     username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child
-     *     username.setClassName("input");
-     *     username.setInputType("android.widget.EditText");
-     *     username.setAutofillHints("username");
-     *     username.setHtmlInfo(username.newHtmlInfoBuilder("input")
-     *         .addAttribute("type", "text")
-     *         .addAttribute("name", "username")
-     *         .addAttribute("id", "user")
-     *         .build());
-     *     username.setHint("Email or username");
-     *     username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
-     *     username.setAutofillValue(AutofillValue.forText("Type your username"));
-     *     username.setText("Type your username");
-     *     // Value of the field is not sensitive because it was not dynamically changed:
-     *     username.setDataIsSensitive(false);
-     *
-     *     ViewStructure password = structure.newChild(index + 1);
-     *     username.setAutofillId(structure, 2); // id 2 - second child
-     *     password.setInputType("android.widget.EditText");
-     *     password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
-     *     password.setAutofillHints("current-password");
-     *     password.setHtmlInfo(password.newHtmlInfoBuilder("input")
-     *         .addAttribute("type", "password")
-     *         .addAttribute("name", "password")
-     *         .addAttribute("id", "pass")
-     *         .build());
-     *     password.setHint("Password");
-     *     password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
-     * </pre>
-     *
-     * <p>Example2: an IFRAME tag.
-     *
-     * <pre class="prettyprint">
-     *    &lt;iframe src="https://example.com/login"/&gt;
-     * </pre>
-     *
-     * <p>Would map to:
-     *
-     * <pre class="prettyprint">
-     *     int index = structure.addChildCount(1);
-     *     ViewStructure iframe = structure.newChildFor(index);
-     *     iframe.setAutofillId(structure.getAutofillId(), 1);
-     *     iframe.setHtmlInfo(iframe.newHtmlInfoBuilder("iframe")
-     *         .addAttribute("src", "https://example.com/login")
-     *         .build());
-     * </pre>
-     */
-    @Override
-    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
-        mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
-    }
-
-    @Override
-    public void autofill(SparseArray<AutofillValue>values) {
-        mProvider.getViewDelegate().autofill(values);
-    }
-
-    /** @hide */
-    @Override
-    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfoInternal(info);
-        mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
-    }
-
-    /** @hide */
-    @Override
-    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
-        super.onInitializeAccessibilityEventInternal(event);
-        mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
-    }
-
-    /** @hide */
-    @Override
-    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
-        return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
-    }
-
-    /** @hide */
-    @Override
-    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
-            int l, int t, int r, int b) {
-        mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
-    }
-
-    @Override
-    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
-        mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY);
-    }
-
-    @Override
-    protected void onWindowVisibilityChanged(int visibility) {
-        super.onWindowVisibilityChanged(visibility);
-        mProvider.getViewDelegate().onWindowVisibilityChanged(visibility);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        mProvider.getViewDelegate().onDraw(canvas);
-    }
-
-    @Override
-    public boolean performLongClick() {
-        return mProvider.getViewDelegate().performLongClick();
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        mProvider.getViewDelegate().onConfigurationChanged(newConfig);
-    }
-
-    /**
-     * Creates a new InputConnection for an InputMethod to interact with the WebView.
-     * This is similar to {@link View#onCreateInputConnection} but note that WebView
-     * calls InputConnection methods on a thread other than the UI thread.
-     * If these methods are overridden, then the overriding methods should respect
-     * thread restrictions when calling View methods or accessing data.
-     */
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        return mProvider.getViewDelegate().onCreateInputConnection(outAttrs);
-    }
-
-    @Override
-    public boolean onDragEvent(DragEvent event) {
-        return mProvider.getViewDelegate().onDragEvent(event);
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        super.onVisibilityChanged(changedView, visibility);
-        // This method may be called in the constructor chain, before the WebView provider is
-        // created.
-        ensureProviderCreated();
-        mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus);
-        super.onWindowFocusChanged(hasWindowFocus);
-    }
-
-    @Override
-    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
-        mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect);
-        super.onFocusChanged(focused, direction, previouslyFocusedRect);
-    }
-
-    /** @hide */
-    @Override
-    protected boolean setFrame(int left, int top, int right, int bottom) {
-        return mProvider.getViewDelegate().setFrame(left, top, right, bottom);
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int ow, int oh) {
-        super.onSizeChanged(w, h, ow, oh);
-        mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh);
-    }
-
-    @Override
-    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
-        super.onScrollChanged(l, t, oldl, oldt);
-        mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        return mProvider.getViewDelegate().dispatchKeyEvent(event);
-    }
-
-    @Override
-    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
-        return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
-    public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
-        return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate);
-    }
-
-    @Override
-    public void setBackgroundColor(int color) {
-        mProvider.getViewDelegate().setBackgroundColor(color);
-    }
-
-    @Override
-    public void setLayerType(int layerType, Paint paint) {
-        super.setLayerType(layerType, paint);
-        mProvider.getViewDelegate().setLayerType(layerType, paint);
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        mProvider.getViewDelegate().preDispatchDraw(canvas);
-        super.dispatchDraw(canvas);
-    }
-
-    @Override
-    public void onStartTemporaryDetach() {
-        super.onStartTemporaryDetach();
-        mProvider.getViewDelegate().onStartTemporaryDetach();
-    }
-
-    @Override
-    public void onFinishTemporaryDetach() {
-        super.onFinishTemporaryDetach();
-        mProvider.getViewDelegate().onFinishTemporaryDetach();
-    }
-
-    @Override
-    public Handler getHandler() {
-        return mProvider.getViewDelegate().getHandler(super.getHandler());
-    }
-
-    @Override
-    public View findFocus() {
-        return mProvider.getViewDelegate().findFocus(super.findFocus());
-    }
-
-    /**
-     * If WebView has already been loaded into the current process this method will return the
-     * package that was used to load it. Otherwise, the package that would be used if the WebView
-     * was loaded right now will be returned; this does not cause WebView to be loaded, so this
-     * information may become outdated at any time.
-     * The WebView package changes either when the current WebView package is updated, disabled, or
-     * uninstalled. It can also be changed through a Developer Setting.
-     * If the WebView package changes, any app process that has loaded WebView will be killed. The
-     * next time the app starts and loads WebView it will use the new WebView package instead.
-     * @return the current WebView package, or {@code null} if there is none.
-     */
-    public static PackageInfo getCurrentWebViewPackage() {
-        PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
-        if (webviewPackage != null) {
-            return webviewPackage;
-        }
-
-        try {
-            return WebViewFactory.getUpdateService().getCurrentWebViewPackage();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
-     *
-     * @param requestCode The integer request code originally supplied to
-     *                    startActivityForResult(), allowing you to identify who this
-     *                    result came from.
-     * @param resultCode The integer result code returned by the child activity
-     *                   through its setResult().
-     * @param data An Intent, which can return result data to the caller
-     *               (various data can be attached to Intent "extras").
-     * @hide
-     */
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data);
-    }
-
-    /** @hide */
-    @Override
-    protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
-        super.encodeProperties(encoder);
-
-        checkThread();
-        encoder.addProperty("webview:contentHeight", mProvider.getContentHeight());
-        encoder.addProperty("webview:contentWidth", mProvider.getContentWidth());
-        encoder.addProperty("webview:scale", mProvider.getScale());
-        encoder.addProperty("webview:title", mProvider.getTitle());
-        encoder.addProperty("webview:url", mProvider.getUrl());
-        encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl());
+        return false;
     }
 }
diff --git a/android/widget/DatePickerCalendarDelegate.java b/android/widget/DatePickerCalendarDelegate.java
index 60b4757..e40023d 100644
--- a/android/widget/DatePickerCalendarDelegate.java
+++ b/android/widget/DatePickerCalendarDelegate.java
@@ -22,11 +22,10 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.icu.text.DateFormat;
 import android.icu.text.DisplayContext;
-import android.icu.text.SimpleDateFormat;
 import android.icu.util.Calendar;
 import android.os.Parcelable;
-import android.text.format.DateFormat;
 import android.util.AttributeSet;
 import android.util.StateSet;
 import android.view.HapticFeedbackConstants;
@@ -62,8 +61,8 @@
     private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
             com.android.internal.R.attr.disabledAlpha};
 
-    private SimpleDateFormat mYearFormat;
-    private SimpleDateFormat mMonthDayFormat;
+    private DateFormat mYearFormat;
+    private DateFormat mMonthDayFormat;
 
     // Top-level container.
     private ViewGroup mContainer;
@@ -273,19 +272,16 @@
     /**
      * Listener called when the user clicks on a header item.
      */
-    private final OnClickListener mOnHeaderClickListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            tryVibrate();
+    private final OnClickListener mOnHeaderClickListener = v -> {
+        tryVibrate();
 
-            switch (v.getId()) {
-                case R.id.date_picker_header_year:
-                    setCurrentView(VIEW_YEAR);
-                    break;
-                case R.id.date_picker_header_date:
-                    setCurrentView(VIEW_MONTH_DAY);
-                    break;
-            }
+        switch (v.getId()) {
+            case R.id.date_picker_header_year:
+                setCurrentView(VIEW_YEAR);
+                break;
+            case R.id.date_picker_header_date:
+                setCurrentView(VIEW_MONTH_DAY);
+                break;
         }
     };
 
@@ -299,10 +295,9 @@
         }
 
         // Update the date formatter.
-        final String datePattern = DateFormat.getBestDateTimePattern(locale, "EMMMd");
-        mMonthDayFormat = new SimpleDateFormat(datePattern, locale);
+        mMonthDayFormat = DateFormat.getInstanceForSkeleton("EMMMd", locale);
         mMonthDayFormat.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
-        mYearFormat = new SimpleDateFormat("y", locale);
+        mYearFormat = DateFormat.getInstanceForSkeleton("y", locale);
 
         // Update the header text.
         onCurrentDateChanged(false);
@@ -344,14 +339,11 @@
             case VIEW_YEAR:
                 final int year = mCurrentDate.get(Calendar.YEAR);
                 mYearPickerView.setYear(year);
-                mYearPickerView.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mYearPickerView.requestFocus();
-                        final View selected = mYearPickerView.getSelectedView();
-                        if (selected != null) {
-                            selected.requestFocus();
-                        }
+                mYearPickerView.post(() -> {
+                    mYearPickerView.requestFocus();
+                    final View selected = mYearPickerView.getSelectedView();
+                    if (selected != null) {
+                        selected.requestFocus();
                     }
                 });
 
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index d23dfe4..0f61724 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -41,6 +41,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.os.LocaleList;
 import android.os.Parcel;
@@ -1585,49 +1586,49 @@
         outText.startOffset = 0;
         outText.selectionStart = mTextView.getSelectionStart();
         outText.selectionEnd = mTextView.getSelectionEnd();
+        outText.hint = mTextView.getHint();
         return true;
     }
 
     boolean reportExtractedText() {
         final Editor.InputMethodState ims = mInputMethodState;
-        if (ims != null) {
-            final boolean contentChanged = ims.mContentChanged;
-            if (contentChanged || ims.mSelectionModeChanged) {
-                ims.mContentChanged = false;
-                ims.mSelectionModeChanged = false;
-                final ExtractedTextRequest req = ims.mExtractedTextRequest;
-                if (req != null) {
-                    InputMethodManager imm = InputMethodManager.peekInstance();
-                    if (imm != null) {
-                        if (TextView.DEBUG_EXTRACT) {
-                            Log.v(TextView.LOG_TAG, "Retrieving extracted start="
-                                    + ims.mChangedStart
-                                    + " end=" + ims.mChangedEnd
-                                    + " delta=" + ims.mChangedDelta);
-                        }
-                        if (ims.mChangedStart < 0 && !contentChanged) {
-                            ims.mChangedStart = EXTRACT_NOTHING;
-                        }
-                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
-                                ims.mChangedDelta, ims.mExtractedText)) {
-                            if (TextView.DEBUG_EXTRACT) {
-                                Log.v(TextView.LOG_TAG,
-                                        "Reporting extracted start="
-                                                + ims.mExtractedText.partialStartOffset
-                                                + " end=" + ims.mExtractedText.partialEndOffset
-                                                + ": " + ims.mExtractedText.text);
-                            }
-
-                            imm.updateExtractedText(mTextView, req.token, ims.mExtractedText);
-                            ims.mChangedStart = EXTRACT_UNKNOWN;
-                            ims.mChangedEnd = EXTRACT_UNKNOWN;
-                            ims.mChangedDelta = 0;
-                            ims.mContentChanged = false;
-                            return true;
-                        }
-                    }
-                }
+        if (ims == null) {
+            return false;
+        }
+        ims.mSelectionModeChanged = false;
+        final ExtractedTextRequest req = ims.mExtractedTextRequest;
+        if (req == null) {
+            return false;
+        }
+        final InputMethodManager imm = InputMethodManager.peekInstance();
+        if (imm == null) {
+            return false;
+        }
+        if (TextView.DEBUG_EXTRACT) {
+            Log.v(TextView.LOG_TAG, "Retrieving extracted start="
+                    + ims.mChangedStart
+                    + " end=" + ims.mChangedEnd
+                    + " delta=" + ims.mChangedDelta);
+        }
+        if (ims.mChangedStart < 0 && !ims.mContentChanged) {
+            ims.mChangedStart = EXTRACT_NOTHING;
+        }
+        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
+                ims.mChangedDelta, ims.mExtractedText)) {
+            if (TextView.DEBUG_EXTRACT) {
+                Log.v(TextView.LOG_TAG,
+                        "Reporting extracted start="
+                                + ims.mExtractedText.partialStartOffset
+                                + " end=" + ims.mExtractedText.partialEndOffset
+                                + ": " + ims.mExtractedText.text);
             }
+
+            imm.updateExtractedText(mTextView, req.token, ims.mExtractedText);
+            ims.mChangedStart = EXTRACT_UNKNOWN;
+            ims.mChangedEnd = EXTRACT_UNKNOWN;
+            ims.mChangedDelta = 0;
+            ims.mContentChanged = false;
+            return true;
         }
         return false;
     }
@@ -3932,6 +3933,10 @@
                         textClassification.getLabel())
                         .setIcon(textClassification.getIcon())
                         .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+                mMetricsLogger.write(
+                        new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+                                .setType(MetricsEvent.TYPE_OPEN)
+                                .setSubtype(textClassification.getLogType()));
             }
         }
 
@@ -3973,6 +3978,9 @@
                                 .onClick(mTextView);
                     }
                 }
+                mMetricsLogger.action(
+                        MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
+                        textClassification.getLogType());
                 stopTextActionMode();
                 return true;
             }
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index 4b6c4d3..efcc3a2 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -5547,6 +5547,14 @@
      */
     @android.view.RemotableViewMethod
     public final void setHint(CharSequence hint) {
+        setHintInternal(hint);
+
+        if (isInputMethodTarget()) {
+            mEditor.reportExtractedText();
+        }
+    }
+
+    private void setHintInternal(CharSequence hint) {
         mHint = TextUtils.stringOrSpannedString(hint);
 
         if (mLayout != null) {
@@ -7644,6 +7652,8 @@
         } else {
             MetaKeyKeyListener.stopSelecting(this, sp);
         }
+
+        setHintInternal(text.hint);
     }
 
     /**
@@ -8433,7 +8443,8 @@
 
         if (mMaxMode != LINES) {
             desired = Math.min(desired, mMaximum);
-        } else if (cap && linecount > mMaximum && layout instanceof DynamicLayout) {
+        } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
+                || layout instanceof BoringLayout)) {
             desired = layout.getLineTop(mMaximum);
 
             if (dr != null) {