Merge "ConnectivityService: improve wakelock logging" into oc-mr1-dev
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 0bf2eda..77df02b 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -105,6 +105,7 @@
     <!-- Titles for Bluetooth AVRCP Versions -->
     <string-array name="bluetooth_avrcp_versions">
         <item>AVRCP 1.4 (Default)</item>
+        <item>AVRCP 1.3</item>
         <item>AVRCP 1.5</item>
         <item>AVRCP 1.6</item>
     </string-array>
@@ -112,6 +113,7 @@
     <!-- Values for Bluetooth AVRCP Versions -->
     <string-array name="bluetooth_avrcp_version_values">
         <item>avrcp14</item>
+        <item>avrcp13</item>
         <item>avrcp15</item>
         <item>avrcp16</item>
     </string-array>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 443f1ee..87bf0de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1638,6 +1638,21 @@
         }
     };
 
+    public static final AppFilter FILTER_PHOTOS =
+            new AppFilter() {
+                @Override
+                public void init() {}
+
+                @Override
+                public boolean filterApp(AppEntry entry) {
+                    boolean isPhotosApp;
+                    synchronized (entry) {
+                        isPhotosApp = entry.info.category == ApplicationInfo.CATEGORY_IMAGE;
+                    }
+                    return isPhotosApp;
+                }
+            };
+
     public static final AppFilter FILTER_OTHER_APPS =
             new AppFilter() {
                 @Override
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index fed18fa..751b4ba 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -110,6 +110,27 @@
     }
 
     @Test
+    public void testPhotosFilterAcceptsFilter() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_IMAGE;
+
+        assertThat(ApplicationsState.FILTER_PHOTOS.filterApp(mEntry)).isTrue();
+    }
+
+    @Test
+    public void testPhotosFilterRejectsNotPhotos() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_VIDEO;
+
+        assertThat(ApplicationsState.FILTER_PHOTOS.filterApp(mEntry)).isFalse();
+    }
+
+    @Test
+    public void testPhotosFilterRejectsDefaultCategory() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
+
+        assertThat(ApplicationsState.FILTER_PHOTOS.filterApp(mEntry)).isFalse();
+    }
+
+    @Test
     public void testDownloadAndLauncherAndInstantAcceptsCorrectApps() {
         // should include instant apps
         mEntry.isHomeApp = false;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 6f8bcff..9b1842a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -58,20 +58,22 @@
                 params);
 
         DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock);
+        DozeScreenBrightness screenBrightness = createDozeScreenBrightness(
+                context, wrappedService, sensorManager, host, handler);
         machine.setParts(new DozeMachine.Part[]{
                 new DozePauser(handler, machine, alarmManager, new AlwaysOnDisplayPolicy(context)),
                 new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)),
                 createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
-                        handler, wakeLock, machine),
+                        handler, screenBrightness, wakeLock, machine),
                 createDozeUi(context, host, wakeLock, machine, handler, alarmManager),
                 new DozeScreenState(wrappedService, handler),
-                createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
+                screenBrightness,
         });
 
         return machine;
     }
 
