Merge "ScrimController: Wait with clearing wakup animation until done" into oc-dr1-dev
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 44c88e1..1a2968f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -65,7 +65,8 @@
     private static native void nativeSetSize(long nativeObject, int w, int h);
     private static native void nativeSetTransparentRegionHint(long nativeObject, Region region);
     private static native void nativeSetAlpha(long nativeObject, float alpha);
-    private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx, float dsdy, float dtdy);
+    private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx,
+            float dtdy, float dsdy);
     private static native void nativeSetFlags(long nativeObject, int flags, int mask);
     private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
     private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b);
diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml
index 7fafef7..3a81c13 100644
--- a/data/etc/framework-sysconfig.xml
+++ b/data/etc/framework-sysconfig.xml
@@ -28,4 +28,6 @@
     <backup-transport-whitelisted-service
         service="android/com.android.internal.backup.LocalTransportService" />
 
+    <!-- Whitelist of bundled applications which all handle URLs to their websites by default -->
+    <app-link package="com.android.carrierdefaultapp" />
 </config>
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index c309133..1cd7b61 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,6 +34,7 @@
             <intent-filter>
                 <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
                 <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_RESET" />
+                <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE" />
                 <action android:name="android.intent.action.LOCALE_CHANGED" />
             </intent-filter>
         </receiver>
@@ -43,10 +44,24 @@
             android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
             android:label="@string/action_bar_label"
             android:theme="@style/AppTheme"
-            android:configChanges="keyboardHidden|orientation|screenSize" >
+            android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
+
+        <activity-alias
+            android:name="com.android.carrierdefaultapp.URLHandlerActivity"
+            android:targetActivity="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
+            android:enabled="false" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="*" />
+            </intent-filter>
+        </activity-alias>
     </application>
 </manifest>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 6194b87..b0052cc 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -20,6 +20,9 @@
 import android.app.LoadedApk;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
@@ -34,6 +37,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.TypedValue;
@@ -68,7 +72,7 @@
     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 static final int NETWORK_REQUEST_TIMEOUT_MS = 5 * 1000;
 
     private URL mUrl;
     private Network mNetwork;
@@ -188,16 +192,19 @@
             CarrierActionUtils.applyCarrierAction(
                     CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(),
                     getApplicationContext());
-
+            CarrierActionUtils.applyCarrierAction(
+                    CarrierActionUtils.CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER, getIntent(),
+                    getApplicationContext());
+            CarrierActionUtils.applyCarrierAction(
+                    CarrierActionUtils.CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL, getIntent(),
+                    getApplicationContext());
         }
         finishAndRemoveTask();
     }
 
     private URL getUrlForCaptivePortal() {
         String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
-        if (url.isEmpty()) {
-            url = mCm.getCaptivePortalServerUrl();
-        }
+        if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl();
         final CarrierConfigManager configManager = getApplicationContext()
                 .getSystemService(CarrierConfigManager.class);
         final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
@@ -437,6 +444,27 @@
         }
     }
 
+    /**
+     * This alias presents the target activity, CaptivePortalLoginActivity, as a independent
+     * entity with its own intent filter to handle URL links. This alias will be enabled/disabled
+     * dynamically to handle url links based on the network conditions.
+     */
+    public static String getAlias(Context context) {
+        try {
+            PackageInfo p = context.getPackageManager().getPackageInfo(context.getPackageName(),
+                    PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS);
+            for (ActivityInfo activityInfo : p.activities) {
+                String targetActivity = activityInfo.targetActivity;
+                if (CaptivePortalLoginActivity.class.getName().equals(targetActivity)) {
+                    return activityInfo.name;
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
     private static void logd(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 0213306..a2bf964 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -19,8 +19,10 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.telephony.SubscriptionManager;
@@ -49,6 +51,10 @@
     public static final int CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION          = 4;
     public static final int CARRIER_ACTION_SHOW_NO_DATA_SERVICE_NOTIFICATION = 5;
     public static final int CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS          = 6;
+    public static final int CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER        = 7;
+    public static final int CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER       = 8;
+    public static final int CARRIER_ACTION_REGISTER_DEFAULT_NETWORK_AVAIL    = 9;
+    public static final int CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL  = 10;
 
     public static void applyCarrierAction(int actionIdx, Intent intent, Context context) {
         switch (actionIdx) {
@@ -73,6 +79,18 @@
             case CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS:
                 onCancelAllNotifications(context);
                 break;
+            case CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER:
+                onEnableDefaultURLHandler(context);
+                break;
+            case CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER:
+                onDisableDefaultURLHandler(context);
+                break;
+            case CARRIER_ACTION_REGISTER_DEFAULT_NETWORK_AVAIL:
+                onRegisterDefaultNetworkAvail(intent, context);
+                break;
+            case CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL:
+                onDeregisterDefaultNetworkAvail(intent, context);
+                break;
             default:
                 loge("unsupported carrier action index: " + actionIdx);
         }
@@ -94,6 +112,38 @@
         telephonyMgr.carrierActionSetMeteredApnsEnabled(subId, ENABLE);
     }
 
+    private static void onEnableDefaultURLHandler(Context context) {
+        logd("onEnableDefaultURLHandler");
+        final PackageManager pm = context.getPackageManager();
+        pm.setComponentEnabledSetting(
+                new ComponentName(context, CaptivePortalLoginActivity.getAlias(context)),
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+    }
+
+    private static void onDisableDefaultURLHandler(Context context) {
+        logd("onDisableDefaultURLHandler");
+        final PackageManager pm = context.getPackageManager();
+        pm.setComponentEnabledSetting(
+                new ComponentName(context, CaptivePortalLoginActivity.getAlias(context)),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+    }
+
+    private static void onRegisterDefaultNetworkAvail(Intent intent, Context context) {
+        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.getDefaultVoiceSubscriptionId());
+        logd("onRegisterDefaultNetworkAvail subId: " + subId);
+        final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+        telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, true);
+    }
+
+    private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) {
+        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.getDefaultVoiceSubscriptionId());
+        logd("onDeregisterDefaultNetworkAvail subId: " + subId);
+        final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+        telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, false);
+    }
+
     private static void onDisableRadio(Intent intent, Context context) {
         int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index d5d0b79..02c61d7 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -22,7 +22,6 @@
 import android.telephony.Rlog;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.ArrayUtils;
@@ -95,6 +94,12 @@
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET);
                     break;
+                case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
+                    configs = b.getStringArray(CarrierConfigManager
+                            .KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE);
+                    arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents
+                            .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
+                    break;
                 default:
                     Rlog.e(TAG, "load carrier config failure with un-configured key: " +
                             intent.getAction());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
index 1240e05..cc7798e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -31,4 +31,5 @@
     void sendRecentsDrawnEvent();
     void sendDockingTopTaskEvent(int dragMode, in Rect initialRect);
     void sendLaunchRecentsEvent();
+    void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index de2ace4..86dde54 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -54,9 +54,11 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -612,6 +614,14 @@
                 }
             });
         }
