Merge "Display magnification via the nav bar Accessibility Button"
diff --git a/api/current.txt b/api/current.txt
index 77cb9e4..477e346 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24653,7 +24653,7 @@
 
   public final class TvInputInfo implements android.os.Parcelable {
     method public boolean canRecord();
-    method public android.content.Intent createSettingsIntent();
+    method public deprecated android.content.Intent createSettingsIntent();
     method public android.content.Intent createSetupIntent();
     method public int describeContents();
     method public android.os.Bundle getExtras();
@@ -33878,6 +33878,7 @@
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID";
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME";
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI";
+    field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH";
     field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT";
     field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type";
     field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype";
@@ -39812,7 +39813,6 @@
     method public android.os.PersistableBundle getCarrierConfig();
     method public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
-    method public boolean getDataEnabled();
     method public int getDataNetworkType();
     method public int getDataState();
     method public java.lang.String getDeviceId();
@@ -39853,6 +39853,7 @@
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
     method public boolean isConcurrentVoiceAndDataAllowed();
+    method public boolean isDataEnabled();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isNetworkRoaming();
     method public boolean isSmsCapable();
diff --git a/api/system-current.txt b/api/system-current.txt
index 827b46c..04dc632 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -26653,7 +26653,7 @@
 
   public final class TvInputInfo implements android.os.Parcelable {
     method public boolean canRecord();
-    method public android.content.Intent createSettingsIntent();
+    method public deprecated android.content.Intent createSettingsIntent();
     method public android.content.Intent createSetupIntent();
     method public static deprecated android.media.tv.TvInputInfo createTvInputInfo(android.content.Context, android.content.pm.ResolveInfo, android.hardware.hdmi.HdmiDeviceInfo, java.lang.String, java.lang.String, android.net.Uri) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method public static deprecated android.media.tv.TvInputInfo createTvInputInfo(android.content.Context, android.content.pm.ResolveInfo, android.hardware.hdmi.HdmiDeviceInfo, java.lang.String, int, android.graphics.drawable.Icon) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -36758,6 +36758,7 @@
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID";
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME";
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI";
+    field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH";
     field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT";
     field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type";
     field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype";
@@ -43216,8 +43217,8 @@
     method public int getCurrentPhoneType();
     method public int getCurrentPhoneType(int);
     method public int getDataActivity();
-    method public boolean getDataEnabled();
-    method public boolean getDataEnabled(int);
+    method public deprecated boolean getDataEnabled();
+    method public deprecated boolean getDataEnabled(int);
     method public int getDataNetworkType();
     method public int getDataState();
     method public java.lang.String getDeviceId();
@@ -43264,6 +43265,7 @@
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
     method public boolean isConcurrentVoiceAndDataAllowed();
     method public boolean isDataConnectivityPossible();
+    method public boolean isDataEnabled();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isIdle();
     method public boolean isNetworkRoaming();
diff --git a/api/test-current.txt b/api/test-current.txt
index 04f4f21..6962c45 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -24754,7 +24754,7 @@
 
   public final class TvInputInfo implements android.os.Parcelable {
     method public boolean canRecord();
-    method public android.content.Intent createSettingsIntent();
+    method public deprecated android.content.Intent createSettingsIntent();
     method public android.content.Intent createSetupIntent();
     method public int describeContents();
     method public android.os.Bundle getExtras();
@@ -34005,6 +34005,7 @@
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID";
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME";
     field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI";
+    field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH";
     field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT";
     field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type";
     field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype";
@@ -40001,7 +40002,6 @@
     method public android.os.PersistableBundle getCarrierConfig();
     method public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
-    method public boolean getDataEnabled();
     method public int getDataNetworkType();
     method public int getDataState();
     method public java.lang.String getDeviceId();
@@ -40042,6 +40042,7 @@
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
     method public boolean isConcurrentVoiceAndDataAllowed();
+    method public boolean isDataEnabled();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isNetworkRoaming();
     method public boolean isSmsCapable();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index be38f42..77c4c7e 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -597,8 +597,7 @@
         // Avoid the binder call when the package is the current application package.
         // The activity manager will perform ensure that dexopt is performed before
         // spinning up the process.
-        if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
-            VMRuntime.getRuntime().vmInstructionSet();
+        if (!Objects.equals(mPackageName, ActivityThread.currentPackageName()) && mIncludeCode) {
             try {
                 ActivityThread.getPackageManager().notifyPackageUse(mPackageName,
                         PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index d428a3a..2f87633 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -24,6 +24,7 @@
 import android.database.CursorToBulkCursorAdaptor;
 import android.database.DatabaseUtils;
 import android.database.IContentObserver;
+import android.database.PageViewCursor;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -103,6 +104,7 @@
                     if (cursor != null) {
                         CursorToBulkCursorAdaptor adaptor = null;
 
+                        cursor = PageViewCursor.wrap(cursor, queryArgs);
                         try {
                             adaptor = new CursorToBulkCursorAdaptor(cursor, observer,
                                     getProviderName());
diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java
new file mode 100644
index 0000000..fbd039d
--- /dev/null
+++ b/core/java/android/database/PageViewCursor.java
@@ -0,0 +1,245 @@
+/*
+ * 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.database;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Cursor wrapper that provides visibility into a subset of a wrapped cursor.
+ *
+ * The window is specified by offset and limit.
+ *
+ * @hide
+ */
+public final class PageViewCursor extends CrossProcessCursorWrapper {
+
+    /**
+     * An extra added to results that are auto-paged using the wrapper.
+     */
+    public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED";
+
+    private static final String TAG = "PageViewCursor";
+    private static final boolean DEBUG = false;
+    private static final boolean VERBOSE = false;
+
+    private final int mOffset;  // aka first index
+    private final int mCount;
+    private final Bundle mExtras;
+
+    private int mPos = -1;
+
+    /**
+     * @see PageViewCursor#wrap(Cursor, Bundle)
+     */
+    @VisibleForTesting
+    public PageViewCursor(Cursor cursor, int offset, int limit) {
+        super(cursor);
+
+        checkArgument(offset > -1);
+        checkArgument(limit > -1);
+
+        mOffset = offset;
+
+        mExtras = new Bundle();
+        Bundle extras = cursor.getExtras();
+        if (extras != null) {
+            mExtras.putAll(extras);
+        }
+        mExtras.putBoolean(EXTRA_AUTO_PAGED, true);
+
+        // We need a mutable bundle so we can add QUERY_RESULT_SIZE.
+        // Direct equality check is correct here. Bundle.EMPTY is a specific instance
+        // of Bundle that is immutable by way of implementation.
+        // mExtras = (extras == Bundle.EMPTY) ? new Bundle() : extras;
+
+        // When we're wrapping another cursor, it should not already be "paged".
+        checkArgument(!mExtras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE));
+
+        int count = mCursor.getCount();
+        mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count);
+
+        mCount = MathUtils.constrain(count - offset, 0, limit);
+
+        if (DEBUG) Log.d(TAG, "Wrapped cursor"
+            + " offset: " + mOffset
+            + ", limit: " + limit
+            + ", delegate_size: " + count
+            + ", paged_count: " + mCount);
+    }
+
+    @Override
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int getPosition() {
+        return mPos;
+    }
+
+    @Override
+    public boolean isBeforeFirst() {
+        if (mCount == 0) {
+            return true;
+        }
+        return mPos == -1;
+    }
+
+    @Override
+    public boolean isAfterLast() {
+        if (mCount == 0) {
+            return true;
+        }
+        return mPos == mCount;
+    }
+
+    @Override
+    public boolean isFirst() {
+        return mPos == 0;
+    }
+
+    @Override
+    public boolean isLast() {
+        return mPos == mCount - 1;
+    }
+
+    @Override
+    public boolean moveToFirst() {
+        return moveToPosition(0);
+    }
+
+    @Override
+    public boolean moveToLast() {
+        return moveToPosition(mCount - 1);
+    }
+
+    @Override
+    public boolean moveToNext() {
+        return move(1);
+    }
+
+    @Override
+    public boolean moveToPrevious() {
+        return move(-1);
+    }
+
+    @Override
+    public boolean move(int offset) {
+        return moveToPosition(mPos + offset);
+    }
+
+    @Override
+    public boolean moveToPosition(int position) {
+        if (position >= mCount) {
+            if (VERBOSE) Log.v(TAG, "Invalid Positon: " + position + " >= count: " + mCount
+                    + ". Moving to last record.");
+            mPos = mCount;
+            super.moveToPosition(mOffset + mPos);  // move into "after last" state.
+            return false;
+        }
+
+        // Make sure position isn't before the beginning of the cursor
+        if (position < 0) {
+            if (VERBOSE) Log.v(TAG, "Ignoring invalid move to position: " + position);
+            mPos = -1;
+            super.moveToPosition(mPos);
+            return false;
+        }
+
+        if (position == mPos) {
+            if (VERBOSE) Log.v(TAG, "Ignoring no-op move to position: " + position);
+            return true;
+        }
+
+        int delegatePosition = position + mOffset;
+        if (VERBOSE) Log.v(TAG, "Moving delegate cursor to position: " + delegatePosition);
+        if (super.moveToPosition(delegatePosition)) {
+            mPos = position;
+            return true;
+        } else {
+            mPos = -1;
+            super.moveToPosition(-1);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public int getCount() {
+        return mCount;
+    }
+
+    /**
+     * Wraps the cursor such that it will honor paging args (if present), AND if the cursor
+     * does not report paging size.
+     *
+     * <p>No-op if cursor already contains paging or is less than specified page size.
+     */
+    public static Cursor wrap(Cursor cursor, @Nullable Bundle queryArgs) {
+
+        boolean hasPagingArgs =
+                queryArgs != null
+                && (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)
+                || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT));
+
+        if (!hasPagingArgs) {
+            if (VERBOSE) Log.d(TAG, "No-wrap: No paging args in request.");
+            return cursor;
+        }
+
+        if (hasPagedResponseDetails(cursor.getExtras())) {
+            if (VERBOSE) Log.d(TAG, "No-wrap. Cursor has paging details.");
+            return cursor;
+        }
+
+        return new PageViewCursor(
+                cursor,
+                queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0),
+                queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE));
+    }
+
+    /**
+     * @return true if the extras contains information indicating the associated
+     * cursor is paged.
+     */
+    private static boolean hasPagedResponseDetails(@Nullable Bundle extras) {
+        if (extras != null && extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) {
+            return true;
+        }
+
+        String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+        if (honoredArgs != null && (
+                ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET)
+                || ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a0d16bc..dac8354 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -44,7 +44,6 @@
 import android.util.DisplayMetrics;
 import android.util.Pair;
 import android.view.View;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -8913,11 +8912,15 @@
          * ambiguous then the activity should prompt the user for the recipient to send the message
          * to.
          * <p>
+         * Voice Assistant may provide additional information to messaging app about which account
+         * to use for sending a message by populating {@link #EXTRA_SENDER_ACCOUNT_HASH}.
+         * <p>
          * Output: nothing
          *
          * @see #EXTRA_RECIPIENT_CONTACT_URI
          * @see #EXTRA_RECIPIENT_CONTACT_CHAT_ID
          * @see #EXTRA_RECIPIENT_CONTACT_NAME
+         * @see #EXTRA_SENDER_ACCOUNT_HASH
          * @see #METADATA_ACCOUNT_TYPE
          * @see #METADATA_MIMETYPE
          */
@@ -8975,6 +8978,16 @@
                 "android.provider.extra.RECIPIENT_CONTACT_NAME";
 
         /**
+         * This optional extra specifies the hash of the account that should be used by messaging
+         * app for sending voice message with {@link #ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS}. The
+         * value of this extra is a {@code String} and should be the value of {@link
+         * android.accounts.Account#hashCode()} for some account returned by {@link
+         * android.accounts.AccountManager#getAccounts()}.
+         */
+        public static final String EXTRA_SENDER_ACCOUNT_HASH =
+                "android.provider.extra.SENDER_ACCOUNT_HASH";
+
+        /**
          * A string associated with an {@link #ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS} activity
          * describing {@link RawContacts#ACCOUNT_TYPE} for the corresponding Contacts Provider
          * implementation.
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index ae31873..95d714f 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -321,7 +321,7 @@
                 stackBounds.top));
         boundsOut.set(stackBounds);
         if (mIsMinimized) {
-            boundsOut.offsetTo(boundedLeft, boundsOut.top);
+            boundsOut.offsetTo(boundedLeft, boundedTop);
             return;
         }
 
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 79b0cd1..818cc2c 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -1163,21 +1163,21 @@
                         isLastItem && menuItemButtonWidth <= availableWidth - extraPadding;
                 if (canFitWithOverflow || canFitNoOverflow) {
                     if (isNewGroup) {
-                        final View border = createBorder(mContext);
-                        final int borderWidth = border.getLayoutParams().width;
+                        final View divider = createDivider(mContext);
+                        final int dividerWidth = divider.getLayoutParams().width;
 
                         // Add extra padding to the end of the previous button.
                         // Half of the extra padding (less borderWidth) goes to the previous button.
                         View previousButton = mMainPanel.getChildAt(mMainPanel.getChildCount() - 1);
                         final int prevPaddingEnd = previousButton.getPaddingEnd()
-                                + extraPadding / 2 - borderWidth;
+                                + extraPadding / 2 - dividerWidth;
                         previousButton.setPaddingRelative(
                                 previousButton.getPaddingStart(),
                                 previousButton.getPaddingTop(),
                                 prevPaddingEnd,
                                 previousButton.getPaddingBottom());
                         final ViewGroup.LayoutParams prevParams = previousButton.getLayoutParams();
-                        prevParams.width += extraPadding / 2 - borderWidth;
+                        prevParams.width += extraPadding / 2 - dividerWidth;
                         previousButton.setLayoutParams(prevParams);
 
                         // Add extra padding to the start of this button.
@@ -1190,8 +1190,8 @@
                                 menuItemButton.getPaddingEnd(),
                                 menuItemButton.getPaddingBottom());
 
-                        // Include a border.
-                        mMainPanel.addView(border);
+                        // Include a divider.
+                        mMainPanel.addView(divider);
                     }
 
                     setButtonTagAndClickListener(menuItemButton, menuItem);
@@ -1670,21 +1670,28 @@
         return popupWindow;
     }
 
-    private static View createBorder(Context context) {
+    private static View createDivider(Context context) {
         // TODO: Inflate this instead.
-        View border = new View(context);
+        View divider = new View(context);
+
         int _1dp = (int) TypedValue.applyDimension(
                 TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                 _1dp, ViewGroup.LayoutParams.MATCH_PARENT);
         params.setMarginsRelative(0, _1dp * 10, 0, _1dp * 10);
-        border.setLayoutParams(params);
-        border.setBackgroundColor(Color.parseColor("#9E9E9E"));
-        border.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-        border.setEnabled(false);
-        border.setFocusable(false);
-        border.setContentDescription(null);
-        return border;
+        divider.setLayoutParams(params);
+
+        TypedArray a = context.obtainStyledAttributes(
+                new TypedValue().data, new int[] { R.attr.floatingToolbarDividerColor });
+        divider.setBackgroundColor(a.getColor(0, 0));
+        a.recycle();
+
+        divider.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+        divider.setEnabled(false);
+        divider.setFocusable(false);
+        divider.setContentDescription(null);
+
+        return divider;
     }
 
     /**
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c547ae5..4432e3c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -576,6 +576,7 @@
        <attr name="floatingToolbarItemBackgroundDrawable" format="reference" />
        <attr name="floatingToolbarOpenDrawable" format="reference" />
        <attr name="floatingToolbarPopupBackgroundDrawable" format="reference" />
+       <attr name="floatingToolbarDividerColor" format="reference" />
 
         <!-- ============ -->
         <!-- Alert Dialog styles -->
@@ -8379,7 +8380,9 @@
         <!-- Component name of an activity that allows the user to set up this service. -->
         <attr name="setupActivity" format="string" />
         <!-- Component name of an activity that allows the user to modify the settings for this
-             service. -->
+             service.
+             {@deprecated This value is deprecated and not used by the framework starting from API
+                         level 26. Use setupActivity instead.} -->
         <attr name="settingsActivity" />
         <!-- Attribute whether the TV input service can record programs. This value can be changed
              at runtime by calling
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 6015ed5..f9fd57c 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -194,4 +194,8 @@
 
     <color name="tooltip_background_dark">#e6616161</color>
     <color name="tooltip_background_light">#e6FFFFFF</color>
+
+    <!-- FloatingToolbar -->
+    <color name="floating_popup_divider_dark">#2F2F2F</color>
+    <color name="floating_popup_divider_light">#E9E9E9</color>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0c318cf..26d71c3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2425,6 +2425,7 @@
   <java-symbol type="drawable" name="ft_avd_toarrow" />
   <java-symbol type="drawable" name="ft_avd_toarrow_animation" />
   <java-symbol type="drawable" name="ft_avd_tooverflow_animation" />
+  <java-symbol type="attr" name="floatingToolbarDividerColor" />
 
   <java-symbol type="string" name="date_picker_prev_month_button" />
   <java-symbol type="string" name="date_picker_next_month_button" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d100c63..a661b07 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -398,6 +398,7 @@
         <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_dark</item>
         <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_dark</item>
         <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_dark</item>
+        <item name="floatingToolbarDividerColor">@color/floating_popup_divider_dark</item>
 
         <!-- SearchView attributes -->
         <item name="searchViewStyle">@style/Widget.Holo.SearchView</item>
@@ -559,6 +560,7 @@
         <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_light</item>
         <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item>
         <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_light</item>
+        <item name="floatingToolbarDividerColor">@color/floating_popup_divider_light</item>
 
         <!-- Tooltip popup colors -->
         <item name="tooltipForegroundColor">@color/bright_foreground_dark</item>
diff --git a/core/tests/coretests/src/android/database/PageViewCursorTest.java b/core/tests/coretests/src/android/database/PageViewCursorTest.java
new file mode 100644
index 0000000..0be89d5
--- /dev/null
+++ b/core/tests/coretests/src/android/database/PageViewCursorTest.java
@@ -0,0 +1,318 @@
+/*
+ * 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.database;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+public class PageViewCursorTest {
+
+    private static final int ITEM_COUNT = 20;
+
+    private static final String NAME_COLUMN = "name";
+    private static final String NUM_COLUMN = "num";
+
+    private static final String[] COLUMNS = new String[]{
+      NAME_COLUMN,
+      NUM_COLUMN
+    };
+
+    private static final String[] NAMES = new String[] {
+            "000",
+            "111",
+            "222",
+            "333",
+            "444",
+            "555",
+            "666",
+            "777",
+            "888",
+            "999",
+            "aaa",
+            "bbb",
+            "ccc",
+            "ddd",
+            "eee",
+            "fff",
+            "ggg",
+            "hhh",
+            "iii",
+            "jjj"
+    };
+
+    private MatrixCursor mDelegate;
+    private PageViewCursor mCursor;
+
+    @Before
+    public void setUp() {
+        Random rand = new Random();
+
+        mDelegate = new MatrixCursor(COLUMNS);
+        for (int i = 0; i < ITEM_COUNT; i++) {
+            MatrixCursor.RowBuilder row = mDelegate.newRow();
+            row.add(NAME_COLUMN, NAMES[i]);
+            row.add(NUM_COLUMN, rand.nextInt());
+        }
+
+        mCursor = new PageViewCursor(mDelegate, 10, 5);
+    }
+
+    @Test
+    public void testPage_Size() {
+        assertEquals(5, mCursor.getCount());
+    }
+
+    @Test
+    public void testPage_TotalSize() {
+        assertEquals(ITEM_COUNT, mCursor.getExtras().getInt(ContentResolver.EXTRA_TOTAL_SIZE));
+    }
+
+    @Test
+    public void testPage_OffsetExceedsCursorCount_EffectivelyEmptyCursor() {
+        mCursor = new PageViewCursor(mDelegate, ITEM_COUNT * 2, 5);
+        assertEquals(0, mCursor.getCount());
+    }
+
+    @Test
+    public void testMoveToPosition() {
+        assertTrue(mCursor.moveToPosition(0));
+        assertEquals(NAMES[10], mCursor.getString(0));
+        assertTrue(mCursor.moveToPosition(1));
+        assertEquals(NAMES[11], mCursor.getString(0));
+        assertTrue(mCursor.moveToPosition(4));
+        assertEquals(NAMES[14], mCursor.getString(0));
+
+        // and then back down again for good measure.
+        assertTrue(mCursor.moveToPosition(1));
+        assertEquals(NAMES[11], mCursor.getString(0));
+        assertTrue(mCursor.moveToPosition(0));
+        assertEquals(NAMES[10], mCursor.getString(0));
+    }
+
+    @Test
+    public void testMoveToPosition_MoveToSamePosition_NoOp() {
+        assertTrue(mCursor.moveToPosition(1));
+        assertEquals(NAMES[11], mCursor.getString(0));
+        assertTrue(mCursor.moveToPosition(1));
+        assertEquals(NAMES[11], mCursor.getString(0));
+    }
+
+    @Test
+    public void testMoveToPosition_PositionOutOfBounds_MovesToBeforeFirst() {
+        assertTrue(mCursor.moveToPosition(0));
+        assertEquals(NAMES[10], mCursor.getString(0));
+
+        // move before
+        assertFalse(mCursor.moveToPosition(-12));
+        assertTrue(mCursor.isBeforeFirst());
+    }
+
+    @Test
+    public void testMoveToPosition_PositionOutOfBounds_MovesToAfterLast() {
+        assertTrue(mCursor.moveToPosition(0));
+        assertEquals(NAMES[10], mCursor.getString(0));
+
+        assertFalse(mCursor.moveToPosition(222));
+        assertTrue(mCursor.isAfterLast());
+    }
+
+    @Test
+    public void testPosition() {
+        assertEquals(-1, mCursor.getPosition());
+    }
+
+    @Test
+    public void testIsBeforeFirst() {
+        assertTrue(mCursor.isBeforeFirst());
+        mCursor.moveToFirst();
+        assertFalse(mCursor.isBeforeFirst());
+    }
+
+    @Test
+    public void testCount_ZeroForEmptyCursor() {
+        mCursor = new PageViewCursor(mDelegate, 0, 0);
+        assertEquals(0, mCursor.getCount());
+    }
+
+    @Test
+    public void testIsBeforeFirst_TrueForEmptyCursor() {
+        mCursor = new PageViewCursor(mDelegate, 0, 0);
+        assertTrue(mCursor.isBeforeFirst());
+    }
+
+    @Test
+    public void testIsAfterLast() {
+        assertFalse(mCursor.isAfterLast());
+        mCursor.moveToLast();
+        mCursor.moveToNext();
+        assertTrue(mCursor.isAfterLast());
+    }
+
+    @Test
+    public void testIsAfterLast_TrueForEmptyCursor() {
+        mCursor = new PageViewCursor(mDelegate, 0, 0);
+        assertTrue(mCursor.isAfterLast());
+    }
+
+    @Test
+    public void testIsFirst() {
+        assertFalse(mCursor.isFirst());
+        mCursor.moveToFirst();
+        assertTrue(mCursor.isFirst());
+    }
+
+    @Test
+    public void testIsLast() {
+        assertFalse(mCursor.isLast());
+        mCursor.moveToLast();
+        assertTrue(mCursor.isLast());
+    }
+
+    @Test
+    public void testMove() {
+        // note that initial position is -1, so moving
+        // 2 will only put as at 1.
+        mCursor.move(2);
+        assertEquals(NAMES[11], mCursor.getString(0));
+        mCursor.move(-1);
+        assertEquals(NAMES[10], mCursor.getString(0));
+    }
+
+    @Test
+    public void testMoveToFist() {
+        mCursor.moveToPosition(3);
+        mCursor.moveToFirst();
+        assertEquals(NAMES[10], mCursor.getString(0));
+    }
+
+    @Test
+    public void testMoveToLast() {
+        mCursor.moveToLast();
+        assertEquals(NAMES[14], mCursor.getString(0));
+    }
+
+    @Test
+    public void testMoveToNext() {
+        // default position is -1, so next is 0.
+        mCursor.moveToNext();
+        assertEquals(NAMES[10], mCursor.getString(0));
+    }
+
+    @Test
+    public void testMoveToNext_AfterLastReturnsFalse() {
+        mCursor.moveToLast();
+        assertFalse(mCursor.moveToNext());
+    }
+
+    @Test
+    public void testMoveToPrevious() {
+        mCursor.moveToPosition(3);
+        mCursor.moveToPrevious();
+        assertEquals(NAMES[12], mCursor.getString(0));
+    }
+
+    @Test
+    public void testMoveToPrevious_BeforeFirstReturnsFalse() {
+        assertFalse(mCursor.moveToPrevious());
+    }
+
+    @Test
+    public void testWindow_ReadPastEnd() {
+        assertFalse(mCursor.moveToPosition(10));
+    }
+
+    @Test
+    public void testOffset_LimitOutOfBounds() {
+        mCursor = new PageViewCursor(mDelegate, 5, 100);
+        assertEquals(15, mCursor.getCount());
+    }
+
+    @Test
+    public void testPagingMarker() {
+        mCursor = new PageViewCursor(mDelegate, 5, 100);
+        assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED));
+    }
+
+    @Test
+    public void testWrap() {
+        Bundle queryArgs = new Bundle();
+        queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
+        queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+        assertTrue(wrapped instanceof PageViewCursor);
+        assertEquals(5, wrapped.getCount());
+    }
+
+    @Test
+    public void testWrap_NoOpWithoutPagingArgs() {
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, Bundle.EMPTY);
+        assertTrue(mDelegate == wrapped);
+    }
+
+    @Test
+    public void testWrap_NoOpCursorsWithExistingPaging_ByTotalSize() {
+        Bundle extras = new Bundle();
+        extras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, 5);
+        mDelegate.setExtras(extras);
+
+        Bundle queryArgs = new Bundle();
+        queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
+        queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+        assertTrue(mDelegate == wrapped);
+    }
+
+    @Test
+    public void testWrap_NoOpCursorsWithExistingPaging_ByHonoredArgs() {
+        Bundle extras = new Bundle();
+        extras.putStringArray(
+                ContentResolver.EXTRA_HONORED_ARGS,
+                new String[] {
+                    ContentResolver.QUERY_ARG_OFFSET,
+                    ContentResolver.QUERY_ARG_LIMIT
+                });
+        mDelegate.setExtras(extras);
+
+        Bundle queryArgs = new Bundle();
+        queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
+        queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+        assertTrue(mDelegate == wrapped);
+    }
+
+    private void assertStringAt(int row, int column, String expected) {
+        mCursor.moveToPosition(row);
+        assertEquals(expected, mCursor.getString(column));
+    }
+}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 39184f1..0906ba5 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -677,7 +677,7 @@
              ((audioSource > MediaRecorder.getAudioSourceMax()) &&
               (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) &&
               (audioSource != MediaRecorder.AudioSource.HOTWORD)) )  {
-            throw new IllegalArgumentException("Invalid audio source.");
+            throw new IllegalArgumentException("Invalid audio source " + audioSource);
         }
         mRecordSource = audioSource;
 
@@ -703,8 +703,8 @@
             mAudioFormat = audioFormat;
             break;
         default:
-            throw new IllegalArgumentException("Unsupported sample encoding."
-                    + " Should be ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, or ENCODING_PCM_FLOAT.");
+            throw new IllegalArgumentException("Unsupported sample encoding " + audioFormat
+                    + ". Should be ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, or ENCODING_PCM_FLOAT.");
         }
     }
 
@@ -722,7 +722,8 @@
         int frameSizeInBytes = mChannelCount
             * (AudioFormat.getBytesPerSample(mAudioFormat));
         if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) {
-            throw new IllegalArgumentException("Invalid audio buffer size.");
+            throw new IllegalArgumentException("Invalid audio buffer size " + audioBufferSize
+                    + " (frame size " + frameSizeInBytes + ")");
         }
 
         mNativeBufferSizeInBytes = audioBufferSize;
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 35988d4..a292b8e 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -138,7 +138,6 @@
 
     // Attributes from XML meta data.
     private final String mSetupActivity;
-    private final String mSettingsActivity;
     private final boolean mCanRecord;
     private final int mTunerCount;
 
@@ -259,9 +258,8 @@
 
     private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
             CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
-            String setupActivity, String settingsActivity, boolean canRecord, int tunerCount,
-            HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, String parentId,
-            Bundle extras) {
+            String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo,
+            boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) {
         mService = service;
         mId = id;
         mType = type;
@@ -272,7 +270,6 @@
         mIconStandby = iconStandby;
         mIconDisconnected = iconDisconnected;
         mSetupActivity = setupActivity;
-        mSettingsActivity = settingsActivity;
         mCanRecord = canRecord;
         mTunerCount = tunerCount;
         mHdmiDeviceInfo = hdmiDeviceInfo;
@@ -340,14 +337,12 @@
 
     /**
      * Returns an intent to start the settings activity for this TV input.
+     *
+     * @deprecated Use {@link #createSetupIntent()} instead. Settings activity is deprecated.
+     *             Use setup activity instead to provide settings.
      */
+    @Deprecated
     public Intent createSettingsIntent() {
-        if (!TextUtils.isEmpty(mSettingsActivity)) {
-            Intent intent = new Intent(Intent.ACTION_MAIN);
-            intent.setClassName(mService.serviceInfo.packageName, mSettingsActivity);
-            intent.putExtra(EXTRA_INPUT_ID, getId());
-            return intent;
-        }
         return null;
     }
 
@@ -554,7 +549,6 @@
                 && Objects.equals(mIconStandby, obj.mIconStandby)
                 && Objects.equals(mIconDisconnected, obj.mIconDisconnected)
                 && TextUtils.equals(mSetupActivity, obj.mSetupActivity)
-                && TextUtils.equals(mSettingsActivity, obj.mSettingsActivity)
                 && mCanRecord == obj.mCanRecord
                 && mTunerCount == obj.mTunerCount
                 && Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo)
@@ -589,7 +583,6 @@
         dest.writeParcelable(mIconStandby, flags);
         dest.writeParcelable(mIconDisconnected, flags);
         dest.writeString(mSetupActivity);
-        dest.writeString(mSettingsActivity);
         dest.writeByte(mCanRecord ? (byte) 1 : 0);
         dest.writeInt(mTunerCount);
         dest.writeParcelable(mHdmiDeviceInfo, flags);
@@ -631,7 +624,6 @@
         mIconStandby = in.readParcelable(null);
         mIconDisconnected = in.readParcelable(null);
         mSetupActivity = in.readString();
-        mSettingsActivity = in.readString();
         mCanRecord = in.readByte() == 1;
         mTunerCount = in.readInt();
         mHdmiDeviceInfo = in.readParcelable(null);
@@ -678,7 +670,6 @@
         private Icon mIconStandby;
         private Icon mIconDisconnected;
         private String mSetupActivity;
-        private String mSettingsActivity;
         private Boolean mCanRecord;
         private Integer mTunerCount;
         private TvInputHardwareInfo mTvInputHardwareInfo;
@@ -906,7 +897,7 @@
             }
             parseServiceMetadata(type);
             return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId,
-                    mIcon, mIconStandby, mIconDisconnected, mSetupActivity, mSettingsActivity,
+                    mIcon, mIconStandby, mIconDisconnected, mSetupActivity,
                     mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount,
                     mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras);
         }
@@ -961,8 +952,6 @@
                 if (inputType == TYPE_TUNER && TextUtils.isEmpty(mSetupActivity)) {
                     throw new IllegalStateException("Setup activity not found for " + si.name);
                 }
-                mSettingsActivity = sa.getString(
-                        com.android.internal.R.styleable.TvInputService_settingsActivity);
                 if (mCanRecord == null) {
                     mCanRecord = sa.getBoolean(
                             com.android.internal.R.styleable.TvInputService_canRecord, false);
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 2e642ec..8df194c 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -25,7 +25,6 @@
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
     <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
 
     <application android:label="@string/app_name" >
@@ -34,10 +33,16 @@
                 <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
             </intent-filter>
         </receiver>
-        <activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar"
-            android:excludeFromRecents="true"/>
         <service android:name="com.android.carrierdefaultapp.ProvisionObserver"
                  android:permission="android.permission.BIND_JOB_SERVICE"/>
+        <activity
+            android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
+            android:label="@string/action_bar_label"
+            android:theme="@style/AppTheme"
+            android:configChanges="keyboardHidden|orientation|screenSize" >
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png
new file mode 100644
index 0000000..08294ce
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png
Binary files differ
diff --git a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
index dc54fe2..75aa405 100644
--- a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
+++ b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
@@ -22,4 +22,4 @@
 <path
     android:fillColor="#757575"
     android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM13,17h-2v-2h2v2zM13,13h-2L11,8h2v5z"/>
-</vector>
\ No newline at end of file
+</vector>
diff --git a/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml b/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml
new file mode 100644
index 0000000..528576b
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml
@@ -0,0 +1,34 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
+    tools:ignore="MergeRootFrame">
+    <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/url_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="20sp"
+        android:singleLine="true" />
+
+    <ProgressBar
+        android:id="@+id/progress_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/progressBarStyleHorizontal" />
+
+    <WebView
+        android:id="@+id/webview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentBottom="false"
+        android:layout_alignParentRight="false" />
+
+</LinearLayout>
+</FrameLayout>
diff --git a/packages/CarrierDefaultApp/res/values/dimens.xml b/packages/CarrierDefaultApp/res/values/dimens.xml
index a3c5049..1ea8c35 100644
--- a/packages/CarrierDefaultApp/res/values/dimens.xml
+++ b/packages/CarrierDefaultApp/res/values/dimens.xml
@@ -1,3 +1,6 @@
 <resources>
     <dimen name="glif_icon_size">32dp</dimen>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index f904600..f9342b7 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -6,9 +6,8 @@
     <string name="no_data_notification_id">Your mobile data has been deactivated</string>
     <string name="portal_notification_detail">Tap to visit the %s website</string>
     <string name="no_data_notification_detail">Please contact your service provider %s</string>
-    <string name="progress_dialogue_network_connection">Connecting to captive portal...</string>
-    <string name="alert_dialogue_network_timeout">Network timeout, would you like to retry?</string>
-    <string name="alert_dialogue_network_timeout_title">Network unavailable</string>
-    <string name="quit">Quit</string>
-    <string name="wait">Wait</string>
+    <string name="action_bar_label">Sign in to mobile network</string>
+    <string name="ssl_error_warning">The network you&#8217;re trying to join has security issues.</string>
+    <string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
+    <string name="ssl_error_continue">Continue anyway via browser</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values/styles.xml b/packages/CarrierDefaultApp/res/values/styles.xml
index 3d26915..939c1aa 100644
--- a/packages/CarrierDefaultApp/res/values/styles.xml
+++ b/packages/CarrierDefaultApp/res/values/styles.xml
@@ -1,3 +1,16 @@
 <resources>
-    <style name="AlertDialog" parent="android:Theme.Material.Light.Dialog.Alert"/>
+    <style name="AppBaseTheme" parent="@android:style/Theme.Material.Settings">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+        <!-- Setting's theme's accent color makes ProgressBar useless, reset back. -->
+        <item name="android:colorAccent">@*android:color/material_deep_teal_500</item>
+    </style>
 </resources>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java
deleted file mode 100644
index 28251cb..0000000
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.carrierdefaultapp;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.ProgressDialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.net.CaptivePortal;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkRequest;
-import android.os.Bundle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
-import android.telephony.SubscriptionManager;
-import android.text.TextUtils;
-import android.net.ICaptivePortal;
-import android.view.ContextThemeWrapper;
-import android.view.WindowManager;
-import com.android.carrierdefaultapp.R;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.ArrayUtils;
-
-import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
-
-/**
- * Activity that launches in response to the captive portal notification
- * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
- * This activity requests network connection if there is no available one, launches the
- * {@link com.android.captiveportallogin portalApp} and keeps track of the portal activation result.
- */
-public class CaptivePortalLaunchActivity extends Activity {
-    private static final String TAG = CaptivePortalLaunchActivity.class.getSimpleName();
-    private static final boolean DBG = true;
-    public static final int NETWORK_REQUEST_TIMEOUT_IN_MS = 5 * 1000;
-
-    private ConnectivityManager mCm = null;
-    private ConnectivityManager.NetworkCallback mCb = null;
-    /* Progress dialogue when request network connection for captive portal */
-    private AlertDialog mProgressDialog = null;
-    /* Alert dialogue when network request is timeout */
-    private AlertDialog mAlertDialog = null;
-
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mCm = ConnectivityManager.from(this);
-        // Check network connection before loading portal
-        Network network = getNetworkForCaptivePortal();
-        NetworkInfo nwInfo = mCm.getNetworkInfo(network);
-        if (nwInfo == null || !nwInfo.isConnected()) {
-            if (DBG) logd("Network unavailable, request restricted connection");
-            requestNetwork(getIntent());
-        } else {
-            launchCaptivePortal(getIntent(), network);
-        }
-    }
-
-    // show progress dialog during network connecting
-    private void showConnectingProgressDialog() {
-        mProgressDialog = new ProgressDialog(getApplicationContext());
-        mProgressDialog.setMessage(getString(R.string.progress_dialogue_network_connection));
-        mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
-        mProgressDialog.show();
-    }
-
-    // if network request is timeout, show alert dialog with two option: cancel & wait
-    private void showConnectionTimeoutAlertDialog() {
-        mAlertDialog = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialog))
-                .setMessage(getString(R.string.alert_dialogue_network_timeout))
-                .setTitle(getString(R.string.alert_dialogue_network_timeout_title))
-                .setNegativeButton(getString(R.string.quit),
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                // cancel
-                                dismissDialog(mAlertDialog);
-                                finish();
-                            }
-                        })
-                .setPositiveButton(getString(R.string.wait),
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                // wait, request network again
-                                dismissDialog(mAlertDialog);
-                                requestNetwork(getIntent());
-                            }
-                        })
-                .create();
-        mAlertDialog.show();
-    }
-
-    private void requestNetwork(final Intent intent) {
-        NetworkRequest request = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .build();
-
-        mCb = new ConnectivityManager.NetworkCallback() {
-            @Override
-            public void onAvailable(Network network) {
-                if (DBG) logd("Network available: " + network);
-                dismissDialog(mProgressDialog);
-                mCm.bindProcessToNetwork(network);
-                launchCaptivePortal(intent, network);
-            }
-
-            @Override
-            public void onUnavailable() {
-                if (DBG) logd("Network unavailable");
-                dismissDialog(mProgressDialog);
-                showConnectionTimeoutAlertDialog();
-            }
-        };
-        showConnectingProgressDialog();
-        mCm.requestNetwork(request, mCb, NETWORK_REQUEST_TIMEOUT_IN_MS);
-    }
-
-    private void releaseNetworkRequest() {
-        logd("release Network Request");
-        if (mCb != null) {
-            mCm.unregisterNetworkCallback(mCb);
-            mCb = null;
-        }
-    }
-
-    private void dismissDialog(AlertDialog dialog) {
-        if (dialog != null) {
-            dialog.dismiss();
-        }
-    }
-
-    private Network getNetworkForCaptivePortal() {
-        Network[] info = mCm.getAllNetworks();
-        if (!ArrayUtils.isEmpty(info)) {
-            for (Network nw : info) {
-                final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw);
-                if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-                    return nw;
-                }
-            }
-        }
-        return null;
-    }
-
-    private void launchCaptivePortal(final Intent intent, Network network) {
-        String redirectUrl = intent.getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
-                SubscriptionManager.getDefaultVoiceSubscriptionId());
-        if (TextUtils.isEmpty(redirectUrl) || !matchUrl(redirectUrl, subId)) {
-            loge("Launch portal fails due to incorrect redirection URL: " +
-                    Rlog.pii(TAG, redirectUrl));
-            return;
-        }
-        final Intent portalIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
-        portalIntent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
-        portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
-                new CaptivePortal(new ICaptivePortal.Stub() {
-                    @Override
-                    public void appResponse(int response) {
-                        logd("portal response code: " + response);
-                        releaseNetworkRequest();
-                        if (response == APP_RETURN_DISMISSED) {
-                            // Upon success http response code, trigger re-evaluation
-                            CarrierActionUtils.applyCarrierAction(
-                                    CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, intent,
-                                    getApplicationContext());
-                            CarrierActionUtils.applyCarrierAction(
-                                    CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, intent,
-                                    getApplicationContext());
-                            CarrierActionUtils.applyCarrierAction(
-                                    CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS,
-                                    intent, getApplicationContext());
-                        }
-                    }
-                }));
-        portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, redirectUrl);
-        portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        if (DBG) logd("launching portal");
-        startActivity(portalIntent);
-        finish();
-    }
-
-    // match configured redirection url
-    private boolean matchUrl(String url, int subId) {
-        CarrierConfigManager configManager = getApplicationContext()
-                .getSystemService(CarrierConfigManager.class);
-        String[] redirectURLs = configManager.getConfigForSubId(subId).getStringArray(
-                CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
-        if (ArrayUtils.isEmpty(redirectURLs)) {
-            if (DBG) logd("match is unnecessary without any configured redirection url");
-            return true;
-        }
-        for (String redirectURL : redirectURLs) {
-            if (url.startsWith(redirectURL)) {
-                return true;
-            }
-        }
-        if (DBG) loge("no match found for configured redirection url");
-        return false;
-    }
-
-    private static void logd(String s) {
-        Rlog.d(TAG, s);
-    }
-
-    private static void loge(String s) {
-        Rlog.d(TAG, s);
-    }
-}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
new file mode 100644
index 0000000..a5820f2
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -0,0 +1,434 @@
+/*
+ * 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 com.android.carrierdefaultapp;
+
+import android.app.Activity;
+import android.app.LoadedApk;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.Proxy;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.net.http.SslError;
+import android.os.Bundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.TypedValue;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Random;
+
+/**
+ * Activity that launches in response to the captive portal notification
+ * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
+ * This activity requests network connection if there is no available one before loading the real
+ * portal page and apply carrier actions on the portal activation result.
+ */
+public class CaptivePortalLoginActivity extends Activity {
+    private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName();
+    private static final boolean DBG = true;
+
+    private static final int SOCKET_TIMEOUT_MS = 10 * 1000;
+    public static final int NETWORK_REQUEST_TIMEOUT_MS = 5 * 1000;
+
+    private URL mUrl;
+    private Network mNetwork;
+    private NetworkCallback mNetworkCallback;
+    private ConnectivityManager mCm;
+    private WebView mWebView;
+    private MyWebViewClient mWebViewClient;
+    private boolean mLaunchBrowser = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCm = ConnectivityManager.from(this);
+        mUrl = getUrlForCaptivePortal();
+        if (mUrl == null) {
+            done(false);
+            return;
+        }
+        if (DBG) logd(String.format("onCreate for %s", mUrl.toString()));
+        setContentView(R.layout.activity_captive_portal_login);
+        getActionBar().setDisplayShowHomeEnabled(false);
+
+        mWebView = (WebView) findViewById(R.id.webview);
+        mWebView.clearCache(true);
+        WebSettings webSettings = mWebView.getSettings();
+        webSettings.setJavaScriptEnabled(true);
+        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
+        mWebViewClient = new MyWebViewClient();
+        mWebView.setWebViewClient(mWebViewClient);
+        mWebView.setWebChromeClient(new MyWebChromeClient());
+
+        mNetwork = getNetworkForCaptivePortal();
+        if (mNetwork == null) {
+            requestNetworkForCaptivePortal();
+        } else {
+            mCm.bindProcessToNetwork(mNetwork);
+            // Start initial page load so WebView finishes loading proxy settings.
+            // Actual load of mUrl is initiated by MyWebViewClient.
+            mWebView.loadData("", "text/html", null);
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        WebView myWebView = (WebView) findViewById(R.id.webview);
+        if (myWebView.canGoBack() && mWebViewClient.allowBack()) {
+            myWebView.goBack();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        releaseNetworkRequest();
+        if (mLaunchBrowser) {
+            // Give time for this network to become default. After 500ms just proceed.
+            for (int i = 0; i < 5; i++) {
+                // TODO: This misses when mNetwork underlies a VPN.
+                if (mNetwork.equals(mCm.getActiveNetwork())) break;
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            final String url = mUrl.toString();
+            if (DBG) logd("starting activity with intent ACTION_VIEW for " + url);
+            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+        }
+    }
+
+    // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties.
+    private void setWebViewProxy() {
+        LoadedApk loadedApk = getApplication().mLoadedApk;
+        try {
+            Field receiversField = LoadedApk.class.getDeclaredField("mReceivers");
+            receiversField.setAccessible(true);
+            ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
+            for (Object receiverMap : receivers.values()) {
+                for (Object rec : ((ArrayMap) receiverMap).keySet()) {
+                    Class clazz = rec.getClass();
+                    if (clazz.getName().contains("ProxyChangeListener")) {
+                        Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class,
+                                Intent.class);
+                        Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+                        onReceiveMethod.invoke(rec, getApplicationContext(), intent);
+                        Log.v(TAG, "Prompting WebView proxy reload.");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            loge("Exception while setting WebView proxy: " + e);
+        }
+    }
+
+    private void done(boolean success) {
+        if (DBG) logd(String.format("Result success %b for %s", success, mUrl.toString()));
+        if (success) {
+            // Trigger re-evaluation upon success http response code
+            CarrierActionUtils.applyCarrierAction(
+                    CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, getIntent(),
+                    getApplicationContext());
+            CarrierActionUtils.applyCarrierAction(
+                    CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, getIntent(),
+                    getApplicationContext());
+            CarrierActionUtils.applyCarrierAction(
+                    CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(),
+                    getApplicationContext());
+
+        }
+        finishAndRemoveTask();
+    }
+
+    private URL getUrlForCaptivePortal() {
+        String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
+        if (url.isEmpty()) {
+            url = mCm.getCaptivePortalServerUrl();
+        }
+        final CarrierConfigManager configManager = getApplicationContext()
+                .getSystemService(CarrierConfigManager.class);
+        final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.getDefaultVoiceSubscriptionId());
+        final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray(
+                CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
+        if (!ArrayUtils.isEmpty(portalURLs)) {
+            for (String portalUrl : portalURLs) {
+                if (url.startsWith(portalUrl)) {
+                    break;
+                }
+            }
+            url = null;
+        }
+        try {
+            return new URL(url);
+        } catch (MalformedURLException e) {
+            loge("Invalid captive portal URL " + url);
+        }
+        return null;
+    }
+
+    private void testForCaptivePortal() {
+        new Thread(new Runnable() {
+            public void run() {
+                // Give time for captive portal to open.
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                }
+                HttpURLConnection urlConnection = null;
+                int httpResponseCode = 500;
+                int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
+                try {
+                    urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl);
+                    urlConnection.setInstanceFollowRedirects(false);
+                    urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
+                    urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
+                    urlConnection.setUseCaches(false);
+                    urlConnection.getInputStream();
+                    httpResponseCode = urlConnection.getResponseCode();
+                } catch (IOException e) {
+                } finally {
+                    if (urlConnection != null) urlConnection.disconnect();
+                    TrafficStats.setThreadStatsTag(oldTag);
+                }
+                if (httpResponseCode == 204) {
+                    done(true);
+                }
+            }
+        }).start();
+    }
+
+    private Network getNetworkForCaptivePortal() {
+        Network[] info = mCm.getAllNetworks();
+        if (!ArrayUtils.isEmpty(info)) {
+            for (Network nw : info) {
+                final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw);
+                if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                    return nw;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void requestNetworkForCaptivePortal() {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+
+        mNetworkCallback = new ConnectivityManager.NetworkCallback() {
+            @Override
+            public void onAvailable(Network network) {
+                if (DBG) logd("Network available: " + network);
+                mCm.bindProcessToNetwork(network);
+                mNetwork = network;
+                runOnUiThreadIfNotFinishing(() -> {
+                    // Start initial page load so WebView finishes loading proxy settings.
+                    // Actual load of mUrl is initiated by MyWebViewClient.
+                    mWebView.loadData("", "text/html", null);
+                });
+            }
+
+            @Override
+            public void onUnavailable() {
+                if (DBG) logd("Network unavailable");
+                runOnUiThreadIfNotFinishing(() -> {
+                    // Instead of not loading anything in webview, simply load the page and return
+                    // HTTP error page in the absence of network connection.
+                    mWebView.loadUrl(mUrl.toString());
+                });
+            }
+        };
+        logd("request Network for captive portal");
+        mCm.requestNetwork(request, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MS);
+    }
+
+    private void releaseNetworkRequest() {
+        logd("release Network for captive portal");
+        if (mNetworkCallback != null) {
+            mCm.unregisterNetworkCallback(mNetworkCallback);
+            mNetworkCallback = null;
+            mNetwork = null;
+        }
+    }
+
+    private class MyWebViewClient extends WebViewClient {
+        private static final String INTERNAL_ASSETS = "file:///android_asset/";
+        private final String mBrowserBailOutToken = Long.toString(new Random().nextLong());
+        // How many Android device-independent-pixels per scaled-pixel
+        // dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp)
+        private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
+                    getResources().getDisplayMetrics())
+                / TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
+                    getResources().getDisplayMetrics());
+        private int mPagesLoaded;
+
+        // If we haven't finished cleaning up the history, don't allow going back.
+        public boolean allowBack() {
+            return mPagesLoaded > 1;
+        }
+
+        @Override
+        public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            if (url.contains(mBrowserBailOutToken)) {
+                mLaunchBrowser = true;
+                done(false);
+                return;
+            }
+            // The first page load is used only to cause the WebView to
+            // fetch the proxy settings.  Don't update the URL bar, and
+            // don't check if the captive portal is still there.
+            if (mPagesLoaded == 0) return;
+            // For internally generated pages, leave URL bar listing prior URL as this is the URL
+            // the page refers to.
+            if (!url.startsWith(INTERNAL_ASSETS)) {
+                final TextView myUrlBar = (TextView) findViewById(R.id.url_bar);
+                myUrlBar.setText(url);
+            }
+            if (mNetwork != null) {
+                testForCaptivePortal();
+            }
+        }
+
+        @Override
+        public void onPageFinished(WebView view, String url) {
+            mPagesLoaded++;
+            if (mPagesLoaded == 1) {
+                // Now that WebView has loaded at least one page we know it has read in the proxy
+                // settings.  Now prompt the WebView read the Network-specific proxy settings.
+                setWebViewProxy();
+                // Load the real page.
+                view.loadUrl(mUrl.toString());
+                return;
+            } else if (mPagesLoaded == 2) {
+                // Prevent going back to empty first page.
+                view.clearHistory();
+            }
+            if (mNetwork != null) {
+                testForCaptivePortal();
+            }
+        }
+
+        // Convert Android device-independent-pixels (dp) to HTML size.
+        private String dp(int dp) {
+            // HTML px's are scaled just like dp's, so just add "px" suffix.
+            return Integer.toString(dp) + "px";
+        }
+
+        // Convert Android scaled-pixels (sp) to HTML size.
+        private String sp(int sp) {
+            // Convert sp to dp's.
+            float dp = sp * mDpPerSp;
+            // Apply a scale factor to make things look right.
+            dp *= 1.3;
+            // Convert dp's to HTML size.
+            return dp((int) dp);
+        }
+
+        // A web page consisting of a large broken lock icon to indicate SSL failure.
+        private final String SSL_ERROR_HTML = "<html><head><style>"
+                + "body { margin-left:" + dp(48) + "; margin-right:" + dp(48) + "; "
+                + "margin-top:" + dp(96) + "; background-color:#fafafa; }"
+                + "img { width:" + dp(48) + "; height:" + dp(48) + "; }"
+                + "div.warn { font-size:" + sp(16) + "; margin-top:" + dp(16) + "; "
+                + "           opacity:0.87; line-height:1.28; }"
+                + "div.example { font-size:" + sp(14) + "; margin-top:" + dp(16) + "; "
+                + "              opacity:0.54; line-height:1.21905; }"
+                + "a { font-size:" + sp(14) + "; text-decoration:none; text-transform:uppercase; "
+                + "    margin-top:" + dp(24) + "; display:inline-block; color:#4285F4; "
+                + "    height:" + dp(48) + "; font-weight:bold; }"
+                + "</style></head><body><p><img src=quantum_ic_warning_amber_96.png><br>"
+                + "<div class=warn>%s</div>"
+                + "<div class=example>%s</div>" + "<a href=%s>%s</a></body></html>";
+
+        @Override
+        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+            Log.w(TAG, "SSL error (error: " + error.getPrimaryError() + " host: "
+                    // Only show host to avoid leaking private info.
+                    + Uri.parse(error.getUrl()).getHost() + " certificate: "
+                    + error.getCertificate() + "); displaying SSL warning.");
+            final String html = String.format(SSL_ERROR_HTML, getString(R.string.ssl_error_warning),
+                    getString(R.string.ssl_error_example), mBrowserBailOutToken,
+                    getString(R.string.ssl_error_continue));
+            view.loadDataWithBaseURL(INTERNAL_ASSETS, html, "text/HTML", "UTF-8", null);
+        }
+
+        @Override
+        public boolean shouldOverrideUrlLoading(WebView view, String url) {
+            if (url.startsWith("tel:")) {
+                startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url)));
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private class MyWebChromeClient extends WebChromeClient {
+        @Override
+        public void onProgressChanged(WebView view, int newProgress) {
+            final ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
+            myProgressBar.setProgress(newProgress);
+        }
+    }
+
+    private void runOnUiThreadIfNotFinishing(Runnable r) {
+        if (!isFinishing()) {
+            runOnUiThread(r);
+        }
+    }
+
+    private static void logd(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private static void loge(String s) {
+        Rlog.d(TAG, s);
+    }
+
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index d9bd2fc..73ff3a9 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -112,8 +112,10 @@
         logd("onShowCaptivePortalNotification");
         final NotificationManager notificationMgr = context.getSystemService(
                 NotificationManager.class);
-        Intent portalIntent = new Intent(context, CaptivePortalLaunchActivity.class);
+        Intent portalIntent = new Intent(context, CaptivePortalLoginActivity.class);
         portalIntent.putExtras(intent);
+        portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
+                | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, portalIntent,
                 PendingIntent.FLAG_UPDATE_CURRENT);
         Notification notification = getNotification(context, R.string.portal_notification_id,
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java
deleted file mode 100644
index 8a18d72..0000000
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.android.carrierdefaultapp;
-
-import android.annotation.TargetApi;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkRequest;
-
-import com.android.internal.telephony.TelephonyIntents;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class LaunchCaptivePortalActivityTest extends
-        CarrierDefaultActivityTestCase<CaptivePortalLaunchActivity> {
-
-    @Mock
-    private ConnectivityManager mCm;
-    @Mock
-    private NetworkInfo mNetworkInfo;
-    @Mock
-    private Network mNetwork;
-
-    @Captor
-    private ArgumentCaptor<Integer> mInt;
-    @Captor
-    private ArgumentCaptor<NetworkRequest> mNetworkReq;
-
-    private NetworkCapabilities mNetworkCapabilities;
-
-    public LaunchCaptivePortalActivityTest() {
-        super(CaptivePortalLaunchActivity.class);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        injectSystemService(ConnectivityManager.class, mCm);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    @Override
-    protected Intent createActivityIntent() {
-        Intent intent = new Intent(getInstrumentation().getTargetContext(),
-                CaptivePortalLaunchActivity.class);
-        intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, "url");
-        return intent;
-    }
-
-    @Test
-    public void testWithoutInternetConnection() throws Throwable {
-        startActivity();
-        TestContext.waitForMs(100);
-        verify(mCm, atLeast(1)).requestNetwork(mNetworkReq.capture(), any(), mInt.capture());
-        // verify network request
-        assert(mNetworkReq.getValue().networkCapabilities.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET));
-        assert(mNetworkReq.getValue().networkCapabilities.hasTransport(
-                NetworkCapabilities.TRANSPORT_CELLULAR));
-        assertFalse(mNetworkReq.getValue().networkCapabilities.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
-        assertEquals(CaptivePortalLaunchActivity.NETWORK_REQUEST_TIMEOUT_IN_MS,
-                (int) mInt.getValue());
-        // verify captive portal app is not launched due to unavailable network
-        assertNull(getStartedActivityIntent());
-        stopActivity();
-    }
-
-    @Test
-    public void testWithInternetConnection() throws Throwable {
-        // Mock internet connection
-        mNetworkCapabilities = new NetworkCapabilities()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        doReturn(new Network[]{mNetwork}).when(mCm).getAllNetworks();
-        doReturn(mNetworkCapabilities).when(mCm).getNetworkCapabilities(eq(mNetwork));
-        doReturn(mNetworkInfo).when(mCm).getNetworkInfo(eq(mNetwork));
-        doReturn(true).when(mNetworkInfo).isConnected();
-
-        startActivity();
-        TestContext.waitForMs(100);
-        // verify there is no network request with internet connection
-        verify(mCm, times(0)).requestNetwork(any(), any(), anyInt());
-        // verify captive portal app is launched
-        assertNotNull(getStartedActivityIntent());
-        assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN,
-                getStartedActivityIntent().getAction());
-        stopActivity();
-    }
-}
diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
index 5e49d05..c6837fa 100644
--- a/packages/SystemUI/res/layout/pip_menu_activity.xml
+++ b/packages/SystemUI/res/layout/pip_menu_activity.xml
@@ -15,50 +15,55 @@
 -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/menu"
+    android:id="@+id/background"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="#4D000000">
-    <!-- The above background is only for the dismiss button ripple to show. -->
+    android:layout_height="match_parent">
 
-    <ImageView
-        android:id="@+id/dismiss"
-        android:layout_width="@dimen/pip_action_size"
-        android:layout_height="@dimen/pip_action_size"
-        android:layout_gravity="top|end"
-        android:padding="@dimen/pip_action_padding"
-        android:contentDescription="@string/pip_phone_close"
-        android:src="@drawable/ic_close_white"
-        android:background="?android:selectableItemBackgroundBorderless" />
+  <!-- Menu layout -->
+  <FrameLayout
+      android:id="@+id/menu_container"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent">
 
-    <!-- The margins for this container is calculated in the code depending on whether the
-         actions_container is visible. -->
-    <FrameLayout
-        android:id="@+id/expand_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <ImageView
-            android:id="@+id/expand_button"
-            android:layout_width="60dp"
-            android:layout_height="60dp"
-            android:layout_gravity="center"
-            android:contentDescription="@string/pip_phone_expand"
-            android:background="?android:selectableItemBackgroundBorderless" />
-    </FrameLayout>
+      <ImageView
+          android:id="@+id/dismiss"
+          android:layout_width="@dimen/pip_action_size"
+          android:layout_height="@dimen/pip_action_size"
+          android:layout_gravity="top|end"
+          android:padding="@dimen/pip_action_padding"
+          android:contentDescription="@string/pip_phone_close"
+          android:src="@drawable/ic_close_white"
+          android:background="?android:selectableItemBackgroundBorderless" />
 
-    <FrameLayout
-        android:id="@+id/actions_container"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/pip_action_size"
-        android:layout_gravity="bottom"
-        android:visibility="invisible">
-        <LinearLayout
-            android:id="@+id/actions_group"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="center_horizontal"
-            android:orientation="horizontal"
-            android:divider="@android:color/transparent"
-            android:showDividers="middle" />
-    </FrameLayout>
+      <!-- The margins for this container is calculated in the code depending on whether the
+           actions_container is visible. -->
+      <FrameLayout
+          android:id="@+id/expand_container"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent">
+          <ImageView
+              android:id="@+id/expand_button"
+              android:layout_width="60dp"
+              android:layout_height="60dp"
+              android:layout_gravity="center"
+              android:contentDescription="@string/pip_phone_expand"
+              android:background="?android:selectableItemBackgroundBorderless" />
+      </FrameLayout>
+
+      <FrameLayout
+          android:id="@+id/actions_container"
+          android:layout_width="match_parent"
+          android:layout_height="@dimen/pip_action_size"
+          android:layout_gravity="bottom"
+          android:visibility="invisible">
+          <LinearLayout
+              android:id="@+id/actions_group"
+              android:layout_width="wrap_content"
+              android:layout_height="match_parent"
+              android:layout_gravity="center_horizontal"
+              android:orientation="horizontal"
+              android:divider="@android:color/transparent"
+              android:showDividers="middle" />
+      </FrameLayout>
+  </FrameLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 86e2c49..2f9c3fc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -20,10 +20,12 @@
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS;
+import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_MENU;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -35,6 +37,8 @@
 import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Handler;
@@ -71,13 +75,19 @@
     public static final int MESSAGE_POKE_MENU = 2;
     public static final int MESSAGE_HIDE_MENU = 3;
     public static final int MESSAGE_UPDATE_ACTIONS = 4;
+    public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
 
     private static final long INITIAL_DISMISS_DELAY = 2000;
     private static final long POST_INTERACTION_DISMISS_DELAY = 1500;
     private static final long MENU_FADE_DURATION = 125;
 
+    private static final float MENU_BACKGROUND_ALPHA = 0.3f;
+    private static final float DISMISS_BACKGROUND_ALPHA = 0.8f;
+
     private boolean mMenuVisible;
     private final List<RemoteAction> mActions = new ArrayList<>();
+    private View mViewRoot;
+    private Drawable mBackgroundDrawable;
     private View mMenuContainer;
     private LinearLayout mActionsGroup;
     private View mDismissButton;
@@ -85,6 +95,14 @@
     private int mBetweenActionPaddingLand;
 
     private ObjectAnimator mMenuContainerAnimator;
+    private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    final float alpha = (float) animation.getAnimatedValue();
+                    mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA*alpha*255));
+                }
+            };
 
     private PointF mDownPosition = new PointF();
     private PointF mDownDelta = new PointF();