-    private DozeMachine.Part createDozeScreenBrightness(Context context,
+    private DozeScreenBrightness createDozeScreenBrightness(Context context,
             DozeMachine.Service service, SensorManager sensorManager, DozeHost host,
             Handler handler) {
         Sensor sensor = DozeSensors.findSensorWithType(sensorManager,
@@ -82,10 +84,11 @@
 
     private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
             DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
-            DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine) {
+            DozeParameters params, Handler handler, DozeScreenBrightness screenBrightness,
+            WakeLock wakeLock, DozeMachine machine) {
         boolean allowPulseTriggers = true;
         return new DozeTriggers(context, machine, host, alarmManager, config, params,
-                sensorManager, handler, wakeLock, allowPulseTriggers);
+                sensorManager, handler, screenBrightness, wakeLock, allowPulseTriggers);
     }
 
     private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 11b4b0ef..92f8d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -24,7 +24,6 @@
 import android.os.Handler;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
 
 /**
  * Controls the screen brightness when dozing.
@@ -39,10 +38,14 @@
     private final int[] mSensorToBrightness;
     private final int[] mSensorToScrimOpacity;
     private boolean mRegistered;
+    private boolean mReady = true;
+    private ReadyListener mReadyListener;
+    private int mDefaultDozeBrightness;
 
     public DozeScreenBrightness(Context context, DozeMachine.Service service,
             SensorManager sensorManager, Sensor lightSensor, DozeHost host,
-            Handler handler, AlwaysOnDisplayPolicy policy) {
+            Handler handler, int defaultDozeBrightness, int[] sensorToBrightness,
+            int[] sensorToScrimOpacity) {
         mContext = context;
         mDozeService = service;
         mSensorManager = sensorManager;
@@ -50,8 +53,19 @@
         mDozeHost = host;
         mHandler = handler;
 
-        mSensorToBrightness = policy.screenBrightnessArray;
-        mSensorToScrimOpacity = policy.dimmingScrimArray;
+        mDefaultDozeBrightness = defaultDozeBrightness;
+        mSensorToBrightness = sensorToBrightness;
+        mSensorToScrimOpacity = sensorToScrimOpacity;
+    }
+
+    @VisibleForTesting
+    public DozeScreenBrightness(Context context, DozeMachine.Service service,
+            SensorManager sensorManager, Sensor lightSensor, DozeHost host,
+            Handler handler, AlwaysOnDisplayPolicy policy) {
+        this(context, service, sensorManager, lightSensor, host, handler,
+                context.getResources().getInteger(
+                        com.android.internal.R.integer.config_screenBrightnessDoze),
+                policy.screenBrightnessArray, policy.dimmingScrimArray);
     }
 
     @Override
@@ -65,7 +79,6 @@
                 setLightSensorEnabled(true);
                 break;
             case DOZE:
-            case DOZE_AOD_PAUSED:
                 setLightSensorEnabled(false);
                 resetBrightnessToDefault();
                 break;
@@ -83,6 +96,10 @@
             if (brightness > 0) {
                 mDozeService.setDozeScreenBrightness(brightness);
             }
+            // If the brightness is zero or negative, this indicates that the brightness sensor is
+            // covered or reports that the screen should be off, therefore we're not ready to turn
+            // on the screen yet.
+            setReady(brightness > 0);
 
             int scrimOpacity = computeScrimOpacity(sensorValue);
             if (scrimOpacity >= 0) {
@@ -110,17 +127,48 @@
     }
 
     private void resetBrightnessToDefault() {
-        mDozeService.setDozeScreenBrightness(mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_screenBrightnessDoze));
+        mDozeService.setDozeScreenBrightness(mDefaultDozeBrightness);
     }
 
     private void setLightSensorEnabled(boolean enabled) {
         if (enabled && !mRegistered && mLightSensor != null) {
+            // Wait until we get an event from the sensor until indicating ready.
+            setReady(false);
             mRegistered = mSensorManager.registerListener(this, mLightSensor,
                     SensorManager.SENSOR_DELAY_NORMAL, mHandler);
         } else if (!enabled && mRegistered) {
             mSensorManager.unregisterListener(this);
             mRegistered = false;
+            // Sensor is not enabled, hence we use the default brightness and are always ready.
+            setReady(true);
         }
     }
+
+    private void setReady(boolean ready) {
+        if (ready != mReady) {
+            mReady = ready;
+            if (mReadyListener != null) {
+                mReadyListener.onBrightnessReadyChanged(mReady);
+            }
+        }
+    }
+
+    public void setBrightnessReadyListener(ReadyListener l) {
+        mReadyListener = l;
+        l.onBrightnessReadyChanged(mReady);
+    }
+
+    /**
+     * @return true if the screen brightness is properly calculated.
+     *
+     * Can be used to wait for transitioning out of the paused state, such that we don't turn the
+     * display on before the display brightness is properly calculated.
+     */
+    public boolean isReady() {
+        return mReady;
+    }
+
+    public interface ReadyListener {
+        void onBrightnessReadyChanged(boolean ready);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 02a725d..bef9cb3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -25,8 +25,9 @@
 public class DozeScreenState implements DozeMachine.Part {
     private final DozeMachine.Service mDozeService;
     private final Handler mHandler;
+    private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
+
     private int mPendingScreenState = Display.STATE_UNKNOWN;
-    private Runnable mApplyPendingScreenState = this::applyPendingScreenState;
 
     public DozeScreenState(DozeMachine.Service service, Handler handler) {
         mDozeService = service;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index f7a258a..8d8c55c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -33,6 +33,7 @@
 import android.text.format.Formatter;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.internal.util.Preconditions;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -65,6 +66,7 @@
     private final boolean mAllowPulseTriggers;
     private final UiModeManager mUiModeManager;
     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
+    private final DozeScreenBrightness mDozeScreenBrightness;
 
     private long mNotificationPulseTime;
     private boolean mPulsePending;
@@ -73,7 +75,7 @@
     public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
             AlarmManager alarmManager, AmbientDisplayConfiguration config,
             DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
-            WakeLock wakeLock, boolean allowPulseTriggers) {
+            DozeScreenBrightness brightness, WakeLock wakeLock, boolean allowPulseTriggers) {
         mContext = context;
         mMachine = machine;
         mDozeHost = dozeHost;
@@ -87,6 +89,7 @@
                 config, wakeLock, this::onSensor, this::onProximityFar,
                 new AlwaysOnDisplayPolicy(context));
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
+        mDozeScreenBrightness = brightness;
     }
 
     private void onNotification() {
@@ -159,16 +162,41 @@
     private void onProximityFar(boolean far) {
         final boolean near = !far;
         final DozeMachine.State state = mMachine.getState();
-        final boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED);
-        final boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING);
-        final boolean aod = (state == DozeMachine.State.DOZE_AOD);
 
         if (state == DozeMachine.State.DOZE_PULSING) {
             boolean ignoreTouch = near;
             if (DEBUG) Log.i(TAG, "Prox changed, ignore touch = " + ignoreTouch);
             mDozeHost.onIgnoreTouchWhilePulsing(ignoreTouch);
         }
-        if (far && (paused || pausing)) {
+
+        recalculatePausing();
+    }
+
+    private void onBrightnessReady(boolean brightnessReady) {
+        // Post because this is sometimes called during state transitions and we cannot query
+        // the machine's state while it's transitioning.
+        mHandler.post(this::recalculatePausing);
+    }
+
+    private void recalculatePausing() {
+        boolean brightnessReady = mDozeScreenBrightness.isReady();
+        Boolean proxCurrentlyFar = mDozeSensors.isProximityCurrentlyFar();
+
+        // Treat UNKNOWN the same as FAR, such that we don't pause the display just because
+        // the prox has unknown state.
+        boolean proximityFar = proxCurrentlyFar == null || proxCurrentlyFar;
+        recalculatePausing(proximityFar, brightnessReady);
+    }
+
+    @VisibleForTesting
+    void recalculatePausing(boolean proximityFar, boolean brightnessReady) {
+        final boolean near = !proximityFar;
+        final DozeMachine.State state = mMachine.getState();
+        final boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED);
+        final boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING);
+        final boolean aod = (state == DozeMachine.State.DOZE_AOD);
+
+        if (proximityFar && (pausing || paused && brightnessReady)) {
             if (DEBUG) Log.i(TAG, "Prox FAR, unpausing AOD");
             mMachine.requestState(DozeMachine.State.DOZE_AOD);
         } else if (near && aod) {
@@ -183,6 +211,7 @@
             case INITIALIZED:
                 mBroadcastReceiver.register(mContext);
                 mDozeHost.addCallback(mHostCallback);
+                mDozeScreenBrightness.setBrightnessReadyListener(this::onBrightnessReady);
                 checkTriggersAtInit();
                 break;
             case DOZE:
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 13ba7c1..901b0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
+import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_DISMISS_FRACTION;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MENU_STATE;
@@ -132,7 +133,8 @@
                     showMenu(data.getInt(EXTRA_MENU_STATE),
                             data.getParcelable(EXTRA_STACK_BOUNDS),
                             data.getParcelable(EXTRA_MOVEMENT_BOUNDS),
-                            data.getBoolean(EXTRA_ALLOW_TIMEOUT));
+                            data.getBoolean(EXTRA_ALLOW_TIMEOUT),
+                            data.getBoolean(EXTRA_WILL_RESIZE_MENU));
                     break;
                 }
                 case MESSAGE_POKE_MENU:
@@ -307,12 +309,14 @@
     }
 
     private void showMenu(int menuState, Rect stackBounds, Rect movementBounds,
-            boolean allowMenuTimeout) {
+            boolean allowMenuTimeout, boolean resizeMenuOnShow) {
         mAllowMenuTimeout = allowMenuTimeout;
         if (mMenuState != menuState) {
-            boolean deferTouchesUntilAnimationEnds = (mMenuState == MENU_STATE_FULL) ||
-                    (menuState == MENU_STATE_FULL);
-            mAllowTouches = !deferTouchesUntilAnimationEnds;
+            // Disallow touches if the menu needs to resize while showing, and we are transitioning
+            // to/from a full menu state.
+            boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow &&
+                    (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
+            mAllowTouches = !disallowTouchesUntilAnimationEnd;
             cancelDelayedFinish();
             updateActionViews(stackBounds);
             if (mMenuContainerAnimator != null) {
@@ -409,7 +413,8 @@
             Rect stackBounds = intent.getParcelableExtra(EXTRA_STACK_BOUNDS);
             Rect movementBounds = intent.getParcelableExtra(EXTRA_MOVEMENT_BOUNDS);
             boolean allowMenuTimeout = intent.getBooleanExtra(EXTRA_ALLOW_TIMEOUT, true);
-            showMenu(menuState, stackBounds, movementBounds, allowMenuTimeout);
+            boolean willResizeMenu = intent.getBooleanExtra(EXTRA_WILL_RESIZE_MENU, false);
+            showMenu(menuState, stackBounds, movementBounds, allowMenuTimeout, willResizeMenu);
         }
     }
 
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 c558056..e898a51 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -63,6 +63,7 @@
     public static final String EXTRA_STACK_BOUNDS = "stack_bounds";
     public static final String EXTRA_MOVEMENT_BOUNDS = "movement_bounds";
     public static final String EXTRA_ALLOW_TIMEOUT = "allow_timeout";
+    public static final String EXTRA_WILL_RESIZE_MENU = "resize_menu_on_show";
     public static final String EXTRA_DISMISS_FRACTION = "dismiss_fraction";
     public static final String EXTRA_MENU_STATE = "menu_state";
 
@@ -268,7 +269,8 @@
             // If we haven't requested the start activity, or if it previously took too long to
             // start, then start it
             startMenuActivity(MENU_STATE_NONE, null /* stackBounds */,
-                    null /* movementBounds */, false /* allowMenuTimeout */);
+                    null /* movementBounds */, false /* allowMenuTimeout */,
+                    false /* resizeMenuOnShow */);
         }
     }
 
@@ -276,18 +278,20 @@
      * Shows the menu activity.
      */
     public void showMenu(int menuState, Rect stackBounds, Rect movementBounds,
-            boolean allowMenuTimeout) {
+            boolean allowMenuTimeout, boolean willResizeMenu) {
         if (DEBUG) {
             Log.d(TAG, "showMenu() state=" + menuState
                     + " hasActivity=" + (mToActivityMessenger != null)
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
+
         if (mToActivityMessenger != null) {
             Bundle data = new Bundle();
             data.putInt(EXTRA_MENU_STATE, menuState);
             data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
             data.putParcelable(EXTRA_MOVEMENT_BOUNDS, movementBounds);
             data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
+            data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
             Message m = Message.obtain();
             m.what = PipMenuActivity.MESSAGE_SHOW_MENU;
             m.obj = data;
@@ -299,7 +303,8 @@
         } else if (!mStartActivityRequested || isStartActivityRequestedElapsed()) {
             // If we haven't requested the start activity, or if it previously took too long to
             // start, then start it
-            startMenuActivity(menuState, stackBounds, movementBounds, allowMenuTimeout);
+            startMenuActivity(menuState, stackBounds, movementBounds, allowMenuTimeout,
+                    willResizeMenu);
         }
     }
 
@@ -372,7 +377,7 @@
      * Starts the menu activity on the top task of the pinned stack.
      */
     private void startMenuActivity(int menuState, Rect stackBounds, Rect movementBounds,
-            boolean allowMenuTimeout) {
+            boolean allowMenuTimeout, boolean willResizeMenu) {
         try {
             StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
@@ -388,6 +393,7 @@
                 }
                 intent.putExtra(EXTRA_MENU_STATE, menuState);
                 intent.putExtra(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
+                intent.putExtra(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
                 ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
                 options.setLaunchTaskId(
                         pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
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 8ddd888..3181481 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -170,7 +170,7 @@
         @Override
         public void onPipShowMenu() {
             mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                    mMovementBounds, true /* allowMenuTimeout */);
+                    mMovementBounds, true /* allowMenuTimeout */, willResizeMenu());
         }
     }
 
@@ -214,7 +214,7 @@
         // Only show the menu if the user isn't currently interacting with the PiP
         if (!mTouchState.isUserInteracting()) {
             mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                    mMovementBounds, false /* allowMenuTimeout */);
+                    mMovementBounds, false /* allowMenuTimeout */, willResizeMenu());
         }
     }
 