+
+        // This will catch the cases when a user launches from recents to another app
+        // (and vice versa) that is not in the recents stack (such as home or bugreport) and it
+        // would not reset the wait for transition flag. This will catch it and make sure that the
+        // flag is reset.
+        if (!event.visible) {
+            mImpl.setWaitingForTransitionStart(false);
+        }
     }
 
     /**
@@ -684,6 +694,11 @@
         }
     }
 
+    public final void onBusEvent(LaunchTaskFailedEvent event) {
+        // Reset the transition when tasks fail to launch
+        mImpl.setWaitingForTransitionStart(false);
+    }
+
     public final void onBusEvent(ConfigurationChangedEvent event) {
         // Update the configuration for the Recents component when the activity configuration
         // changes as well
@@ -711,6 +726,25 @@
         }
     }
 
+    public final void onBusEvent(SetWaitingForTransitionStartEvent event) {
+        int processUser = sSystemServicesProxy.getProcessUser();
+        if (sSystemServicesProxy.isSystemUser(processUser)) {
+            mImpl.setWaitingForTransitionStart(event.waitingForTransitionStart);
+        } else {
+            postToSystemUser(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mUserToSystemCallbacks.setWaitingForTransitionStartEvent(
+                                event.waitingForTransitionStart);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                }
+            });
+        }
+    }
+
     /**
      * Attempts to register with the system user.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 7cd93bb..86e93fd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -24,12 +24,11 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -208,6 +207,20 @@
     protected static RecentsTaskLoadPlan sInstanceLoadPlan;
     // Stores the last pinned task time
     protected static long sLastPipTime = -1;
+    // Stores whether we are waiting for a transition to/from recents to start. During this time,
+    // we disallow the user from manually toggling recents until the transition has started.
+    private static boolean mWaitingForTransitionStart = false;
+    // Stores whether or not the user toggled while we were waiting for a transition to/from
+    // recents. In this case, we defer the toggle state until then and apply it immediately after.
+    private static boolean mToggleFollowingTransitionStart = true;
+
+    private ActivityOptions.OnAnimationStartedListener mResetToggleFlagListener =
+            new OnAnimationStartedListener() {
+                @Override
+                public void onAnimationStarted() {
+                    setWaitingForTransitionStart(false);
+                }
+            };
 
     protected Context mContext;
     protected Handler mHandler;
@@ -365,6 +378,11 @@
             return;
         }
 
+        if (mWaitingForTransitionStart) {
+            mToggleFollowingTransitionStart = true;
+            return;
+        }
+
         mDraggingInRecents = false;
         mLaunchedWhileDocking = false;
         mTriggeredFromAltTab = false;
@@ -638,6 +656,18 @@
         }
     }
 
+    public void setWaitingForTransitionStart(boolean waitingForTransitionStart) {
+        if (mWaitingForTransitionStart == waitingForTransitionStart) {
+            return;
+        }
+
+        mWaitingForTransitionStart = waitingForTransitionStart;
+        if (!waitingForTransitionStart && mToggleFollowingTransitionStart) {
+            toggleRecents(DividerView.INVALID_RECENTS_GROW_TARGET);
+        }
+        mToggleFollowingTransitionStart = false;
+    }
+
     /**
      * Returns the preloaded load plan and invalidates it.
      */
@@ -865,8 +895,9 @@
             }
             AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
             specs.toArray(specsArray);
+
             return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    specsArray, mHandler, null, this), null);
+                    specsArray, mHandler, mResetToggleFlagListener, this), null);
         } else {
             // Update the destination rect
             Task toTask = new Task();
@@ -884,8 +915,10 @@
                         return Lists.newArrayList(new AppTransitionAnimationSpec(
                                 toTask.key.id, thumbnail, rect));
                     });
+
             return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
-                    mHandler, future.getFuture(), null, false /* scaleUp */), future);
+                    mHandler, future.getFuture(), mResetToggleFlagListener, false /* scaleUp */),
+                    future);
         }
     }
 
@@ -991,6 +1024,10 @@
         launchState.launchedToTaskId = runningTaskId;
         launchState.launchedWithAltTab = mTriggeredFromAltTab;
 
+        // Disable toggling of recents between starting the activity and it is visible and the app
+        // has started its transition into recents.
+        setWaitingForTransitionStart(useThumbnailTransition);
+
         // Preload the icon (this will be a null-op if we have preloaded the icon already in
         // preloadRecents())
         preloadIcon(runningTaskId);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
index 3921a20..1285626 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
@@ -29,6 +29,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.ForegroundThread;
 
@@ -105,4 +106,10 @@
     public void sendLaunchRecentsEvent() throws RemoteException {
         EventBus.getDefault().post(new RecentsActivityStartingEvent());
     }