@@ -109,6 +127,10 @@
                     Pair<Rect, ParceledListSlice> data = (Pair<Rect, ParceledListSlice>) msg.obj;
                     setActions(data.first, data.second.getList());
                     break;
+                case MESSAGE_UPDATE_DISMISS_FRACTION:
+                    float fraction = (float) msg.obj;
+                    updateDismissFraction(fraction);
+                    break;
             }
         }
     });
@@ -130,7 +152,12 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.pip_menu_activity);
 
-        mMenuContainer = findViewById(R.id.menu);
+        mBackgroundDrawable = new ColorDrawable(Color.BLACK);
+        mBackgroundDrawable.setAlpha(0);
+        mViewRoot = findViewById(R.id.background);
+        mViewRoot.setBackground(mBackgroundDrawable);
+        mMenuContainer = findViewById(R.id.menu_container);
+        mMenuContainer.setAlpha(0);
         mMenuContainer.setOnClickListener((v) -> {
             expandPip();
         });
@@ -222,10 +249,10 @@
 
     private void showMenu(Rect stackBounds, Rect movementBounds) {
         if (!mMenuVisible) {
+            updateActionViews(stackBounds);
             if (mMenuContainerAnimator != null) {
                 mMenuContainerAnimator.cancel();
             }
-
             notifyMenuVisibility(true);
             updateExpandButtonFromBounds(stackBounds, movementBounds);
             mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
@@ -238,6 +265,7 @@
                     repostDelayedFinish(INITIAL_DISMISS_DELAY);
                 }
             });