@@ -236,7 +236,7 @@
 
         if (mShowPipMenuOnAnimationEnd) {
             mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
-                    mMovementBounds, true /* allowMenuTimeout */);
+                    mMovementBounds, true /* allowMenuTimeout */, false /* willResizeMenu */);
             mShowPipMenuOnAnimationEnd = false;
         }
     }
@@ -337,7 +337,7 @@
 
     private void onAccessibilityShowMenu() {
         mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                mMovementBounds, false /* allowMenuTimeout */);
+                mMovementBounds, false /* allowMenuTimeout */, willResizeMenu());
     }
 
     private boolean handleTouchEvent(MotionEvent ev) {
@@ -704,7 +704,7 @@
                     // If the menu is still visible, and we aren't minimized, then just poke the
                     // menu so that it will timeout after the user stops touching it
                     mMenuController.showMenu(mMenuState, mMotionHelper.getBounds(),
-                            mMovementBounds, true /* allowMenuTimeout */);
+                            mMovementBounds, true /* allowMenuTimeout */, willResizeMenu());
                 } else {
                     // If the menu is not visible, then we can still be showing the activity for the
                     // dismiss overlay, so just finish it after the animation completes
@@ -731,7 +731,7 @@
                 setMinimizedStateInternal(false);
             } else if (mMenuState != MENU_STATE_FULL) {
                 mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                        mMovementBounds, true /* allowMenuTimeout */);
+                        mMovementBounds, true /* allowMenuTimeout */, willResizeMenu());
             } else {
                 mMenuController.hideMenu();
                 mMotionHelper.expandPip();
@@ -773,6 +773,14 @@
         cleanUpDismissTarget();
     }
 
+    /**
+     * @return whether the menu will resize as a part of showing the full menu.
+     */
+    private boolean willResizeMenu() {
+        return mExpandedBounds.width() != mNormalBounds.width() ||
+                mExpandedBounds.height() != mNormalBounds.height();
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 46e1d55..4f59fc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
@@ -27,6 +28,7 @@
 import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -38,15 +40,17 @@
 import com.android.systemui.utils.hardware.FakeSensorManager;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@Ignore
 public class DozeScreenBrightnessTest extends SysuiTestCase {
 
+    static final int DEFAULT_BRIGHTNESS = 10;
+    static final int[] SENSOR_TO_BRIGHTNESS = new int[]{-1, 1, 2, 3, 4};
+    static final int[] SENSOR_TO_OPACITY = new int[]{-1, 10, 0, 0, 0};
+
     DozeServiceFake mServiceFake;
     DozeScreenBrightness mScreen;
     FakeSensorManager.FakeGenericSensor mSensor;
@@ -61,14 +65,14 @@
         mSensor = mSensorManager.getFakeLightSensor();
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 mSensor.getSensor(), mHostFake, null /* handler */,
-                new AlwaysOnDisplayPolicy(mContext));
+                DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY);
     }
 
     @Test
     public void testInitialize_setsScreenBrightnessToValidValue() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
 
-        assertNotEquals(PowerManager.BRIGHTNESS_DEFAULT, mServiceFake.screenBrightness);
+        assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness);
         assertTrue(mServiceFake.screenBrightness <= PowerManager.BRIGHTNESS_ON);
     }
 
@@ -77,35 +81,37 @@
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
 
-        mSensor.sendSensorEvent(1000);
+        mSensor.sendSensorEvent(3);
 
-        assertEquals(1000, mServiceFake.screenBrightness);
+        assertEquals(3, mServiceFake.screenBrightness);
     }
 
     @Test
-    public void testPausingAod_pausesLightSensor() throws Exception {
+    public void testPausingAod_doesntPauseLightSensor() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
 
-        mSensor.sendSensorEvent(1000);
+        mSensor.sendSensorEvent(1);
 
-        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSED);
+        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
+        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
 
-        mSensor.sendSensorEvent(1001);
+        mSensor.sendSensorEvent(2);
 
-        assertNotEquals(1001, mServiceFake.screenBrightness);
+        assertEquals(2, mServiceFake.screenBrightness);
     }
 
     @Test
-    public void testPausingAod_resetsBrightness() throws Exception {
+    public void testPausingAod_doesNotResetBrightness() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
 
-        mSensor.sendSensorEvent(1000);
+        mSensor.sendSensorEvent(1);
 
-        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSED);
+        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
+        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
 
-        assertNotEquals(1000, mServiceFake.screenBrightness);
+        assertEquals(1, mServiceFake.screenBrightness);
     }
 
     @Test
@@ -114,9 +120,9 @@
         mScreen.transitionTo(INITIALIZED, DOZE);
         mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
 
-        mSensor.sendSensorEvent(1000);
+        mSensor.sendSensorEvent(1);
 
-        assertEquals(1000, mServiceFake.screenBrightness);
+        assertEquals(1, mServiceFake.screenBrightness);
     }
 
     @Test
@@ -128,20 +134,23 @@
         mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
         mScreen.transitionTo(DOZE_PULSE_DONE, DOZE);
 
-        mSensor.sendSensorEvent(1000);
+        mSensor.sendSensorEvent(1);
 
-        assertNotEquals(1000, mServiceFake.screenBrightness);
+        assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness);
     }
 
     @Test
     public void testNullSensor() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 null /* sensor */, mHostFake, null /* handler */,
-                new AlwaysOnDisplayPolicy(mContext));
+                DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSED);
+        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
+        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+
+        assertTrue(mScreen.isReady());
     }
 
     @Test