+
+    @Override
+    public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
+        EventBus.getDefault().post(new SetWaitingForTransitionStartEvent(
+                waitingForTransitionStart));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java
new file mode 100644
index 0000000..d9cf5fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java
@@ -0,0 +1,31 @@
+/*
+ * 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.systemui.recents.events.component;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when we are setting/resetting the flag to wait for the transition to start.
+ */
+public class SetWaitingForTransitionStartEvent extends EventBus.Event {
+
+    public final boolean waitingForTransitionStart;
+
+    public SetWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
+        this.waitingForTransitionStart = waitingForTransitionStart;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 968b77f..67685b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -54,6 +54,7 @@
 import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
@@ -117,31 +118,58 @@
             final Rect windowRect = Recents.getSystemServices().getWindowRect();
             transitionFuture = getAppTransitionFuture(
                     () -> composeAnimationSpecs(task, stackView, destinationStack, windowRect));
-            animStartedListener = () -> {
-                // If we are launching into another task, cancel the previous task's
-                // window transition
-                EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                stackView.cancelAllTaskViewAnimations();
+            animStartedListener = new OnAnimationStartedListener() {
+                private boolean mHandled;
 
-                if (screenPinningRequested) {
-                    // Request screen pinning after the animation runs
-                    mStartScreenPinningRunnable.taskId = task.key.id;
-                    mHandler.postDelayed(mStartScreenPinningRunnable, 350);
+                @Override
+                public void onAnimationStarted() {
+                    if (mHandled) {
+                        return;
+                    }
+                    mHandled = true;
+
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                    stackView.cancelAllTaskViewAnimations();
+
+                    if (screenPinningRequested) {
+                        // Request screen pinning after the animation runs
+                        mStartScreenPinningRunnable.taskId = task.key.id;
+                        mHandler.postDelayed(mStartScreenPinningRunnable, 350);
+                    }
+
+                    // Reset the state where we are waiting for the transition to start
+                    EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
                 }
             };
         } else {
             // This is only the case if the task is not on screen (scrolled offscreen for example)
             transitionFuture = null;
-            animStartedListener = () -> {
-                // If we are launching into another task, cancel the previous task's
-                // window transition
-                EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                stackView.cancelAllTaskViewAnimations();
+            animStartedListener = new OnAnimationStartedListener() {
+                private boolean mHandled;
+
+                @Override
+                public void onAnimationStarted() {
+                    if (mHandled) {
+                        return;
+                    }
+                    mHandled = true;
+
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                    stackView.cancelAllTaskViewAnimations();
+
+                    // Reset the state where we are waiting for the transition to start
+                    EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+                }
             };
         }
 
+        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
         final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
                 mHandler, transitionFuture != null ? transitionFuture.future : null,
                 animStartedListener, true /* scaleUp */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 4bbe895..291ec1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -3714,6 +3714,9 @@
      * See {@link AmbientState#setDark}.
      */
     public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
+        if (mAmbientState.isDark() == dark) {
+            return;
+        }
         mAmbientState.setDark(dark);
         if (animate && mAnimationsEnabled) {
             mDarkNeedsAnimation = true;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 38327bc..84017e7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13367,7 +13367,6 @@
                 final ActivityRecord r = ActivityRecord.isInStackLocked(token);
                 if (r != null) {
                     final ActivityOptions activityOptions = r.pendingOptions;
-                    r.pendingOptions = null;
                     return activityOptions == null ? null : activityOptions.toBundle();
                 }
                 return null;
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index cdf25cf..11a4eb4 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -543,7 +543,9 @@
                     loadPropertiesFromResource(context, mProperties);
                     String lpp_profile = mProperties.getProperty("LPP_PROFILE");
                     // set the persist property LPP_PROFILE for the value
-                    SystemProperties.set(LPP_PROFILE, lpp_profile);
+                    if (lpp_profile != null) {
+                        SystemProperties.set(LPP_PROFILE, lpp_profile);
+                    }
                 } else {
                     // reset the persist property
                     SystemProperties.set(LPP_PROFILE, "");
diff --git a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
index 0508fdf..a12c2c4 100644
--- a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
+++ b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
@@ -32,7 +32,7 @@
 // the surface control.
 //
 // See cts/hostsidetests/../../SurfaceTraceReceiver.java for parsing side.
-class RemoteSurfaceTrace extends SurfaceControl {
+class RemoteSurfaceTrace extends SurfaceControlWithBackground {
     static final String TAG = "RemoteSurfaceTrace";
 
     final FileDescriptor mWriteFd;
@@ -41,7 +41,8 @@
     final WindowManagerService mService;
     final WindowState mWindow;
 
-    RemoteSurfaceTrace(FileDescriptor fd, SurfaceControl wrapped, WindowState window) {
+    RemoteSurfaceTrace(FileDescriptor fd, SurfaceControlWithBackground wrapped,
+            WindowState window) {
         super(wrapped);
 
         mWriteFd = fd;
diff --git a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
new file mode 100644
index 0000000..f5ef2e6
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
@@ -0,0 +1,306 @@
+/*
+ * 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.server.wm;
+
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * SurfaceControl extension that has background sized to match its container.
+ */
+class SurfaceControlWithBackground extends SurfaceControl {
+    // SurfaceControl that holds the background behind opaque letterboxed app windows.
+    private SurfaceControl mBackgroundControl;
+
+    // Flags that define whether the background should be shown.
+    private boolean mOpaque;
+    private boolean mVisible;
+
+    // Way to communicate with corresponding window.
+    private WindowSurfaceController mWindowSurfaceController;
+
+    // Rect to hold task bounds when computing metrics for background.
+    private Rect mTmpContainerRect = new Rect();
+
+    // Last metrics applied to the main SurfaceControl.
+    private float mLastWidth, mLastHeight;
+    private float mLastDsDx = 1, mLastDsDy = 1;
+    private float mLastX, mLastY;
+
+    public SurfaceControlWithBackground(SurfaceControlWithBackground other) {
+        super(other);
+        mBackgroundControl = other.mBackgroundControl;
+        mOpaque = other.mOpaque;
+        mVisible = other.mVisible;
+        mWindowSurfaceController = other.mWindowSurfaceController;
+    }
+
+    public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format,
+            int flags, int windowType, int ownerUid,
+            WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
+        super(s, name, w, h, format, flags, windowType, ownerUid);
+
+        // We should only show background when the window is letterboxed in a task.
+        if (!windowSurfaceController.mAnimator.mWin.isLetterboxedAppWindow()) {
+            return;
+        }
+        mWindowSurfaceController = windowSurfaceController;
+        mLastWidth = w;
+        mLastHeight = h;
+        mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        mBackgroundControl = new SurfaceControl(s, "Background for - " + name,
+                mTmpContainerRect.width(), mTmpContainerRect.height(), PixelFormat.OPAQUE,
+                flags | SurfaceControl.FX_SURFACE_DIM);
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        super.setAlpha(alpha);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.setAlpha(alpha);
+    }
+
+    @Override
+    public void setLayer(int zorder) {
+        super.setLayer(zorder);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        // TODO: Use setRelativeLayer(Integer.MIN_VALUE) when it's fixed.
+        mBackgroundControl.setLayer(zorder - 1);
+    }
+
+    @Override
+    public void setPosition(float x, float y) {
+        super.setPosition(x, y);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mLastX = x;
+        mLastY = y;
+        updateBgPosition();
+    }
+
+    private void updateBgPosition() {
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
+        final float offsetX = (mTmpContainerRect.left - winFrame.left) * mLastDsDx;
+        final float offsetY = (mTmpContainerRect.top - winFrame.top) * mLastDsDy;
+        mBackgroundControl.setPosition(mLastX + offsetX, mLastY + offsetY);
+    }
+
+    @Override
+    public void setSize(int w, int h) {
+        super.setSize(w, h);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mLastWidth = w;
+        mLastHeight = h;
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        mBackgroundControl.setSize(mTmpContainerRect.width(), mTmpContainerRect.height());
+    }
+
+    @Override
+    public void setWindowCrop(Rect crop) {
+        super.setWindowCrop(crop);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        if (crop.width() < mLastWidth || crop.height() < mLastHeight) {
+            // We're animating and cropping window, compute the appropriate crop for background.
+            calculateBgCrop(crop);
+            mBackgroundControl.setWindowCrop(mTmpContainerRect);
+        } else {
+            // When not animating just set crop to container rect.
+            mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+            mBackgroundControl.setWindowCrop(mTmpContainerRect);
+        }
+    }
+
+    @Override
+    public void setFinalCrop(Rect crop) {
+        super.setFinalCrop(crop);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        if (crop.width() < mLastWidth || crop.height() < mLastHeight) {
+            // We're animating and cropping window, compute the appropriate crop for background.
+            calculateBgCrop(crop);
+            mBackgroundControl.setFinalCrop(mTmpContainerRect);
+        } else {
+            // When not animating just set crop to container rect.
+            mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+            mBackgroundControl.setFinalCrop(mTmpContainerRect);
+        }
+    }
+
+    /** Compute background crop based on current animation progress for main surface control. */
+    private void calculateBgCrop(Rect crop) {
+        // Track overall progress of animation by computing cropped portion of status bar.
+        final Rect contentInsets = mWindowSurfaceController.mAnimator.mWin.mContentInsets;
+        float d = contentInsets.top == 0 ? 0 : (float) crop.top / contentInsets.top;
+
+        // Compute additional offset for the background when app window is positioned not at (0,0).
+        // E.g. landscape with navigation bar on the left.
+        final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
+        final int offsetX = (int) (winFrame.left * mLastDsDx * d + 0.5);
+        final int offsetY = (int) (winFrame.top * mLastDsDy * d + 0.5);
+
+        // Compute new scaled width and height for background that will depend on current animation
+        // progress. Those consist of current crop rect for the main surface + scaled areas outside
+        // of letterboxed area.
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        final int backgroundWidth =
+                (int) (crop.width() + (mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
+        final int backgroundHeight =
+                (int) (crop.height() + (mTmpContainerRect.height() - mLastHeight) * (1 - d) + 0.5);
+
+        mTmpContainerRect.set(crop);
+        // Make sure that part of background to left/top is visible and scaled.
+        mTmpContainerRect.offset(offsetX, offsetY);
+        // Set correct width/height, so that area to right/bottom is cropped properly.
+        mTmpContainerRect.right = mTmpContainerRect.left + backgroundWidth;
+        mTmpContainerRect.bottom = mTmpContainerRect.top + backgroundHeight;
+    }
+
+    @Override
+    public void setLayerStack(int layerStack) {
+        super.setLayerStack(layerStack);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.setLayerStack(layerStack);
+    }
+
+    @Override
+    public void setOpaque(boolean isOpaque) {
+        super.setOpaque(isOpaque);
+        mOpaque = isOpaque;
+        updateBackgroundVisibility();
+    }
+
+    @Override
+    public void setSecure(boolean isSecure) {
+        super.setSecure(isSecure);
+    }
+
+    @Override
+    public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
+        super.setMatrix(dsdx, dtdx, dtdy, dsdy);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.setMatrix(dsdx, dtdx, dtdy, dsdy);
+        mLastDsDx = dsdx;
+        mLastDsDy = dsdy;
+        updateBgPosition();
+    }
+
+    @Override
+    public void hide() {
+        super.hide();
+        mVisible = false;
+        updateBackgroundVisibility();
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        mVisible = true;
+        updateBackgroundVisibility();
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.destroy();
+    }
+
+    @Override
+    public void release() {
+        super.release();
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.release();
+    }
+
+    @Override
+    public void setTransparentRegionHint(Region region) {
+        super.setTransparentRegionHint(region);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.setTransparentRegionHint(region);
+    }
+
+    @Override
+    public void deferTransactionUntil(IBinder handle, long frame) {
+        super.deferTransactionUntil(handle, frame);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.deferTransactionUntil(handle, frame);
+    }
+
+    @Override
+    public void deferTransactionUntil(Surface barrier, long frame) {
+        super.deferTransactionUntil(barrier, frame);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.deferTransactionUntil(barrier, frame);
+    }
+
+    private void updateBackgroundVisibility() {
+        if (mBackgroundControl == null) {
+            return;
+        }
+        if (mOpaque && mVisible) {
+            mBackgroundControl.show();
+        } else {
+            mBackgroundControl.hide();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c968653..02242f3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3219,6 +3219,15 @@
         return !isInMultiWindowMode();
     }
 
+    /** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */
+    boolean isLetterboxedAppWindow() {
+        final Task task = getTask();
+        final boolean taskIsFullscreen = task != null && task.isFullscreen();
+        final boolean appWindowIsFullscreen = mAppToken != null && !mAppToken.hasBounds();
+
+        return taskIsFullscreen && !appWindowIsFullscreen;
+    }
+
     /** Returns the appropriate bounds to use for computing frames. */
     private void getContainerBounds(Rect outBounds) {
         if (isInMultiWindowMode()) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0cc505e..86265c29 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1200,7 +1200,8 @@
         if (DEBUG_WINDOW_CROP) Slog.d(TAG, "Applying decor to crop win=" + w + " mDecorFrame="
                 + w.mDecorFrame + " mSystemDecorRect=" + mSystemDecorRect);
 
-        final boolean fullscreen = w.fillsDisplay();
+        final Task task = w.getTask();
+        final boolean fullscreen = w.fillsDisplay() || (task != null && task.isFullscreen());
         final boolean isFreeformResizing =
                 w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
 
@@ -1526,6 +1527,19 @@
         }
     }
 
+    /**
+     * Get rect of the task this window is currently in. If there is no task, rect will be set to
+     * empty.
+     */
+    void getContainerRect(Rect rect) {
+        final Task task = mWin.getTask();
+        if (task != null) {
+            task.getDimBounds(rect);
+        } else {
+            rect.left = rect.top = rect.right = rect.bottom = 0;
+        }
+    }
+
     void prepareSurfaceLocked(final boolean recoveringMemory) {
         final WindowState w = mWin;
         if (!hasSurface()) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 27927e6..4819c0f 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -52,7 +52,7 @@
 
     final WindowStateAnimator mAnimator;
 
-    private SurfaceControl mSurfaceControl;
+    private SurfaceControlWithBackground mSurfaceControl;
 
     // Should only be set from within setShown().
     private boolean mSurfaceShown = false;
@@ -99,15 +99,10 @@
         mWindowType = windowType;
         mWindowSession = win.mSession;
 
-        if (DEBUG_SURFACE_TRACE) {
-            mSurfaceControl = new SurfaceTrace(
-                    s, name, w, h, format, flags, windowType, ownerUid);
-        } else {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
-            mSurfaceControl = new SurfaceControl(
-                    s, name, w, h, format, flags, windowType, ownerUid);
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+        mSurfaceControl = new SurfaceControlWithBackground(
+                s, name, w, h, format, flags, windowType, ownerUid, this);
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
         if (mService.mRoot.mSurfaceTraceEnabled) {
             mSurfaceControl = new RemoteSurfaceTrace(
@@ -120,7 +115,7 @@
     }
 
     void removeRemoteTrace() {
-        mSurfaceControl = new SurfaceControl(mSurfaceControl);
+        mSurfaceControl = new SurfaceControlWithBackground(mSurfaceControl);
     }
 
 
@@ -293,30 +288,30 @@
         mSurfaceControl.setGeometryAppliesWithResize();
     }
 
-    void setMatrixInTransaction(float dsdx, float dtdx, float dsdy, float dtdy,
+    void setMatrixInTransaction(float dsdx, float dtdx, float dtdy, float dsdy,
             boolean recoveringMemory) {
         final boolean matrixChanged = mLastDsdx != dsdx || mLastDtdx != dtdx ||
-                                      mLastDsdy != dsdy || mLastDtdy != dtdy;
+                                      mLastDtdy != dtdy || mLastDsdy != dsdy;
         if (!matrixChanged) {
             return;
         }
 
         mLastDsdx = dsdx;
         mLastDtdx = dtdx;
-        mLastDsdy = dsdy;
         mLastDtdy = dtdy;
+        mLastDsdy = dsdy;
 
         try {
             if (SHOW_TRANSACTIONS) logSurface(
-                    "MATRIX [" + dsdx + "," + dtdx + "," + dsdy + "," + dtdy + "]", null);
+                    "MATRIX [" + dsdx + "," + dtdx + "," + dtdy + "," + dsdy + "]", null);
             mSurfaceControl.setMatrix(
-                    dsdx, dtdx, dsdy, dtdy);
+                    dsdx, dtdx, dtdy, dsdy);
         } catch (RuntimeException e) {
             // If something goes wrong with the surface (such
             // as running out of memory), don't take down the
             // entire system.
             Slog.e(TAG, "Error setting matrix on surface surface" + title
-                    + " MATRIX [" + dsdx + "," + dtdx + "," + dsdy + "," + dtdy + "]", null);
+                    + " MATRIX [" + dsdx + "," + dtdx + "," + dtdy + "," + dsdy + "]", null);
             if (!recoveringMemory) {
                 mAnimator.reclaimSomeSurfaceMemory("matrix", true);
             }
@@ -423,6 +418,10 @@
         }
     }
 
+    void getContainerRect(Rect rect) {
+        mAnimator.getContainerRect(rect);
+    }
+
     boolean showRobustlyInTransaction() {
         if (SHOW_TRANSACTIONS) logSurface(
                 "SHOW (performLayout)", null);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3821b9c..bbda1c6 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1022,6 +1022,26 @@
      */
     public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET =
             "carrier_default_actions_on_reset_string_array";
+
+    /**
+     * Defines carrier-specific actions which act upon
+     * com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE,
+     * used for customization of the default carrier app
+     * Format:
+     * {
+     *     "true : CARRIER_ACTION_IDX_1",
+     *     "false: CARRIER_ACTION_IDX_2"
+     * }
+     * Where {@code true} is a boolean indicates default network available/unavailable
+     * Where {@code CARRIER_ACTION_IDX} is an integer defined in
+     * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+     * Example:
+     * {@link com.android.carrierdefaultapp.CarrierActionUtils
+     * #CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER enable the app as the default URL handler}
+     * @hide
+     */
+    public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE =
+            "carrier_default_actions_on_default_network_available_string_array";
     /**
      * Defines a list of acceptable redirection url for default carrier app
      * @hides
@@ -1684,9 +1704,10 @@
         sDefaults.putString(KEY_CARRIER_SETUP_APP_STRING, "");
         sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
                 new String[]{
-                        "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:" +
-                                "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED," +
-                                "com.android.internal.telephony.CARRIER_SIGNAL_RESET"
+                        "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:"
+                                + "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED,"
+                                + "com.android.internal.telephony.CARRIER_SIGNAL_RESET,"
+                                + "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"
                 });
         sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null);
 
@@ -1694,12 +1715,22 @@
         // Default carrier app configurations
         sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY,
                 new String[]{
-                        "4, 1"
+                        "9, 4, 1"
+                        //9: CARRIER_ACTION_REGISTER_NETWORK_AVAIL
                         //4: CARRIER_ACTION_DISABLE_METERED_APNS
                         //1: CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
                 });
         sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET, new String[]{
-                "6" //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+                "6, 8"
+                //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+                //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER
+                });
+        sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE,
+                new String[] {
+                        String.valueOf(false) + ": 7",
+                        //7: CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER
+                        String.valueOf(true) + ": 8"
+                        //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER
                 });
         sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null);
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b5b32e4..e334c63 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1642,8 +1642,7 @@
      * @hide
      */
     public String getNetworkCountryIso(int subId) {
-        int phoneId = SubscriptionManager.getPhoneId(subId);
-        return getNetworkCountryIsoForPhone(phoneId);
+        return getNetworkCountryIsoForPhone(getPhoneId(subId));
     }
 
     /**
@@ -1658,7 +1657,14 @@
      */
     /** {@hide} */
     public String getNetworkCountryIsoForPhone(int phoneId) {
-        return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null)
+                return "";
+            return telephony.getNetworkCountryIsoForPhone(phoneId);
+        } catch (RemoteException ex) {
+                return "";
+        }
     }
 
     /** Network type is unknown */
@@ -6655,6 +6661,25 @@
     }
 
     /**
+     * Action set from carrier signalling broadcast receivers to start/stop reporting default
+     * network available events
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * @param subId the subscription ID that this action applies to.
+     * @param report control start/stop reporting network status.
+     * @hide
+     */
+    public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                service.carrierActionReportDefaultNetworkStatus(subId, report);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#carrierActionReportDefaultNetworkStatus", e);
+        }
+    }
+
+    /**
      * Get aggregated video call data usage since boot.
      * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
      *
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a0e5b7b..9262ec5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -377,6 +377,13 @@
     Bundle getCellLocation(String callingPkg);
 
     /**
+     * Returns the ISO country code equivalent of the current registered
+     * operator's MCC (Mobile Country Code).
+     * @see android.telephony.TelephonyManager#getNetworkCountryIso
+     */
+    String getNetworkCountryIsoForPhone(int phoneId);
+
+    /**
      * Returns the neighboring cell information of the device.
      */
     List<NeighboringCellInfo> getNeighboringCellInfo(String callingPkg);
@@ -1296,6 +1303,16 @@
     void carrierActionSetRadioEnabled(int subId, boolean enabled);
 
     /**
+     * Action set from carrier signalling broadcast receivers to start/stop reporting default
+     * network conditions.
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * @param subId the subscription ID that this action applies to.
+     * @param report control start/stop reporting default network events.
+     * @hide
+     */
+    void carrierActionReportDefaultNetworkStatus(int subId, boolean report);
+
+    /**
      * Get aggregated video call data usage since boot.
      * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
      *
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 0343890..f29d993c 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -447,6 +447,20 @@
             "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
 
     /**
+     * <p>Broadcast Action: when system default network available/unavailable with
+     * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
+     * other network becomes system default network, Wi-Fi for example.
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>defaultNetworkAvailable</li><dd>A boolean indicates default network available.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the default data.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system. </p>
+     */
+    public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
+            "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+
+    /**
      * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
      * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
      * The intent will have the following extra values:</p>
@@ -465,7 +479,7 @@
     public static final String EXTRA_APN_PROTO_KEY = "apnProto";
     public static final String EXTRA_PCO_ID_KEY = "pcoId";
     public static final String EXTRA_PCO_VALUE_KEY = "pcoValue";
-
+    public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY = "defaultNetworkAvailable";
 
    /**
      * Broadcast action to trigger CI OMA-DM Session.
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 46098cb..7ff0c72 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -877,7 +877,16 @@
 }
 
 bool ConfigDescription::Dominates(const ConfigDescription& o) const {
-  if (*this == DefaultConfig() || *this == o) {
+  if (*this == o) {
+    return true;
+  }
+
+  // Locale de-duping is not-trivial, disable for now (b/62409213).
+  if (diff(o) & CONFIG_LOCALE) {
+    return false;
+  }
+
+  if (*this == DefaultConfig()) {
     return true;
   }
   return MatchWithDensity(o) && !o.MatchWithDensity(*this) &&
diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp
index e89c6be..efc523f 100644
--- a/tools/aapt2/DominatorTree_test.cpp
+++ b/tools/aapt2/DominatorTree_test.cpp
@@ -69,14 +69,12 @@
 TEST(DominatorTreeTest, DefaultDominatesEverything) {
   const ConfigDescription default_config = {};
   const ConfigDescription land_config = test::ParseConfigOrDie("land");
-  const ConfigDescription sw600dp_land_config =
-      test::ParseConfigOrDie("sw600dp-land-v13");
+  const ConfigDescription sw600dp_land_config = test::ParseConfigOrDie("sw600dp-land-v13");
 
   std::vector<std::unique_ptr<ResourceConfigValue>> configs;
   configs.push_back(util::make_unique<ResourceConfigValue>(default_config, ""));
   configs.push_back(util::make_unique<ResourceConfigValue>(land_config, ""));
-  configs.push_back(
-      util::make_unique<ResourceConfigValue>(sw600dp_land_config, ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_land_config, ""));
 
   DominatorTree tree(configs);
   PrettyPrinter printer;
@@ -91,16 +89,13 @@
 TEST(DominatorTreeTest, ProductsAreDominatedSeparately) {
   const ConfigDescription default_config = {};
   const ConfigDescription land_config = test::ParseConfigOrDie("land");
-  const ConfigDescription sw600dp_land_config =
-      test::ParseConfigOrDie("sw600dp-land-v13");
+  const ConfigDescription sw600dp_land_config = test::ParseConfigOrDie("sw600dp-land-v13");
 
   std::vector<std::unique_ptr<ResourceConfigValue>> configs;
   configs.push_back(util::make_unique<ResourceConfigValue>(default_config, ""));
   configs.push_back(util::make_unique<ResourceConfigValue>(land_config, ""));
-  configs.push_back(
-      util::make_unique<ResourceConfigValue>(default_config, "phablet"));
-  configs.push_back(
-      util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet"));
+  configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "phablet"));
+  configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet"));
 
   DominatorTree tree(configs);
   PrettyPrinter printer;
@@ -118,16 +113,11 @@
   const ConfigDescription en_config = test::ParseConfigOrDie("en");
   const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
   const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl-v4");
-  const ConfigDescription ldrtl_xhdpi_config =
-      test::ParseConfigOrDie("ldrtl-xhdpi-v4");
-  const ConfigDescription sw300dp_config =
-      test::ParseConfigOrDie("sw300dp-v13");
-  const ConfigDescription sw540dp_config =
-      test::ParseConfigOrDie("sw540dp-v14");
-  const ConfigDescription sw600dp_config =
-      test::ParseConfigOrDie("sw600dp-v14");
-  const ConfigDescription sw720dp_config =
-      test::ParseConfigOrDie("sw720dp-v13");
+  const ConfigDescription ldrtl_xhdpi_config = test::ParseConfigOrDie("ldrtl-xhdpi-v4");
+  const ConfigDescription sw300dp_config = test::ParseConfigOrDie("sw300dp-v13");
+  const ConfigDescription sw540dp_config = test::ParseConfigOrDie("sw540dp-v14");
+  const ConfigDescription sw600dp_config = test::ParseConfigOrDie("sw600dp-v14");
+  const ConfigDescription sw720dp_config = test::ParseConfigOrDie("sw720dp-v13");
   const ConfigDescription v20_config = test::ParseConfigOrDie("v20");
 
   std::vector<std::unique_ptr<ResourceConfigValue>> configs;
@@ -135,8 +125,7 @@
   configs.push_back(util::make_unique<ResourceConfigValue>(en_config, ""));
   configs.push_back(util::make_unique<ResourceConfigValue>(en_v21_config, ""));
   configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_config, ""));
-  configs.push_back(
-      util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, ""));
   configs.push_back(util::make_unique<ResourceConfigValue>(sw300dp_config, ""));
   configs.push_back(util::make_unique<ResourceConfigValue>(sw540dp_config, ""));
   configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_config, ""));
@@ -148,15 +137,37 @@
 
   std::string expected =
       "<default>\n"
-      "  en\n"
-      "    en-v21\n"
       "  ldrtl-v4\n"
       "    ldrtl-xhdpi-v4\n"
       "  sw300dp-v13\n"
       "    sw540dp-v14\n"
       "      sw600dp-v14\n"
       "    sw720dp-v13\n"
-      "  v20\n";
+      "  v20\n"
+      "en\n"
+      "  en-v21\n";
+  EXPECT_EQ(expected, printer.ToString(&tree));
+}
+
+TEST(DominatorTreeTest, LocalesAreNeverDominated) {
+  const ConfigDescription fr_config = test::ParseConfigOrDie("fr");
+  const ConfigDescription fr_rCA_config = test::ParseConfigOrDie("fr-rCA");
+  const ConfigDescription fr_rFR_config = test::ParseConfigOrDie("fr-rFR");
+
+  std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+  configs.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(fr_config, ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(fr_rCA_config, ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(fr_rFR_config, ""));
+
+  DominatorTree tree(configs);
+  PrettyPrinter printer;
+
+  std::string expected =
+      "<default>\n"
+      "fr\n"
+      "fr-rCA\n"
+      "fr-rFR\n";
   EXPECT_EQ(expected, printer.ToString(&tree));
 }
 
diff --git a/tools/aapt2/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp
index 4d00fa6..d9f384c0 100644
--- a/tools/aapt2/optimize/ResourceDeduper_test.cpp
+++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp
@@ -19,69 +19,88 @@
 #include "ResourceTable.h"
 #include "test/Test.h"
 
+using ::aapt::test::HasValue;
+using ::testing::Not;
+
 namespace aapt {
 
 TEST(ResourceDeduperTest, SameValuesAreDeduped) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   const ConfigDescription default_config = {};
+  const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl");
+  const ConfigDescription ldrtl_v21_config = test::ParseConfigOrDie("ldrtl-v21");
   const ConfigDescription en_config = test::ParseConfigOrDie("en");
   const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
-  // Chosen because this configuration is compatible with en.
+  // Chosen because this configuration is compatible with ldrtl/en.
   const ConfigDescription land_config = test::ParseConfigOrDie("land");
 
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
-          .AddString("android:string/dedupe", ResourceId{}, default_config,
-                     "dedupe")
-          .AddString("android:string/dedupe", ResourceId{}, en_config, "dedupe")
-          .AddString("android:string/dedupe", ResourceId{}, land_config,
-                     "dedupe")
-          .AddString("android:string/dedupe2", ResourceId{}, default_config,
-                     "dedupe")
-          .AddString("android:string/dedupe2", ResourceId{}, en_config,
-                     "dedupe")
-          .AddString("android:string/dedupe2", ResourceId{}, en_v21_config,
-                     "keep")
-          .AddString("android:string/dedupe2", ResourceId{}, land_config,
-                     "dedupe")
+          .AddString("android:string/dedupe", ResourceId{}, default_config, "dedupe")
+          .AddString("android:string/dedupe", ResourceId{}, ldrtl_config, "dedupe")
+          .AddString("android:string/dedupe", ResourceId{}, land_config, "dedupe")
+
+          .AddString("android:string/dedupe2", ResourceId{}, default_config, "dedupe")
+          .AddString("android:string/dedupe2", ResourceId{}, ldrtl_config, "dedupe")
+          .AddString("android:string/dedupe2", ResourceId{}, ldrtl_v21_config, "keep")
+          .AddString("android:string/dedupe2", ResourceId{}, land_config, "dedupe")
+
+          .AddString("android:string/dedupe3", ResourceId{}, default_config, "dedupe")
+          .AddString("android:string/dedupe3", ResourceId{}, en_config, "dedupe")
+          .AddString("android:string/dedupe3", ResourceId{}, en_v21_config, "dedupe")
           .Build();
 
   ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
-  EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
-                         table.get(), "android:string/dedupe", en_config));
-  EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
-                         table.get(), "android:string/dedupe", land_config));
-  EXPECT_EQ(nullptr, test::GetValueForConfig<String>(
-                         table.get(), "android:string/dedupe2", en_config));
-  EXPECT_NE(nullptr, test::GetValueForConfig<String>(
-                         table.get(), "android:string/dedupe2", en_v21_config));
+  EXPECT_THAT(table, Not(HasValue("android:string/dedupe", ldrtl_config)));
+  EXPECT_THAT(table, Not(HasValue("android:string/dedupe", land_config)));
+
+  EXPECT_THAT(table, HasValue("android:string/dedupe2", ldrtl_v21_config));
+  EXPECT_THAT(table, Not(HasValue("android:string/dedupe2", ldrtl_config)));
+
+  EXPECT_THAT(table, HasValue("android:string/dedupe3", default_config));
+  EXPECT_THAT(table, HasValue("android:string/dedupe3", en_config));
+  EXPECT_THAT(table, Not(HasValue("android:string/dedupe3", en_v21_config)));
 }
 
 TEST(ResourceDeduperTest, DifferentValuesAreKept) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   const ConfigDescription default_config = {};
-  const ConfigDescription en_config = test::ParseConfigOrDie("en");
-  const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
-  // Chosen because this configuration is compatible with en.
+  const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl");
+  const ConfigDescription ldrtl_v21_config = test::ParseConfigOrDie("ldrtl-v21");
+  // Chosen because this configuration is compatible with ldrtl.
   const ConfigDescription land_config = test::ParseConfigOrDie("land");
 
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
-          .AddString("android:string/keep", ResourceId{}, default_config,
-                     "keep")
-          .AddString("android:string/keep", ResourceId{}, en_config, "keep")
-          .AddString("android:string/keep", ResourceId{}, en_v21_config,
-                     "keep2")
+          .AddString("android:string/keep", ResourceId{}, default_config, "keep")
+          .AddString("android:string/keep", ResourceId{}, ldrtl_config, "keep")
+          .AddString("android:string/keep", ResourceId{}, ldrtl_v21_config, "keep2")
           .AddString("android:string/keep", ResourceId{}, land_config, "keep2")
           .Build();
 
   ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
-  EXPECT_NE(nullptr, test::GetValueForConfig<String>(
-                         table.get(), "android:string/keep", en_config));
-  EXPECT_NE(nullptr, test::GetValueForConfig<String>(
-                         table.get(), "android:string/keep", en_v21_config));
-  EXPECT_NE(nullptr, test::GetValueForConfig<String>(
-                         table.get(), "android:string/keep", land_config));
+  EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config));
+  EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_v21_config));
+  EXPECT_THAT(table, HasValue("android:string/keep", land_config));
+}
+
+TEST(ResourceDeduperTest, LocalesValuesAreKept) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  const ConfigDescription default_config = {};
+  const ConfigDescription fr_config = test::ParseConfigOrDie("fr");
+  const ConfigDescription fr_rCA_config = test::ParseConfigOrDie("fr-rCA");
+
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddString("android:string/keep", ResourceId{}, default_config, "keep")
+          .AddString("android:string/keep", ResourceId{}, fr_config, "keep")
+          .AddString("android:string/keep", ResourceId{}, fr_rCA_config, "keep")
+          .Build();
+
+  ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+  EXPECT_THAT(table, HasValue("android:string/keep", default_config));
+  EXPECT_THAT(table, HasValue("android:string/keep", fr_config));
+  EXPECT_THAT(table, HasValue("android:string/keep", fr_rCA_config));
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 01b2d14..8efd56a 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -156,6 +156,23 @@
   return arg.Equals(&a);
 }
 
+MATCHER_P(StrValueEq, a,
+          std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) {
+  return *(arg.value) == a;
+}
+
+MATCHER_P(HasValue, name,
+          std::string(negation ? "does not have" : "has") + " value " +
+              ::testing::PrintToString(name)) {
+  return GetValueForConfig<Value>(&(*arg), name, {}) != nullptr;
+}
+
+MATCHER_P2(HasValue, name, config,
+           std::string(negation ? "does not have" : "has") + " value " +
+               ::testing::PrintToString(name) + " for config " + ::testing::PrintToString(config)) {
+  return GetValueForConfig<Value>(&(*arg), name, config) != nullptr;
+}
+
 }  // namespace test
 }  // namespace aapt