+            mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
             mMenuContainerAnimator.start();
         } else {
             repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
@@ -269,20 +297,24 @@
                     }
                 }
             });
+            mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
             mMenuContainerAnimator.start();
         }
     }
 
     private void updateFromIntent(Intent intent) {
-        Rect stackBounds = Rect.unflattenFromString(intent.getStringExtra(EXTRA_STACK_BOUNDS));
-        Rect movementBounds = Rect.unflattenFromString(intent.getStringExtra(
-                EXTRA_MOVEMENT_BOUNDS));
         mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
         ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS);
         if (actions != null) {
-            setActions(stackBounds, actions.getList());
+            mActions.clear();
+            mActions.addAll(actions.getList());
         }
-        showMenu(stackBounds, movementBounds);
+        if (intent.getBooleanExtra(EXTRA_SHOW_MENU, false)) {
+            Rect stackBounds = Rect.unflattenFromString(intent.getStringExtra(EXTRA_STACK_BOUNDS));
+            Rect movementBounds = Rect.unflattenFromString(intent.getStringExtra(
+                    EXTRA_MOVEMENT_BOUNDS));
+            showMenu(stackBounds, movementBounds);
+        }
     }
 
     private void updateExpandButtonFromBounds(Rect stackBounds, Rect movementBounds) {
@@ -365,6 +397,19 @@
         }
     }
 