@@ -150,19 +159,93 @@
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         mScreen.transitionTo(DOZE_AOD, FINISH);
 
-        mSensor.sendSensorEvent(1000);
+        mSensor.sendSensorEvent(1);
 
-        assertNotEquals(1000, mServiceFake.screenBrightness);
+        assertNotEquals(1, mServiceFake.screenBrightness);
     }
 
     @Test
-    public void testBrightness_atLeastOne() throws Exception {
+    public void testNonPositiveBrightness_keepsPreviousBrightness() throws Exception {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        mSensor.sendSensorEvent(2);
+        mSensor.sendSensorEvent(0);
+
+        assertEquals(2, mServiceFake.screenBrightness);
+    }
+
+    @Test
+    public void readyWhenNotInitialized() {
+        assertTrue(mScreen.isReady());
+    }
+
+    @Test
+    public void readyWhenNotRegistered() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE);
+
+        assertTrue(mScreen.isReady());
+    }
+
+    @Test
+    public void notReadyWhenRegistered_butNoEventYet() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        assertFalse(mScreen.isReady());
+    }
+
+    @Test
+    public void notReady_afterZeroBrightness() {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
 
         mSensor.sendSensorEvent(0);
 
-        assertTrue("Brightness must be at least 1, but was " + mServiceFake.screenBrightness,
-                mServiceFake.screenBrightness >= 1);
+        assertFalse(mScreen.isReady());
+    }
+
+    @Test
+    public void ready_afterNonZeroBrightness() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        mSensor.sendSensorEvent(1);
+
+        assertTrue(mScreen.isReady());
+    }
+
+    @Test
+    public void notReady_nonZeroThenZeroBrightness() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        mSensor.sendSensorEvent(1);
+        mSensor.sendSensorEvent(0);
+
+        assertFalse(mScreen.isReady());
+    }
+
+    @Test
+    public void readyListener_getsCalled_whenRegistering() throws Exception {
+        Boolean[] ready = new Boolean[1];
+
+        mScreen.setBrightnessReadyListener((x) -> ready[0] = true);
+
+        assertTrue(ready[0]);
+    }
+
+    @Test
+    public void readyListener_getsCalled_whenReadyChanges() throws Exception {
+        Boolean[] ready = new Boolean[1];
+        mScreen.setBrightnessReadyListener((x) -> ready[0] = true);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        ready[0] = null;
+        mSensor.sendSensorEvent(1);
+        assertTrue(ready[0]);
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index a8ea1c0..6a9dac4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -58,6 +58,7 @@
     private WakeLock mWakeLock;
     private Instrumentation mInstrumentation;
     private AlarmManager mAlarmManager;
+    private DozeScreenBrightness mDozeScreenBrightness;
 
     @BeforeClass
     public static void setupSuite() {
@@ -76,10 +77,12 @@
         mSensors = new FakeSensorManager(mContext);
         mHandler = new Handler(Looper.getMainLooper());
         mWakeLock = new WakeLockFake();
+        mDozeScreenBrightness = mock(DozeScreenBrightness.class);
 
         mInstrumentation.runOnMainSync(() -> {
             mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager,
-                    mConfig, mParameters, mSensors, mHandler, mWakeLock, true);
+                    mConfig, mParameters, mSensors, mHandler, mDozeScreenBrightness,
+                    mWakeLock, true);
         });
     }
 
@@ -112,4 +115,22 @@
         verify(mMachine).requestPulse(anyInt());
     }
 
+    @Test
+    public void unpausing_fromPaused_waitsForBrightnessReady() throws Exception {
+        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_AOD_PAUSED);
+
+        mTriggers.recalculatePausing(true /* proxFar */, false /* brightnessReady */);
+        verify(mMachine, never()).requestState(any());
+
+        mTriggers.recalculatePausing(true /* proxFar */, true /* brightnessReady */);
+        verify(mMachine).requestState(DozeMachine.State.DOZE_AOD);
+    }
+
+    @Test
+    public void unpausing_fromPausing_doesntWaitForBrightnessReady() throws Exception {
+        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_AOD_PAUSED);
+
+        mTriggers.recalculatePausing(true /* proxFar */, false /* brightnessReady */);
+        verify(mMachine).requestState(DozeMachine.State.DOZE_AOD);
+    }
 }
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index a287254..1d6e76f 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4282,6 +4282,11 @@
     // the app transition.
     APP_TRANSITION_REPORTED_DRAWN_MS = 1091;
 
+    // OPEN: Settings > Storage > Photos & Videos
+    // CATEGORY: SETTINGS
+    // OS: O MR
+    APPLICATIONS_STORAGE_PHOTOS = 1092;
+
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 29d562b..92d3d39 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1315,7 +1315,7 @@
         mRunBackupIntent = PendingIntent.getBroadcast(context, MSG_RUN_BACKUP, backupIntent, 0);
 
         Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
-        backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
 
         // Set up the backup-request journaling
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index d118917..e8c5058 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -750,7 +750,7 @@
         mRunBackupIntent = PendingIntent.getBroadcast(context, 0, backupIntent, 0);
 
         Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
-        backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
 
         // Set up the backup-request journaling
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e7d1ee8..810a6bf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1471,7 +1471,7 @@
      * Flag that indicates if multi-window is enabled.
      *
      * For any particular form of multi-window to be enabled, generic multi-window must be enabled
-     * in {@link com.android.internal.R.bool.config_supportsMultiWindow} config or
+     * in {@link com.android.internal.R.bool#config_supportsMultiWindow} config or
      * {@link Settings.Global#DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES} development option set.
      * At least one of the forms of multi-window must be enabled in order for this flag to be
      * initialized to 'true'.
@@ -13464,7 +13464,7 @@
     /**
      * Schedule the given thread a normal scheduling priority.
      *
-     * @param newTid the tid of the thread to adjust the scheduling of.
+     * @param tid the tid of the thread to adjust the scheduling of.
      * @param suppressLogs {@code true} if any error logging should be disabled.
      *
      * @return {@code true} if this succeeded.
@@ -13477,6 +13477,10 @@
             if (!suppressLogs) {
                 Slog.w(TAG, "Failed to set scheduling policy, thread does not exist:\n" + e);
             }
+        } catch (SecurityException e) {
+            if (!suppressLogs) {
+                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
+            }
         }
         return false;
     }
@@ -13484,7 +13488,7 @@
     /**
      * Schedule the given thread an FIFO scheduling priority.
      *
-     * @param newTid the tid of the thread to adjust the scheduling of.
+     * @param tid the tid of the thread to adjust the scheduling of.
      * @param suppressLogs {@code true} if any error logging should be disabled.
      *
      * @return {@code true} if this succeeded.
@@ -13497,6 +13501,10 @@
             if (!suppressLogs) {
                 Slog.w(TAG, "Failed to set scheduling policy, thread does not exist:\n" + e);
             }
+        } catch (SecurityException e) {
+            if (!suppressLogs) {
+                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
+            }
         }
         return false;
     }
@@ -22054,13 +22062,21 @@
                                app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                         mVrController.onTopProcChangedLocked(app);
                         if (mUseFifoUiScheduling) {
-                            // Reset UI pipeline to SCHED_OTHER
-                            setThreadScheduler(app.pid, SCHED_OTHER, 0);
-                            setThreadPriority(app.pid, app.savedPriority);
-                            if (app.renderThreadTid != 0) {
-                                setThreadScheduler(app.renderThreadTid,
-                                    SCHED_OTHER, 0);
-                                setThreadPriority(app.renderThreadTid, -4);
+                            try {
+                                // Reset UI pipeline to SCHED_OTHER
+                                setThreadScheduler(app.pid, SCHED_OTHER, 0);
+                                setThreadPriority(app.pid, app.savedPriority);
+                                if (app.renderThreadTid != 0) {
+                                    setThreadScheduler(app.renderThreadTid,
+                                        SCHED_OTHER, 0);
+                                    setThreadPriority(app.renderThreadTid, -4);
+                                }
+                            } catch (IllegalArgumentException e) {
+                                Slog.w(TAG,
+                                        "Failed to set scheduling policy, thread does not exist:\n"
+                                                + e);
+                            } catch (SecurityException e) {
+                                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
                             }
                         } else {
                             // Reset priority for top app UI and render threads
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 6d5c428..788867f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -63,6 +63,8 @@
  */
 public class OffloadController {
     private static final String TAG = OffloadController.class.getSimpleName();
+    private static final String ANYIP = "0.0.0.0";
+    private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
 
     private final Handler mHandler;
     private final OffloadHardwareInterface mHwInterface;
@@ -148,6 +150,14 @@
                     public void onStoppedUnsupported() {
                         if (!started()) return;
                         mLog.log("onStoppedUnsupported");
+                        // Poll for statistics and trigger a sweep of tethering
+                        // stats by observers. This might not succeed, but it's
+                        // worth trying anyway. We need to do this because from
+                        // this point on we continue with software forwarding,
+                        // and we need to synchronize stats and limits between
+                        // software and hardware forwarding.
+                        updateStatsForAllUpstreams();
+                        forceTetherStatsPoll();
                     }
 
                     @Override
@@ -155,11 +165,15 @@
                         if (!started()) return;
                         mLog.log("onSupportAvailable");
 
-                        // [1] Poll for statistics and notify NetworkStats
-                        // [2] (Re)Push all state:
-                        //     [a] push local prefixes
-                        //     [b] push downstreams
-                        //     [c] push upstream parameters
+                        // [1] Poll for statistics and trigger a sweep of stats
+                        // by observers. We need to do this to ensure that any
+                        // limits set take into account any software tethering
+                        // traffic that has been happening in the meantime.
+                        updateStatsForAllUpstreams();
+                        forceTetherStatsPoll();
+                        // [2] (Re)Push all state.
+                        // TODO: computeAndPushLocalPrefixes()
+                        // TODO: push all downstream state.
                         pushUpstreamParameters(null);
                     }
 
@@ -181,12 +195,7 @@
                         // The stats for the previous upstream were already updated on this thread
                         // just after the upstream was changed, so they are also up-to-date.
                         updateStatsForCurrentUpstream();
-
-                        try {
-                            mNms.tetherLimitReached(mStatsProvider);
-                        } catch (RemoteException e) {
-                            mLog.e("Cannot report data limit reached: " + e);
-                        }
+                        forceTetherStatsPoll();
                     }
 
                     @Override
@@ -305,13 +314,33 @@
         maybeUpdateStats(currentUpstreamInterface());
     }
 
+    private void updateStatsForAllUpstreams() {
+        // In practice, there should only ever be a single digit number of
+        // upstream interfaces over the lifetime of an active tethering session.
+        // Roughly speaking, imagine a very ambitious one or two of each of the
+        // following interface types: [ "rmnet_data", "wlan", "eth", "rndis" ].
+        for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
+            maybeUpdateStats(kv.getKey());
+        }
+    }
+
+    private void forceTetherStatsPoll() {
+        try {
+            mNms.tetherLimitReached(mStatsProvider);
+        } catch (RemoteException e) {
+            mLog.e("Cannot report data limit reached: " + e);
+        }
+    }
+
     public void setUpstreamLinkProperties(LinkProperties lp) {
         if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
 
-        String prevUpstream = (mUpstreamLinkProperties != null) ?
-                mUpstreamLinkProperties.getInterfaceName() : null;
+        final String prevUpstream = currentUpstreamInterface();
 
         mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;
+        // Make sure we record this interface in the ForwardedStats map.
+        final String iface = currentUpstreamInterface();
+        if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS);
 
         // TODO: examine return code and decide what to do if programming
         // upstream parameters fails (probably just wait for a subsequent