+    private void updateDismissFraction(float fraction) {
+        int alpha;
+        if (mMenuVisible) {
+            mMenuContainer.setAlpha(1-fraction);
+            final float interpolatedAlpha =
+                    MENU_BACKGROUND_ALPHA * (1.0f - fraction) + DISMISS_BACKGROUND_ALPHA * fraction;
+            alpha = (int) (interpolatedAlpha*255);
+        } else {
+            alpha = (int) (fraction*DISMISS_BACKGROUND_ALPHA*255);
+        }
+        mBackgroundDrawable.setAlpha(alpha);
+    }
+
     private void notifyRegisterInputConsumer() {
         Message m = Message.obtain();
         m.what = PipMenuActivityController.MESSAGE_REGISTER_INPUT_CONSUMER;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index badf64b..7dc455b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -42,7 +42,7 @@
 import java.util.List;
 
 /**
- * Manages the PiP menu activity.
+ * Manages the PiP menu activity which can show menu options or a scrim.
  *
  * The current media session provides actions whenever there are no valid actions provided by the
  * current PiP activity. Otherwise, those actions always take precedence.
@@ -55,6 +55,7 @@
     public static final String EXTRA_ACTIONS = "actions";
     public static final String EXTRA_STACK_BOUNDS = "stack_bounds";
     public static final String EXTRA_MOVEMENT_BOUNDS = "movement_bounds";
+    public static final String EXTRA_SHOW_MENU = "show_menu";
 
     public static final int MESSAGE_MENU_VISIBILITY_CHANGED = 100;
     public static final int MESSAGE_EXPAND_PIP = 101;
@@ -101,6 +102,7 @@
     private ParceledListSlice mMediaActions;
     private boolean mMenuVisible;
 
+    private boolean mStartActivityRequested;
     private Messenger mToActivityMessenger;
     private Messenger mMessenger = new Messenger(new Handler() {
         @Override
@@ -135,6 +137,7 @@
                 }
                 case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
                     mToActivityMessenger = msg.replyTo;
+                    mStartActivityRequested = false;
                     // Mark the menu as invisible once the activity finishes as well
                     if (mToActivityMessenger == null) {
                         onMenuVisibilityChanged(false, true /* resize */);