@@ -378,16 +407,20 @@
     }
 
     private boolean pushUpstreamParameters(String prevUpstream) {
-        if (mUpstreamLinkProperties == null) {
+        final String iface = currentUpstreamInterface();
+
+        if (TextUtils.isEmpty(iface)) {
+            final boolean rval = mHwInterface.setUpstreamParameters("", ANYIP, ANYIP, null);
+            // Update stats after we've told the hardware to stop forwarding so
+            // we don't miss packets.
             maybeUpdateStats(prevUpstream);
-            return mHwInterface.setUpstreamParameters(null, null, null, null);
+            return rval;
         }
 
         // A stacked interface cannot be an upstream for hardware offload.
         // Consequently, we examine only the primary interface name, look at
         // getAddresses() rather than getAllAddresses(), and check getRoutes()
         // rather than getAllRoutes().
-        final String iface = mUpstreamLinkProperties.getInterfaceName();
         final ArrayList<String> v6gateways = new ArrayList<>();
         String v4addr = null;
         String v4gateway = null;
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index 8837c15..4ceb592 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -177,6 +177,34 @@
         }
     }
 
+    /**
+     * Safely multiple a value by a rational.
+     * <p>
+     * Internally it uses integer-based math whenever possible, but switches
+     * over to double-based math if values would overflow.
+     */
+    @VisibleForTesting
+    public static long multiplySafe(long value, long num, long den) {
+        long x = value;
+        long y = num;
+
+        // Logic shamelessly borrowed from Math.multiplyExact()
+        long r = x * y;
+        long ax = Math.abs(x);
+        long ay = Math.abs(y);
+        if (((ax | ay) >>> 31 != 0)) {
+            // Some bits greater than 2^31 that might cause overflow
+            // Check the result using the divide operator
+            // and check for the special case of Long.MIN_VALUE * -1
+            if (((y != 0) && (r / y != x)) ||
+                    (x == Long.MIN_VALUE && y == -1)) {
+                // Use double math to avoid overflowing
+                return (long) (((double) num / den) * value);
+            }
+        }
+        return r / den;
+    }
+
     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
         return getRelevantUids(accessLevel, Binder.getCallingUid());
     }
@@ -274,8 +302,8 @@
             final long rawRxBytes = entry.rxBytes;
             final long rawTxBytes = entry.txBytes;
             final long targetBytes = augmentPlan.getDataUsageBytes();
-            final long targetRxBytes = (rawRxBytes * targetBytes) / rawBytes;
-            final long targetTxBytes = (rawTxBytes * targetBytes) / rawBytes;
+            final long targetRxBytes = multiplySafe(targetBytes, rawRxBytes, rawBytes);
+            final long targetTxBytes = multiplySafe(targetBytes, rawTxBytes, rawBytes);
 
             // Scale all matching buckets to reach anchor target
             final long beforeTotal = combined.getTotalBytes();
@@ -283,8 +311,8 @@
                 combined.getValues(i, entry);
                 if (entry.bucketStart >= augmentStart
                         && entry.bucketStart + entry.bucketDuration <= augmentEnd) {
-                    entry.rxBytes = (entry.rxBytes * targetRxBytes) / rawRxBytes;
-                    entry.txBytes = (entry.txBytes * targetTxBytes) / rawTxBytes;
+                    entry.rxBytes = multiplySafe(targetRxBytes, entry.rxBytes, rawRxBytes);
+                    entry.txBytes = multiplySafe(targetTxBytes, entry.txBytes, rawTxBytes);
                     // We purposefully clear out packet counters to indicate
                     // that this data has been augmented.
                     entry.rxPackets = 0;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index a2c9283..7b047a8 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -53,7 +53,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
     private static final String REDUCED_POSTFIX = "_reduced";
-    static final float REDUCED_SCALE = 0.5f;
+    static final float REDUCED_SCALE = ActivityManager.isLowRamDeviceStatic() ? 0.6f : 0.5f;
     static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
     private static final long DELAY_MS = 100;
     private static final int QUALITY = 95;
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index dfe3f98..7a2ff89 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -32,12 +32,13 @@
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -436,6 +437,9 @@
         ethernetStats.txBytes = 100000;
         when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
         offload.setUpstreamLinkProperties(null);
+        // Expect that we first clear the HAL's upstream parameters.
+        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+                eq(""), eq("0.0.0.0"), eq("0.0.0.0"), eq(null));
         // Expect that we fetch stats from the previous upstream.
         inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
 
@@ -445,8 +449,6 @@
         waitForIdle();
         // There is no current upstream, so no stats are fetched.
         inOrder.verify(mHardware, never()).getForwardedStats(any());
-        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
-                eq(null), eq(null), eq(null), eq(null));
         inOrder.verifyNoMoreInteractions();
 
         assertEquals(2, stats.size());
@@ -621,6 +623,73 @@
         inOrder.verifyNoMoreInteractions();
     }
 
+    @Test
+    public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception {
+        setupFunctioningHardwareInterface();
+        enableOffload();
+
+        final OffloadController offload = makeOffloadController();
+        offload.start();
+
+        // Pretend to set a few different upstreams (only the interface name
+        // matters for this test; we're ignoring IP and route information).
+        final LinkProperties upstreamLp = new LinkProperties();
+        for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) {
+            upstreamLp.setInterfaceName(ifname);
+            offload.setUpstreamLinkProperties(upstreamLp);
+        }
+
+        // Clear invocation history, especially the getForwardedStats() calls
+        // that happen with setUpstreamParameters().
+        clearInvocations(mHardware);
+
+        OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        callback.onStoppedUnsupported();
+
+        // Verify forwarded stats behaviour.
+        verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
+        verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
+        verifyNoMoreInteractions(mHardware);
+        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+        verifyNoMoreInteractions(mNMService);
+    }
+
+    @Test
+    public void testControlCallbackOnSupportAvailableFetchesAllStatsAndPushesAllParameters()
+            throws Exception {
+        setupFunctioningHardwareInterface();
+        enableOffload();
+
+        final OffloadController offload = makeOffloadController();
+        offload.start();
+
+        // Pretend to set a few different upstreams (only the interface name
+        // matters for this test; we're ignoring IP and route information).
+        final LinkProperties upstreamLp = new LinkProperties();
+        for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) {
+            upstreamLp.setInterfaceName(ifname);
+            offload.setUpstreamLinkProperties(upstreamLp);
+        }
+
+        // Clear invocation history, especially the getForwardedStats() calls
+        // that happen with setUpstreamParameters().
+        clearInvocations(mHardware);
+
+        OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        callback.onSupportAvailable();
+
+        // Verify forwarded stats behaviour.
+        verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
+        verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
+        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+        verifyNoMoreInteractions(mNMService);
+
+        // TODO: verify local prefixes and downstreams are also pushed to the HAL.
+        verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any());
+        verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong());
+        verifyNoMoreInteractions(mHardware);
+    }
+
     private static void assertArrayListContains(ArrayList<String> list, String... elems) {
         for (String element : elems) {
             assertTrue(list.contains(element));
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 0c2ad38..9c10264 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -27,7 +27,10 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import static com.android.server.net.NetworkStatsCollection.multiplySafe;
+
 import android.content.res.Resources;
+import android.net.ConnectivityManager;
 import android.net.NetworkIdentity;
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
@@ -40,6 +43,7 @@
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.format.DateUtils;
+import android.util.RecurrenceRule;
 
 import com.android.frameworks.tests.net.R;
 
@@ -53,6 +57,9 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.List;
@@ -70,14 +77,27 @@
     private static final long TIME_B = 1326110400000L; // UTC: Monday 9th January 2012 12:00:00 PM
     private static final long TIME_C = 1326132000000L; // UTC: Monday 9th January 2012 06:00:00 PM
 
+    private static Clock sOriginalClock;
+
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        sOriginalClock = RecurrenceRule.sClock;
 
         // ignore any device overlay while testing
         NetworkTemplate.forceAllNetworkTypes();
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        RecurrenceRule.sClock = sOriginalClock;
+    }
+
+    private void setClock(Instant instant) {
+        RecurrenceRule.sClock = Clock.fixed(instant, ZoneId.systemDefault());
+    }
+
     public void testReadLegacyNetwork() throws Exception {
         final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
         stageFile(R.raw.netstats_v1, testFile);
@@ -238,6 +258,9 @@
         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
         collection.readLegacyNetwork(testFile);
 
+        // We're in the future, but not that far off
+        setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
+
         // Test a bunch of plans that should result in no augmentation
         final List<SubscriptionPlan> plans = new ArrayList<>();
 
@@ -416,6 +439,28 @@
         }
     }
 
+    public void testAugmentPlanGigantic() throws Exception {
+        // We're in the future, but not that far off
+        setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
+
+        // Create a simple history with a ton of measured usage
+        final NetworkStatsCollection large = new NetworkStatsCollection(HOUR_IN_MILLIS);
+        final NetworkIdentitySet ident = new NetworkIdentitySet();
+        ident.add(new NetworkIdentity(ConnectivityManager.TYPE_MOBILE, -1, TEST_IMSI, null,
+                false, true));
+        large.recordData(ident, UID_ALL, SET_ALL, TAG_NONE, TIME_A, TIME_B,
+                new NetworkStats.Entry(12_730_893_164L, 1, 0, 0, 0));
+
+        // Verify untouched total
+        assertEquals(12_730_893_164L, getHistory(large, null, TIME_A, TIME_C).getTotalBytes());
+
+        // Verify anchor that might cause overflows
+        final SubscriptionPlan plan = SubscriptionPlan.Builder
+                .createRecurringMonthly(ZonedDateTime.parse("2012-01-09T00:00:00.00Z"))
+                .setDataUsage(4_939_212_390L, TIME_B).build();
+        assertEquals(4_939_212_386L, getHistory(large, plan, TIME_A, TIME_C).getTotalBytes());
+    }
+
     public void testRounding() throws Exception {
         final NetworkStatsCollection coll = new NetworkStatsCollection(HOUR_IN_MILLIS);
 
@@ -437,6 +482,25 @@
         assertEquals(TIME_A - HOUR_IN_MILLIS, coll.roundDown(TIME_A - 1));
     }
 
+    public void testMultiplySafe() {
+        assertEquals(25, multiplySafe(50, 1, 2));
+        assertEquals(100, multiplySafe(50, 2, 1));
+
+        assertEquals(-10, multiplySafe(30, -1, 3));
+        assertEquals(0, multiplySafe(30, 0, 3));
+        assertEquals(10, multiplySafe(30, 1, 3));
+        assertEquals(20, multiplySafe(30, 2, 3));
+        assertEquals(30, multiplySafe(30, 3, 3));
+        assertEquals(40, multiplySafe(30, 4, 3));
+
+        assertEquals(100_000_000_000L,
+                multiplySafe(300_000_000_000L, 10_000_000_000L, 30_000_000_000L));
+        assertEquals(100_000_000_010L,
+                multiplySafe(300_000_000_000L, 10_000_000_001L, 30_000_000_000L));
+        assertEquals(823_202_048L,
+                multiplySafe(4_939_212_288L, 2_121_815_528L, 12_730_893_165L));
+    }
+
     /**
      * Copy a {@link Resources#openRawResource(int)} into {@link File} for
      * testing purposes.