@@ -179,6 +182,25 @@
     }
 
     /**
+     * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
+     */
+    public void setDismissFraction(float fraction) {
+        if (mToActivityMessenger != null) {
+            Message m = Message.obtain();
+            m.what = PipMenuActivity.MESSAGE_UPDATE_DISMISS_FRACTION;
+            m.obj = fraction;
+            try {
+                mToActivityMessenger.send(m);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not notify menu to show", e);
+            }
+        } else if (!mStartActivityRequested) {
+            startMenuActivity(null /* stackBounds */, null /* movementBounds */,
+                    false /* showMenu */);
+        }
+    }
+
+    /**
      * Shows the menu activity.
      */
     public void showMenu(Rect stackBounds, Rect movementBounds) {
@@ -191,28 +213,8 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "Could not notify menu to show", e);
             }
-        } else {
-            // Start the menu activity on the top task of the pinned stack
-            try {
-                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
-                if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
-                        pinnedStackInfo.taskIds.length > 0) {
-                    Intent intent = new Intent(mContext, PipMenuActivity.class);
-                    intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
-                    intent.putExtra(EXTRA_ACTIONS, resolveMenuActions());
-                    intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds.flattenToString());
-                    intent.putExtra(EXTRA_MOVEMENT_BOUNDS, movementBounds.flattenToString());
-                    ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
-                    options.setLaunchTaskId(
-                            pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
-                    options.setTaskOverlay(true, true /* canResume */);
-                    mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
-                } else {
-                    Log.e(TAG, "No PIP tasks found");
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error showing PIP menu activity", e);
-            }
+        } else if (!mStartActivityRequested) {
+            startMenuActivity(stackBounds, movementBounds, true /* showMenu */);
         }
     }
 
@@ -272,6 +274,39 @@
     }
 
     /**
+     * Starts the menu activity on the top task of the pinned stack.
+     */
+    private void startMenuActivity(Rect stackBounds, Rect movementBounds, boolean showMenu) {
+        try {
+            StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
+                    pinnedStackInfo.taskIds.length > 0) {
+                Intent intent = new Intent(mContext, PipMenuActivity.class);
+                intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
+                intent.putExtra(EXTRA_ACTIONS, resolveMenuActions());
+                if (stackBounds != null) {
+                    intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds.flattenToString());
+                }
+                if (movementBounds != null) {
+                    intent.putExtra(EXTRA_MOVEMENT_BOUNDS, movementBounds.flattenToString());
+                }
+                intent.putExtra(EXTRA_SHOW_MENU, showMenu);
+                ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+                options.setLaunchTaskId(
+                        pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
+                options.setTaskOverlay(true, true /* canResume */);
+                mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
+                mStartActivityRequested = true;
+            } else {
+                Log.e(TAG, "No PIP tasks found");
+            }
+        } catch (RemoteException e) {
+            mStartActivityRequested = false;
+            Log.e(TAG, "Error showing PIP menu activity", e);
+        }
+    }
+
+    /**
      * Updates the PiP menu activity with the best set of actions provided.
      */
     private void updateMenuActions() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 49d89a2..c4cf28c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -65,9 +65,9 @@
     private static final int IME_SHIFT_DURATION = 300;
 
     // The fraction of the stack width that the user has to drag offscreen to minimize the PiP
-    private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.2f;
-    // The fraction of the stack height that the user has to drag offscreen to minimize the PiP
-    private static final float DISMISS_OFFSCREEN_FRACTION = 0.35f;
+    private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f;
+    // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP
+    private static final float DISMISS_OFFSCREEN_FRACTION = 0.15f;
 
     private Context mContext;
     private IActivityManager mActivityManager;
@@ -234,12 +234,16 @@
     /**
      * Animates the PiP to the minimized state, slightly offscreen.
      */
-    Rect animateToClosestMinimizedState(Rect movementBounds) {
+    Rect animateToClosestMinimizedState(Rect movementBounds,
+            AnimatorUpdateListener updateListener) {
         cancelAnimations();
         Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds);
         if (!mBounds.equals(toBounds)) {
             mBoundsAnimator = createAnimationToBounds(mBounds, toBounds,
                     MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN, mUpdateBoundsListener);
+            if (updateListener != null) {
+                mBoundsAnimator.addUpdateListener(updateListener);
+            }
             mBoundsAnimator.start();
         }
         return toBounds;
@@ -248,7 +252,8 @@
     /**
      * Flings the PiP to the closest snap target.
      */
-    Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds) {
+    Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
+            AnimatorUpdateListener listener) {
         cancelAnimations();
         Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
                 velocityX, velocityY);
@@ -258,6 +263,9 @@
             mFlingAnimationUtils.apply(mBoundsAnimator, 0,
                     distanceBetweenRectOffsets(mBounds, toBounds),
                     velocity);
+            if (listener != null) {
+                mBoundsAnimator.addUpdateListener(listener);
+            }
             mBoundsAnimator.start();
         }
         return toBounds;
@@ -266,12 +274,15 @@
     /**
      * Animates the PiP to the closest snap target.
      */
-    Rect animateToClosestSnapTarget(Rect movementBounds) {
+    Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener listener) {
         cancelAnimations();
         Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
         if (!mBounds.equals(toBounds)) {
             mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION,
                     FAST_OUT_SLOW_IN, mUpdateBoundsListener);
+            if (listener != null) {
+                mBoundsAnimator.addUpdateListener(listener);
+            }
             mBoundsAnimator.start();
         }
         return toBounds;
@@ -316,7 +327,7 @@
     /**
      * Animates the dismissal of the PiP off the edge of the screen.
      */
-    Rect animateDragToEdgeDismiss(Rect pipBounds) {
+    Rect animateDragToEdgeDismiss(Rect pipBounds, AnimatorUpdateListener listener) {
         cancelAnimations();
         Point displaySize = new Point();
         mContext.getDisplay().getRealSize(displaySize);
@@ -330,6 +341,9 @@
                 dismissPip();
             }
         });
+        if (listener != null) {
+            mBoundsAnimator.addUpdateListener(listener);
+        }
         mBoundsAnimator.start();
         return toBounds;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 80231a9..c52fc3e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.IActivityManager;
 import android.content.Context;
 import android.graphics.Point;
@@ -55,7 +57,7 @@
 
     // Allow dragging the PIP to a location to close it
     private static final boolean ENABLE_DISMISS_DRAG_TO_TARGET = false;
-    private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = false;
+    private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = true;
 
     private final Context mContext;
     private final IActivityManager mActivityManager;
@@ -87,6 +89,13 @@
             }
         }
     };
+    private ValueAnimator.AnimatorUpdateListener mUpdateScrimListener =
+            new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    updateDismissFraction();
+                }
+            };
 
     // Behaviour states
     private boolean mIsMenuVisible;
@@ -124,7 +133,7 @@
         @Override
         public void onPipMinimize() {
             setMinimizedStateInternal(true);
-            mMotionHelper.animateToClosestMinimizedState(mMovementBounds);
+            mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateListener */);
         }
 
         @Override
@@ -331,6 +340,22 @@
     }
 
     /**
+     * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
+     */
+    void updateDismissFraction() {
+        if (mMenuController != null) {
+            Rect bounds = mMotionHelper.getBounds();
+            final float target = mMovementBounds.bottom + bounds.height();
+            float fraction = 0f;
+            if (bounds.bottom > target) {
+                final float distance = bounds.bottom - target;
+                fraction = Math.min(distance / bounds.height(), 1f);
+            }
+            mMenuController.setDismissFraction(fraction);
+        }
+    }
+
+    /**
      * Sets the controller to update the system of changes from user interaction.
      */
     void setPinnedStackController(IPinnedStackController controller) {
@@ -465,6 +490,9 @@
                 if (ENABLE_DISMISS_DRAG_TO_TARGET) {
                     mDismissViewController.updateDismissTarget(mTmpBounds);
                 }
+                if (ENABLE_DISMISS_DRAG_TO_EDGE) {
+                    updateDismissFraction();
+                }
                 return true;
             }
             return false;
@@ -502,7 +530,8 @@
                 boolean isFlingToBot = isFlingTowardsEdge(touchState, 4 /* bottom */);
                 if (ENABLE_DISMISS_DRAG_TO_EDGE
                         && (mMotionHelper.shouldDismissPip() || isFlingToBot)) {
-                    mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds());
+                    mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds(),
+                            mUpdateScrimListener);
                     MetricsLogger.action(mContext,
                             MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
                             METRIC_VALUE_DISMISSED_BY_DRAG);
@@ -517,7 +546,8 @@
                         // minimize offset adjusted
                         mMenuController.hideMenu();
                     } else {
-                        mMotionHelper.animateToClosestMinimizedState(mMovementBounds);
+                        mMotionHelper.animateToClosestMinimizedState(mMovementBounds,
+                                mUpdateScrimListener);
                     }
                     return true;
                 }
@@ -536,13 +566,14 @@
                 final PointF vel = mTouchState.getVelocity();
                 final float velocity = PointF.length(vel.x, vel.y);
                 if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-                    mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds);
+                    mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
+                            mUpdateScrimListener);
                 } else {
-                    mMotionHelper.animateToClosestSnapTarget(mMovementBounds);
+                    mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener);
                 }
             } else if (mIsMinimized) {
                 // This was a tap, so no longer minimized
-                mMotionHelper.animateToClosestSnapTarget(mMovementBounds);
+                mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* listener */);
                 setMinimizedStateInternal(false);
             } else if (!mIsMenuVisible) {
                 mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 03582ea..3800f29 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3604,6 +3604,10 @@
     // ACTION: Settings > Search > Click saved queries
     ACTION_CLICK_SETTINGS_SEARCH_SAVED_QUERY = 881;
 
+    // OPEN: Settings > Security & screen lock -> Lock screen preferences
+    // CATEGORY: SETTINGS
+    SETTINGS_LOCK_SCREEN_PREFERENCES = 882;
+
     // ---- End O Constants, all O constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 71bfa64..e1426fd 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -245,9 +245,12 @@
 
                     // STOPSHIP Remove the whitelist.
                     if ("com.google.android.talk".equals(callingPackage)
-                            || "com.google.android.quicksearchbox".equals(callingPackage)) {
+                            || "com.google.android.quicksearchbox".equals(callingPackage)
+                            || "com.google.android.googlequicksearchbox".equals(callingPackage)
+                            ) {
                         return false;
                     }
+                    // STOPSHIP Change it to 'e'.
                     Slog.wtfStack(TAG, message + " by " + callingPackage + " for another profile "
                             + targetUserId + " from " + callingUserId);
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 51b91f4..7862523 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5276,23 +5276,42 @@
         }
     }
 
+
+    /**
+     * @deprecated use {@link #isDataEnabled()} instead.
+     * @hide
+     */
+    @SystemApi
+    @Deprecated
+    public boolean getDataEnabled() {
+        return isDataEnabled();
+    }
+
     /**
      * Returns whether mobile data is enabled or not.
      *
-     * <p>Requires Permission:
-     *     {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
-     *     {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
-     *     calling app has carrier privileges.
+     * <p>Requires one of the following permissions:
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
+     * calling app has carrier privileges.
+     *
+     * <p>Note that this does not take into account any data restrictions that may be present on the
+     * calling app. Such restrictions may be inspected with
+     * {@link ConnectivityManager#getRestrictBackgroundStatus}.
      *
      * @return true if mobile data is enabled.
      *
      * @see #hasCarrierPrivileges
      */
-    public boolean getDataEnabled() {
+    @SuppressWarnings("deprecation")
+    public boolean isDataEnabled() {
         return getDataEnabled(getSubId());
     }
 
-    /** @hide */
+    /**
+     * @deprecated use {@link #isDataEnabled(int)} instead.
+     * @hide
+     */
     @SystemApi
     public boolean getDataEnabled(int subId) {
         boolean retVal = false;