Merge "Remove stack scroller references from StatusBar"
diff --git a/Android.bp b/Android.bp
index 1586440..07b3018 100644
--- a/Android.bp
+++ b/Android.bp
@@ -495,6 +495,8 @@
         "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl",
         "telephony/java/android/telephony/data/IDataService.aidl",
         "telephony/java/android/telephony/data/IDataServiceCallback.aidl",
+        "telephony/java/android/telephony/data/IQualifiedNetworksService.aidl",
+        "telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl",
         "telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl",
         "telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl",
         "telephony/java/android/telephony/ims/aidl/IImsConfig.aidl",
@@ -1524,6 +1526,10 @@
             api_file: "api/current.txt",
             removed_api_file: "api/removed.txt",
         },
+        last_released: {
+            api_file: ":last-released-public-api",
+            removed_api_file: "api/removed.txt",
+        },
     },
 }
 
@@ -1544,6 +1550,10 @@
             api_file: "api/system-current.txt",
             removed_api_file: "api/system-removed.txt",
         },
+        last_released: {
+            api_file: ":last-released-system-api",
+            removed_api_file: "api/system-removed.txt",
+        },
     },
 }
 
diff --git a/api/current.txt b/api/current.txt
index fc595ac..e9f5756 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14726,6 +14726,22 @@
     method public void setColorFilter(android.graphics.ColorFilter);
   }
 
+  public class ColorStateListDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+    ctor public ColorStateListDrawable();
+    ctor public ColorStateListDrawable(android.content.res.ColorStateList);
+    method public void clearAlpha();
+    method public void draw(android.graphics.Canvas);
+    method public android.content.res.ColorStateList getColorStateList();
+    method public int getOpacity();
+    method public boolean hasFocusStateSpecified();
+    method public void invalidateDrawable(android.graphics.drawable.Drawable);
+    method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+    method public void setAlpha(int);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void setColorStateList(android.content.res.ColorStateList);
+    method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+  }
+
   public abstract class Drawable {
     ctor public Drawable();
     method public void applyTheme(android.content.res.Resources.Theme);
@@ -41948,9 +41964,10 @@
     field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
     field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
     field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
+    field public static final java.lang.String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array";
     field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
     field public static final java.lang.String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
-    field public static final java.lang.String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
+    field public static final deprecated java.lang.String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
     field public static final java.lang.String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool";
     field public static final java.lang.String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
     field public static final java.lang.String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
@@ -53001,6 +53018,7 @@
     method public int getWidth();
     method public float getZoom();
     method public void show(float, float);
+    method public void show(float, float, float, float);
     method public void update();
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index d9befc7..72cf0c7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4725,6 +4725,7 @@
     method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
     method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+    method public void onNotificationsSeen(java.util.List<java.lang.String>);
     method public final void unsnoozeNotification(java.lang.String);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
   }
@@ -4733,6 +4734,7 @@
     ctor public NotificationStats();
     ctor protected NotificationStats(android.os.Parcel);
     method public int describeContents();
+    method public int getDismissalSentiment();
     method public int getDismissalSurface();
     method public boolean hasDirectReplied();
     method public boolean hasExpanded();
@@ -4741,6 +4743,7 @@
     method public boolean hasSnoozed();
     method public boolean hasViewedSettings();
     method public void setDirectReplied();
+    method public void setDismissalSentiment(int);
     method public void setDismissalSurface(int);
     method public void setExpanded();
     method public void setSeen();
@@ -4753,6 +4756,10 @@
     field public static final int DISMISSAL_OTHER = 0; // 0x0
     field public static final int DISMISSAL_PEEK = 1; // 0x1
     field public static final int DISMISSAL_SHADE = 3; // 0x3
+    field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
+    field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
+    field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
+    field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
   }
 
   public final class SnoozeCriterion implements android.os.Parcelable {
@@ -5546,6 +5553,19 @@
     field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
+  public abstract class QualifiedNetworksService extends android.app.Service {
+    ctor public QualifiedNetworksService();
+    method public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityUpdater createNetworkAvailabilityUpdater(int);
+    field public static final java.lang.String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService";
+  }
+
+  public abstract class QualifiedNetworksService.NetworkAvailabilityUpdater implements java.lang.AutoCloseable {
+    ctor public QualifiedNetworksService.NetworkAvailabilityUpdater(int);
+    method public abstract void close();
+    method public final int getSlotIndex();
+    method public final void updateQualifiedNetworkTypes(int, int[]);
+  }
+
 }
 
 package android.telephony.euicc {
@@ -6206,6 +6226,10 @@
     field public static final int STATE_UNAVAILABLE = 0; // 0x0
   }
 
+  public static class ImsFeature.Capabilities {
+    field protected int mCapabilities;
+  }
+
   protected static class ImsFeature.CapabilityCallbackProxy {
     method public void onChangeCapabilityConfigurationError(int, int, int);
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 1657de5..f6cda32 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5,11 +5,11 @@
     field public static final java.lang.String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final java.lang.String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
+    field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
     field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
+    field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
     field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final java.lang.String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
-    field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
-    field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
   }
 
 }
@@ -28,11 +28,11 @@
   public class ActivityManager {
     method public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
     method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
+    method public void forceStopPackage(java.lang.String);
     method public int getPackageImportance(java.lang.String);
     method public long getTotalRam();
     method public int getUidImportance(int);
     method public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
-    method public void forceStopPackage(java.lang.String);
     method public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
   }
 
@@ -53,21 +53,21 @@
   }
 
   public class ActivityTaskManager {
+    method public java.lang.String listAllStacks();
+    method public void moveTaskToStack(int, int, boolean);
+    method public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect);
     method public void removeStacksInWindowingModes(int[]) throws java.lang.SecurityException;
     method public void removeStacksWithActivityTypes(int[]) throws java.lang.SecurityException;
+    method public void resizeDockedStack(android.graphics.Rect, android.graphics.Rect);
     method public void resizeStack(int, android.graphics.Rect) throws java.lang.SecurityException;
-    method public void setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
-    method public void setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect, boolean) throws java.lang.SecurityException;
-    method public static boolean supportsMultiWindow(android.content.Context);
-    method public static boolean supportsSplitScreenMultiWindow(android.content.Context);
-    method public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect);
-    method public void startSystemLockTaskMode(int);
-    method public void stopSystemLockTaskMode();
-    method public void moveTaskToStack(int, int, boolean);
     method public void resizeStack(int, android.graphics.Rect, boolean);
     method public void resizeTask(int, android.graphics.Rect);
-    method public void resizeDockedStack(android.graphics.Rect,android.graphics.Rect);
-    method public java.lang.String listAllStacks();
+    method public void setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
+    method public void setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect, boolean) throws java.lang.SecurityException;
+    method public void startSystemLockTaskMode(int);
+    method public void stopSystemLockTaskMode();
+    method public static boolean supportsMultiWindow(android.content.Context);
+    method public static boolean supportsSplitScreenMultiWindow(android.content.Context);
     field public static final int INVALID_STACK_ID = -1; // 0xffffffff
     field public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1; // 0x1
     field public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0; // 0x0
@@ -1025,10 +1025,12 @@
 
   public abstract class InternalSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
     ctor public InternalSanitizer();
+    method public abstract android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue);
   }
 
   public abstract class InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
     ctor public InternalTransformation();
+    method public static boolean batchApply(android.service.autofill.ValueFinder, android.widget.RemoteViews, java.util.ArrayList<android.util.Pair<java.lang.Integer, android.service.autofill.InternalTransformation>>);
   }
 
   public abstract class InternalValidator implements android.os.Parcelable android.service.autofill.Validator {
@@ -1092,6 +1094,7 @@
     method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
     method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
     method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+    method public void onNotificationsSeen(java.util.List<java.lang.String>);
     method public final void unsnoozeNotification(java.lang.String);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
   }
@@ -1104,6 +1107,7 @@
     ctor public NotificationStats();
     ctor protected NotificationStats(android.os.Parcel);
     method public int describeContents();
+    method public int getDismissalSentiment();
     method public int getDismissalSurface();
     method public boolean hasDirectReplied();
     method public boolean hasExpanded();
@@ -1112,6 +1116,7 @@
     method public boolean hasSnoozed();
     method public boolean hasViewedSettings();
     method public void setDirectReplied();
+    method public void setDismissalSentiment(int);
     method public void setDismissalSurface(int);
     method public void setExpanded();
     method public void setSeen();
@@ -1124,6 +1129,10 @@
     field public static final int DISMISSAL_OTHER = 0; // 0x0
     field public static final int DISMISSAL_PEEK = 1; // 0x1
     field public static final int DISMISSAL_SHADE = 3; // 0x3
+    field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
+    field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
+    field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
+    field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
   }
 
   public final class SnoozeCriterion implements android.os.Parcelable {
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 048fb43..e915cc8 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -302,6 +302,7 @@
     mHeight = h;
     mFlingerSurfaceControl = control;
     mFlingerSurface = s;
+    mTargetInset = -1;
 
     // If the device has encryption turned on or is in process
     // of being encrypted we show the encrypted boot animation.
@@ -942,6 +943,7 @@
                 if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
                     drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
                 }
+                handleViewport(frameDuration);
 
                 eglSwapBuffers(mDisplay, mSurface);
 
@@ -966,7 +968,7 @@
             usleep(part.pause * ns2us(frameDuration));
 
             // For infinite parts, we've now played them at least once, so perhaps exit
-            if(exitPending() && !part.count)
+            if(exitPending() && !part.count && mCurrentInset >= mTargetInset)
                 break;
         }
 
@@ -986,6 +988,51 @@
     return true;
 }
 
+void BootAnimation::handleViewport(nsecs_t timestep) {
+    if (mShuttingDown || !mFlingerSurfaceControl || mTargetInset == 0) {
+        return;
+    }
+    if (mTargetInset < 0) {
+        // Poll the amount for the top display inset. This will return -1 until persistent properties
+        // have been loaded.
+        mTargetInset = android::base::GetIntProperty("persist.sys.displayinset.top",
+                -1 /* default */, -1 /* min */, mHeight / 2 /* max */);
+    }
+    if (mTargetInset <= 0) {
+        return;
+    }
+
+    if (mCurrentInset < mTargetInset) {
+        // After the device boots, the inset will effectively be cropped away. We animate this here.
+        float fraction = static_cast<float>(mCurrentInset) / mTargetInset;
+        int interpolatedInset = (cosf((fraction + 1) * M_PI) / 2.0f + 0.5f) * mTargetInset;
+
+        SurfaceComposerClient::Transaction()
+                .setCrop(mFlingerSurfaceControl, Rect(0, interpolatedInset, mWidth, mHeight))
+                .apply();
+    } else {
+        // At the end of the animation, we switch to the viewport that DisplayManager will apply
+        // later. This changes the coordinate system, and means we must move the surface up by
+        // the inset amount.
+        sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
+                ISurfaceComposer::eDisplayIdMain));
+
+        Rect layerStackRect(0, 0, mWidth, mHeight - mTargetInset);
+        Rect displayRect(0, mTargetInset, mWidth, mHeight);
+
+        SurfaceComposerClient::Transaction t;
+        t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset)
+                .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight));
+        t.setDisplayProjection(dtoken, 0 /* orientation */, layerStackRect, displayRect);
+        t.apply();
+
+        mTargetInset = mCurrentInset = 0;
+    }
+
+    int delta = timestep * mTargetInset / ms2ns(200);
+    mCurrentInset += delta;
+}
+
 void BootAnimation::releaseAnimation(Animation* animation) const
 {
     for (Vector<Animation::Part>::iterator it = animation->parts.begin(),
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index dffbfde..498eebc 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -158,11 +158,15 @@
 
     void checkExit();
 
+    void handleViewport(nsecs_t timestep);
+
     sp<SurfaceComposerClient>       mSession;
     AssetManager mAssets;
     Texture     mAndroid[2];
     int         mWidth;
     int         mHeight;
+    int         mCurrentInset;
+    int         mTargetInset;
     bool        mUseNpotTextures = false;
     EGLDisplay  mDisplay;
     EGLDisplay  mContext;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a010c72..9120701 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -149,6 +149,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.org.conscrypt.OpenSSLSocketImpl;
 import com.android.org.conscrypt.TrustedCertificateStore;
@@ -5324,6 +5325,16 @@
         }
     }
 
+    /**
+     * Updates the application info.
+     *
+     * This only works in the system process. Must be called on the main thread.
+     */
+    public void handleSystemApplicationInfoChanged(@NonNull ApplicationInfo ai) {
+        Preconditions.checkState(mSystemThread, "Must only be called in the system process");
+        handleApplicationInfoChanged(ai);
+    }
+
     void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
         // Updates triggered by package installation go through a package update
         // receiver. Here we try to capture ApplicationInfo changes that are
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 9e23de1..e5f3dfb 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -52,7 +52,7 @@
     /**
      * Notification has not been dismissed yet.
      */
-    public static final int DISMISSAL_NOT_DISMISSED = -1000;
+    public static final int DISMISSAL_NOT_DISMISSED = -1;
     /**
      * Notification has been dismissed from a {@link NotificationListenerService} or the app
      * itself.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 23fc4d5..1351f6f1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
@@ -1212,13 +1213,30 @@
 
         // Get new instance of display based on current display adjustments. It may be updated later
         // if moving between the displays also involved a configuration change.
-        mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
-            mView.getResources());
+        updateInternalDisplay(displayId, mView.getResources());
         mAttachInfo.mDisplayState = mDisplay.getState();
         // Internal state updated, now notify the view hierarchy.
         mView.dispatchMovedToDisplay(mDisplay, config);
     }
 
+    /**
+     * Updates {@link #mDisplay} to the display object corresponding to {@param displayId}.
+     * Uses DEFAULT_DISPLAY if there isn't a display object in the system corresponding
+     * to {@param displayId}.
+     */
+    private void updateInternalDisplay(int displayId, Resources resources) {
+        final Display preferredDisplay =
+                ResourcesManager.getInstance().getAdjustedDisplay(displayId, resources);
+        if (preferredDisplay == null) {
+            // Fallback to use default display.
+            Slog.w(TAG, "Cannot get desired display with Id: " + displayId);
+            mDisplay = ResourcesManager.getInstance()
+                    .getAdjustedDisplay(DEFAULT_DISPLAY, resources);
+        } else {
+            mDisplay = preferredDisplay;
+        }
+    }
+
     void pokeDrawLockIfNeeded() {
         final int displayState = mAttachInfo.mDisplayState;
         if (mView != null && mAdded && mTraversalScheduled
@@ -3944,8 +3962,7 @@
         // Handle configuration change.
         if (mForceNextConfigUpdate || mLastConfigurationFromResources.diff(config) != 0) {
             // Update the display with new DisplayAdjustments.
-            mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(
-                    mDisplay.getDisplayId(), localResources);
+            updateInternalDisplay(mDisplay.getDisplayId(), localResources);
 
             final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
             final int currentLayoutDirection = config.getLayoutDirection();
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 11054c8..5734171 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -76,10 +76,10 @@
     private final int mWindowHeight;
     // The zoom applied to the view region copied to the magnifier window.
     private final float mZoom;
-    // The width of the bitmaps where the magnifier content is copied.
-    private final int mBitmapWidth;
-    // The height of the bitmaps where the magnifier content is copied.
-    private final int mBitmapHeight;
+    // The width of the content that will be copied to the magnifier.
+    private final int mSourceWidth;
+    // The height of the content that will be copied to the magnifier.
+    private final int mSourceHeight;
     // The elevation of the window containing the magnifier.
     private final float mWindowElevation;
     // The corner radius of the window containing the magnifier.
@@ -91,15 +91,14 @@
     // The center coordinates of the window containing the magnifier.
     private final Point mWindowCoords = new Point();
     // The center coordinates of the content to be magnified,
-    // which can potentially contain a region outside the magnified view.
-    private final Point mCenterZoomCoords = new Point();
-    // The center coordinates of the content to be magnified,
     // clamped inside the visible region of the magnified view.
     private final Point mClampedCenterZoomCoords = new Point();
     // Variables holding previous states, used for detecting redundant calls and invalidation.
     private final Point mPrevStartCoordsInSurface = new Point(
             NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
-    private final PointF mPrevPosInView = new PointF(
+    private final PointF mPrevShowSourceCoords = new PointF(
+            NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
+    private final PointF mPrevShowWindowCoords = new PointF(
             NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
     // Rectangle defining the view surface area we pixel copy content from.
     private final Rect mPixelCopyRequestRect = new Rect();
@@ -120,8 +119,8 @@
         mWindowElevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
         mWindowCornerRadius = getDeviceDefaultDialogCornerRadius();
         mZoom = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
-        mBitmapWidth = Math.round(mWindowWidth / mZoom);
-        mBitmapHeight = Math.round(mWindowHeight / mZoom);
+        mSourceWidth = Math.round(mWindowWidth / mZoom);
+        mSourceHeight = Math.round(mWindowHeight / mZoom);
         // The view's surface coordinates will not be updated until the magnifier is first shown.
         mViewCoordinatesInSurface = new int[2];
     }
@@ -148,25 +147,50 @@
     /**
      * Shows the magnifier on the screen.
      *
-     * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
-     *        to the view. The lower end is clamped to 0 and the higher end is clamped to the view
-     *        width.
-     * @param yPosInView vertical coordinate of the center point of the magnifier source
-     *        relative to the view. The lower end is clamped to 0 and the higher end is clamped to
-     *        the view height.
+     * @param sourceCenterX horizontal coordinate of the center point of the source rectangle that
+     *        will be magnified and copied to the magnifier, relative to the view.
+     *        The parameter is clamped such that the copy rectangle fits inside [0, view width].
+     * @param sourceCenterY vertical coordinate of the center point of the source rectangle that
+     *        will be magnified and copied to the magnifier, relative to the view.
+     *        The parameter is clamped such that the copy rectangle fits inside [0, view height].
      */
-    public void show(@FloatRange(from = 0) float xPosInView,
-            @FloatRange(from = 0) float yPosInView) {
-        xPosInView = Math.max(0, Math.min(xPosInView, mView.getWidth()));
-        yPosInView = Math.max(0, Math.min(yPosInView, mView.getHeight()));
+    public void show(@FloatRange(from = 0) float sourceCenterX,
+            @FloatRange(from = 0) float sourceCenterY) {
+        final int verticalOffset = mView.getContext().getResources()
+                .getDimensionPixelSize(R.dimen.magnifier_offset);
+        show(sourceCenterX, sourceCenterY, sourceCenterX, sourceCenterY - verticalOffset);
+    }
+
+    /**
+     * Shows the magnifier on the screen at a position
+     * that is independent from its content position.
+     *
+     * @param sourceCenterX horizontal coordinate of the center point of the source rectangle that
+     *        will be magnified and copied to the magnifier, relative to the view.
+     *        The parameter is clamped such that the copy rectangle fits inside [0, view width].
+     * @param sourceCenterY vertical coordinate of the center point of the source rectangle that
+     *        will be magnified and copied to the magnifier, relative to the view.
+     *        The parameter is clamped such that the copy rectangle fits inside [0, view height].
+     * @param magnifierCenterX horizontal coordinate of the center point of the magnifier window
+     *        relative to the view. As the magnifier can be arbitrarily positioned, this can be
+     *        negative or larger than the view width.
+     * @param magnifierCenterY vertical coordinate of the center point of the magnifier window
+     *        relative to the view. As the magnifier can be arbitrarily positioned, this can be
+     *        negative or larger than the view height.
+     */
+    public void show(@FloatRange(from = 0) float sourceCenterX,
+            @FloatRange(from = 0) float sourceCenterY,
+            float magnifierCenterX, float magnifierCenterY) {
+        sourceCenterX = Math.max(0, Math.min(sourceCenterX, mView.getWidth()));
+        sourceCenterY = Math.max(0, Math.min(sourceCenterY, mView.getHeight()));
 
         obtainSurfaces();
-        obtainContentCoordinates(xPosInView, yPosInView);
-        obtainWindowCoordinates();
+        obtainContentCoordinates(sourceCenterX, sourceCenterY);
+        obtainWindowCoordinates(magnifierCenterX, magnifierCenterY);
 
-        final int startX = mClampedCenterZoomCoords.x - mBitmapWidth / 2;
-        final int startY = mClampedCenterZoomCoords.y - mBitmapHeight / 2;
-        if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
+        final int startX = mClampedCenterZoomCoords.x - mSourceWidth / 2;
+        final int startY = mClampedCenterZoomCoords.y - mSourceHeight / 2;
+        if (sourceCenterX != mPrevShowSourceCoords.x || sourceCenterY != mPrevShowSourceCoords.y) {
             if (mWindow == null) {
                 synchronized (mLock) {
                     mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
@@ -177,9 +201,24 @@
                 }
             }
             performPixelCopy(startX, startY, true /* update window position */);
-            mPrevPosInView.x = xPosInView;
-            mPrevPosInView.y = yPosInView;
+        } else if (magnifierCenterX != mPrevShowWindowCoords.x
+                || magnifierCenterY != mPrevShowWindowCoords.y) {
+            final Point windowCoords = getCurrentClampedWindowCoordinates();
+            final InternalPopupWindow currentWindowInstance = mWindow;
+            sPixelCopyHandlerThread.getThreadHandler().post(() -> {
+                if (mWindow != currentWindowInstance) {
+                    // The magnifier was dismissed (and maybe shown again) in the meantime.
+                    return;
+                }
+                synchronized (mLock) {
+                    mWindow.setContentPositionForNextDraw(windowCoords.x, windowCoords.y);
+                }
+            });
         }
+        mPrevShowSourceCoords.x = sourceCenterX;
+        mPrevShowSourceCoords.y = sourceCenterY;
+        mPrevShowWindowCoords.x = magnifierCenterX;
+        mPrevShowWindowCoords.y = magnifierCenterY;
     }
 
     /**
@@ -191,8 +230,10 @@
                 mWindow.destroy();
                 mWindow = null;
             }
-            mPrevPosInView.x = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
-            mPrevPosInView.y = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
+            mPrevShowSourceCoords.x = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
+            mPrevShowSourceCoords.y = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
+            mPrevShowWindowCoords.x = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
+            mPrevShowWindowCoords.y = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
             mPrevStartCoordsInSurface.x = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
             mPrevStartCoordsInSurface.y = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
         }
@@ -296,19 +337,17 @@
      * magnifier. These are relative to the surface the content is copied from.
      */
     private void obtainContentCoordinates(final float xPosInView, final float yPosInView) {
-        final float posX;
-        final float posY;
         mView.getLocationInSurface(mViewCoordinatesInSurface);
+        final int zoomCenterX;
+        final int zoomCenterY;
         if (mView instanceof SurfaceView) {
             // No offset required if the backing Surface matches the size of the SurfaceView.
-            posX = xPosInView;
-            posY = yPosInView;
+            zoomCenterX = Math.round(xPosInView);
+            zoomCenterY = Math.round(yPosInView);
         } else {
-            posX = xPosInView + mViewCoordinatesInSurface[0];
-            posY = yPosInView + mViewCoordinatesInSurface[1];
+            zoomCenterX = Math.round(xPosInView + mViewCoordinatesInSurface[0]);
+            zoomCenterY = Math.round(yPosInView + mViewCoordinatesInSurface[1]);
         }
-        mCenterZoomCoords.x = Math.round(posX);
-        mCenterZoomCoords.y = Math.round(posY);
 
         // Clamp the x location to avoid magnifying content which does not belong
         // to the magnified view. This will not take into account overlapping views.
@@ -323,18 +362,29 @@
             // If we copy content from a SurfaceView, clamp coordinates relative to it.
             viewVisibleRegion.offset(-mViewCoordinatesInSurface[0], -mViewCoordinatesInSurface[1]);
         }
-        mClampedCenterZoomCoords.x = Math.max(viewVisibleRegion.left + mBitmapWidth / 2, Math.min(
-                mCenterZoomCoords.x, viewVisibleRegion.right - mBitmapWidth / 2));
-        mClampedCenterZoomCoords.y = mCenterZoomCoords.y;
+        mClampedCenterZoomCoords.x = Math.max(viewVisibleRegion.left + mSourceWidth / 2, Math.min(
+                zoomCenterX, viewVisibleRegion.right - mSourceWidth / 2));
+        mClampedCenterZoomCoords.y = zoomCenterY;
     }
 
-    private void obtainWindowCoordinates() {
-        // Compute the position of the magnifier window. Again, this has to be relative to the
-        // surface of the magnified view, as this surface is the parent of the magnifier surface.
-        final int verticalOffset = mView.getContext().getResources().getDimensionPixelSize(
-                R.dimen.magnifier_offset);
-        mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
-        mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalOffset;
+    /**
+     * Computes the coordinates of the top left corner of the magnifier window.
+     * These are relative to the surface the magnifier window is attached to.
+     */
+    private void obtainWindowCoordinates(final float xWindowPos, final float yWindowPos) {
+        final int windowCenterX;
+        final int windowCenterY;
+        if (mView instanceof SurfaceView) {
+            // No offset required if the backing Surface matches the size of the SurfaceView.
+            windowCenterX = Math.round(xWindowPos);
+            windowCenterY = Math.round(yWindowPos);
+        } else {
+            windowCenterX = Math.round(xWindowPos + mViewCoordinatesInSurface[0]);
+            windowCenterY = Math.round(yWindowPos + mViewCoordinatesInSurface[1]);
+        }
+
+        mWindowCoords.x = windowCenterX - mWindowWidth / 2;
+        mWindowCoords.y = windowCenterY - mWindowHeight / 2;
         if (mParentSurface != mContentCopySurface) {
             mWindowCoords.x += mViewCoordinatesInSurface[0];
             mWindowCoords.y += mViewCoordinatesInSurface[1];
@@ -348,34 +398,21 @@
         }
         // Clamp copy coordinates inside the surface to avoid displaying distorted content.
         final int clampedStartXInSurface = Math.max(0,
-                Math.min(startXInSurface, mContentCopySurface.mWidth - mBitmapWidth));
+                Math.min(startXInSurface, mContentCopySurface.mWidth - mSourceWidth));
         final int clampedStartYInSurface = Math.max(0,
-                Math.min(startYInSurface, mContentCopySurface.mHeight - mBitmapHeight));
-
+                Math.min(startYInSurface, mContentCopySurface.mHeight - mSourceHeight));
         // Clamp window coordinates inside the parent surface, to avoid displaying
         // the magnifier out of screen or overlapping with system insets.
-        final Rect windowBounds;
-        if (mParentSurface.mIsMainWindowSurface) {
-            final Rect systemInsets = mView.getRootWindowInsets().getSystemWindowInsets();
-            windowBounds = new Rect(systemInsets.left, systemInsets.top,
-                     mParentSurface.mWidth - systemInsets.right,
-                    mParentSurface.mHeight - systemInsets.bottom);
-        } else {
-            windowBounds = new Rect(0, 0, mParentSurface.mWidth, mParentSurface.mHeight);
-        }
-        final int windowCoordsX = Math.max(windowBounds.left,
-                Math.min(windowBounds.right - mWindowWidth, mWindowCoords.x));
-        final int windowCoordsY = Math.max(windowBounds.top,
-                Math.min(windowBounds.bottom - mWindowHeight, mWindowCoords.y));
+        final Point windowCoords = getCurrentClampedWindowCoordinates();
 
         // Perform the pixel copy.
         mPixelCopyRequestRect.set(clampedStartXInSurface,
                 clampedStartYInSurface,
-                clampedStartXInSurface + mBitmapWidth,
-                clampedStartYInSurface + mBitmapHeight);
+                clampedStartXInSurface + mSourceWidth,
+                clampedStartYInSurface + mSourceHeight);
         final InternalPopupWindow currentWindowInstance = mWindow;
         final Bitmap bitmap =
-                Bitmap.createBitmap(mBitmapWidth, mBitmapHeight, Bitmap.Config.ARGB_8888);
+                Bitmap.createBitmap(mSourceWidth, mSourceHeight, Bitmap.Config.ARGB_8888);
         PixelCopy.request(mContentCopySurface.mSurface, mPixelCopyRequestRect, bitmap,
                 result -> {
                     synchronized (mLock) {
@@ -385,7 +422,7 @@
                         }
                         if (updateWindowPosition) {
                             // TODO: pull the position update outside #performPixelCopy
-                            mWindow.setContentPositionForNextDraw(windowCoordsX, windowCoordsY);
+                            mWindow.setContentPositionForNextDraw(windowCoords.x, windowCoords.y);
                         }
                         mWindow.updateContent(bitmap);
                     }
@@ -396,6 +433,28 @@
     }
 
     /**
+     * Clamp window coordinates inside the surface the magnifier is attached to, to avoid
+     * displaying the magnifier out of screen or overlapping with system insets.
+     * @return the current window coordinates, after they are clamped inside the parent surface
+     */
+    private Point getCurrentClampedWindowCoordinates() {
+        final Rect windowBounds;
+        if (mParentSurface.mIsMainWindowSurface) {
+            final Rect systemInsets = mView.getRootWindowInsets().getSystemWindowInsets();
+            windowBounds = new Rect(systemInsets.left, systemInsets.top,
+                    mParentSurface.mWidth - systemInsets.right,
+                    mParentSurface.mHeight - systemInsets.bottom);
+        } else {
+            windowBounds = new Rect(0, 0, mParentSurface.mWidth, mParentSurface.mHeight);
+        }
+        final int windowCoordsX = Math.max(windowBounds.left,
+                Math.min(windowBounds.right - mWindowWidth, mWindowCoords.x));
+        final int windowCoordsY = Math.max(windowBounds.top,
+                Math.min(windowBounds.bottom - mWindowHeight, mWindowCoords.y));
+        return new Point(windowCoordsX, windowCoordsY);
+    }
+
+    /**
      * Contains a surface and metadata corresponding to it.
      */
     private static class SurfaceInfo {
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index e183a6b..aa78b0d 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -712,6 +712,12 @@
     public abstract void setNextDataSources(@NonNull List<DataSourceDesc> dsds);
 
     /**
+     * Removes all data sources pending to be played.
+     */
+    // This is an asynchronous call.
+    public abstract void clearNextDataSources();
+
+    /**
      * Gets the current data source as described by a DataSourceDesc.
      *
      * @return the current DataSourceDesc
@@ -2066,6 +2072,11 @@
      */
     public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29;
 
+    /** The player just completed a call {@link #clearNextDataSources}.
+     * @see EventCallback#onCallCompleted
+     */
+    public static final int CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES = 30;
+
     /** The player just completed a call {@link #setBufferingParams}.
      * @see EventCallback#onCallCompleted
      * @hide
@@ -2109,6 +2120,7 @@
             CALL_COMPLETED_SET_SURFACE,
             CALL_COMPLETED_SET_SYNC_PARAMS,
             CALL_COMPLETED_SKIP_TO_NEXT,
+            CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES,
             CALL_COMPLETED_SET_BUFFERING_PARAMS,
             CALL_COMPLETED_SET_VIDEO_SCALING_MODE,
             CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index 95ba00f..c157251 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -451,6 +451,23 @@
     }
 
     @Override
+    public void clearNextDataSources() {
+        addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
+            @Override
+            void process() {
+                synchronized (mSrcLock) {
+                    if (mNextDSDs != null) {
+                        mNextDSDs.clear();
+                        mNextDSDs = null;
+                    }
+                    mNextSrcId = mSrcIdGenerator++;
+                    mNextSourceState = NEXT_SOURCE_STATE_INIT;
+                }
+            }
+        });
+    }
+
+    @Override
     public @NonNull DataSourceDesc getCurrentDataSource() {
         synchronized (mSrcLock) {
             return mCurrentDSD;
@@ -1646,6 +1663,14 @@
         synchronized (mDrmEventCbLock) {
             mDrmEventCallbackRecords.clear();
         }
+        synchronized (mSrcLock) {
+            if (mNextDSDs != null) {
+                mNextDSDs.clear();
+                mNextDSDs = null;
+            }
+            mNextSrcId = mSrcIdGenerator++;
+            mNextSourceState = NEXT_SOURCE_STATE_INIT;
+        }
 
         stayAwake(false);
         _reset();
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 9c58135..e67b100 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -266,6 +266,12 @@
                 if (MtpDatabase.this.mServer != null)
                     MtpDatabase.this.mServer.sendObjectRemoved(id);
             }
+
+            @Override
+            public void sendObjectInfoChanged(int id) {
+                if (MtpDatabase.this.mServer != null)
+                    MtpDatabase.this.mServer.sendObjectInfoChanged(id);
+            }
         }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
 
         initDeviceProperties(context);
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index 8af5ff7..a555d37 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -77,6 +77,10 @@
         native_send_object_removed(handle);
     }
 
+    public void sendObjectInfoChanged(int handle) {
+        native_send_object_info_changed(handle);
+    }
+
     public void sendDevicePropertyChanged(int property) {
         native_send_device_property_changed(property);
     }
@@ -106,6 +110,7 @@
     private native final void native_cleanup();
     private native final void native_send_object_added(int handle);
     private native final void native_send_object_removed(int handle);
+    private native final void native_send_object_info_changed(int handle);
     private native final void native_send_device_property_changed(int property);
     private native final void native_add_storage(MtpStorage storage);
     private native final void native_remove_storage(int storageId);
diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java
index 756e942..f14e7d7 100644
--- a/media/java/android/mtp/MtpStorageManager.java
+++ b/media/java/android/mtp/MtpStorageManager.java
@@ -55,7 +55,8 @@
 
         MtpObjectObserver(MtpObject object) {
             super(object.getPath().toString(),
-                    MOVED_FROM | MOVED_TO | DELETE | CREATE | IN_ONLYDIR);
+                    MOVED_FROM | MOVED_TO | DELETE | CREATE | IN_ONLYDIR
+                  | CLOSE_WRITE);
             mObject = object;
         }
 
@@ -85,6 +86,10 @@
                     if (mObject.mObserver != null)
                         mObject.mObserver.stopWatching();
                     mObject.mObserver = null;
+                } else if ((event & CLOSE_WRITE) != 0) {
+                    if (sDebug)
+                        Log.i(TAG, "inotify for " + mObject.getPath() + " CLOSE_WRITE: " + path);
+                    handleChangedObject(mObject, path);
                 } else {
                     Log.w(TAG, "Got unrecognized event " + path + " " + event);
                 }
@@ -302,6 +307,11 @@
          * Called when an object is deleted.
          */
         public abstract void sendObjectRemoved(int id);
+
+        /**
+         * Called when an object info is changed.
+         */
+        public abstract void sendObjectInfoChanged(int id);
     }
 
     private MtpNotifier mMtpNotifier;
@@ -733,6 +743,25 @@
             Log.i(TAG, state + " transitioned to " + obj.getState() + " in op " + op);
     }
 
+    private synchronized void handleChangedObject(MtpObject parent, String path) {
+        MtpOperation op = MtpOperation.NONE;
+        MtpObject obj = parent.getChild(path);
+        if (obj != null) {
+            // Only handle files for size change notification event
+            if ((!obj.isDir()) && (obj.getSize() > 0))
+            {
+                MtpObjectState state = obj.getState();
+                op = obj.getOperation();
+                MtpStorageManager.this.mMtpNotifier.sendObjectInfoChanged(obj.getId());
+                if (sDebug)
+                    Log.d(TAG, "sendObjectInfoChanged: id=" + obj.getId() + ",size=" + obj.getSize());
+            }
+        } else {
+            if (sDebug)
+                Log.w(TAG, "object " + path + " null");
+        }
+    }
+
     /**
      * Block the caller until all events currently in the event queue have been
      * read and processed. Used for testing purposes.
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index c60590a..39ff04a 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -135,6 +135,18 @@
 }
 
 static void
+android_mtp_MtpServer_send_object_info_changed(JNIEnv *env, jobject thiz, jint handle)
+{
+    Mutex::Autolock autoLock(sMutex);
+
+    MtpServer* server = getMtpServer(env, thiz);
+    if (server)
+        server->sendObjectInfoChanged(handle);
+    else
+        ALOGE("server is null in send_object_info_changed");
+}
+
+static void
 android_mtp_MtpServer_send_device_property_changed(JNIEnv *env, jobject thiz, jint property)
 {
     Mutex::Autolock autoLock(sMutex);
@@ -202,6 +214,7 @@
     {"native_cleanup",              "()V",  (void *)android_mtp_MtpServer_cleanup},
     {"native_send_object_added",    "(I)V", (void *)android_mtp_MtpServer_send_object_added},
     {"native_send_object_removed",  "(I)V", (void *)android_mtp_MtpServer_send_object_removed},
+    {"native_send_object_info_changed",  "(I)V", (void *)android_mtp_MtpServer_send_object_info_changed},
     {"native_send_device_property_changed",  "(I)V",
                                     (void *)android_mtp_MtpServer_send_device_property_changed},
     {"native_add_storage",          "(Landroid/mtp/MtpStorage;)V",
diff --git a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
index d7833cc..566d1c6 100644
--- a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
+++ b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
@@ -58,6 +58,7 @@
 
     private ArrayList<Integer> objectsAdded;
     private ArrayList<Integer> objectsRemoved;
+    private ArrayList<Integer> objectsInfoChanged;
 
     private File mainStorageDir;
     private File secondaryStorageDir;
@@ -73,15 +74,42 @@
         Log.d(TAG, Thread.currentThread().getStackTrace()[3].getMethodName());
     }
 
+    private static void logMethodValue(String szVar, int iValue)
+    {
+        Log.d(TAG, szVar + "=" + iValue + ": " + Thread.currentThread().getStackTrace()[3].getMethodName());
+    }
+
+    private static void vWriteNewFile(File newFile) {
+        try {
+            new FileOutputStream(newFile).write(new byte[] {0, 0, 0});
+        } catch (IOException e) {
+            Assert.fail();
+        }
+    }
+
     private static File createNewFile(File parent) {
-        return createNewFile(parent, UUID.randomUUID().toString());
+        return createNewFile(parent, "file-" + UUID.randomUUID().toString());
+    }
+
+    private static File createNewFileNonZero(File parent) {
+        return createNewFileNonZero(parent, "file-" + UUID.randomUUID().toString());
     }
 
     private static File createNewFile(File parent, String name) {
+        return createNewFile(parent, name, false);
+    }
+
+    private static File createNewFileNonZero(File parent, String name) {
+        return createNewFile(parent, name, true);
+    }
+
+    private static File createNewFile(File parent, String name, boolean fgNonZero) {
         try {
             File ret = new File(parent, name);
             if (!ret.createNewFile())
                 throw new AssertionError("Failed to create file");
+            if (fgNonZero)
+                vWriteNewFile(ret);     // create non-zero size file
             return ret;
         } catch (IOException e) {
             throw new AssertionError(e.getMessage());
@@ -96,11 +124,12 @@
     }
 
     private static File createNewDir(File parent) {
-        return createNewDir(parent, UUID.randomUUID().toString());
+        return createNewDir(parent, "dir-" + UUID.randomUUID().toString());
     }
 
     @Before
     public void before() {
+        FileUtils.deleteContentsAndDir(TEMP_DIR_FILE);
         Assert.assertTrue(TEMP_DIR_FILE.mkdir());
         mainStorageDir = createNewDir(TEMP_DIR_FILE);
         secondaryStorageDir = createNewDir(TEMP_DIR_FILE);
@@ -112,17 +141,26 @@
 
         objectsAdded = new ArrayList<>();
         objectsRemoved = new ArrayList<>();
+        objectsInfoChanged = new ArrayList<>();
 
         manager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
             @Override
             public void sendObjectAdded(int id) {
+                Log.d(TAG, "sendObjectAdded " + id);
                 objectsAdded.add(id);
             }
 
             @Override
             public void sendObjectRemoved(int id) {
+                Log.d(TAG, "sendObjectRemoved " + id);
                 objectsRemoved.add(id);
             }
+
+            @Override
+            public void sendObjectInfoChanged(int id) {
+                Log.d(TAG, "sendObjectInfoChanged: " + id);
+                objectsInfoChanged.add(id);
+            }
         }, null);
 
         mainMtpStorage = manager.addMtpStorage(mainStorage);
@@ -469,11 +507,16 @@
                 mainMtpStorage.getStorageId());
         Assert.assertEquals(stream.size(), 0);
 
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
                 newFile.getPath());
+
+        logMethodValue("objectsInfoChanged.size", objectsInfoChanged.size());
+        if (objectsInfoChanged.size() > 0)
+            Assert.assertEquals(objectsAdded.get(0).intValue(), objectsInfoChanged.get(0).intValue());
+
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -491,6 +534,7 @@
         Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
                 newDir.getPath());
         Assert.assertTrue(manager.getObject(objectsAdded.get(0)).isDir());
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -508,6 +552,7 @@
         Assert.assertEquals(manager.getObject(objectsAdded.get(2)).getPath().toString(),
                 newDir.getPath());
         Assert.assertTrue(manager.getObject(objectsAdded.get(2)).isDir());
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -531,7 +576,7 @@
     @SmallTest
     public void testObjectMoved() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         List<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId());
         Assert.assertEquals(stream.size(), 1);
@@ -544,6 +589,7 @@
         Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
                 toFile.getPath());
         Assert.assertNull(manager.getObject(objectsRemoved.get(0)));
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -561,7 +607,7 @@
                 "newFile", MtpConstants.FORMAT_UNDEFINED);
         Assert.assertEquals(id, 1);
 
-        File newFile = createNewFile(mainStorageDir, "newFile");
+        File newFile = createNewFileNonZero(mainStorageDir, "newFile");
         manager.flushEvents();
         MtpStorageManager.MtpObject obj = manager.getObject(id);
         Assert.assertTrue(manager.endSendObject(obj, true));
@@ -586,11 +632,12 @@
         Assert.assertTrue(manager.endSendObject(obj, true));
         Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(obj.getFormat(), MtpConstants.FORMAT_ASSOCIATION);
         Assert.assertTrue(manager.checkConsistency());
 
         // Check that new dir receives events
-        File newerFile = createNewFile(newFile);
+        File newerFile = createNewFileNonZero(newFile);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
@@ -609,7 +656,7 @@
         MtpStorageManager.MtpObject obj = manager.getObject(id);
         Assert.assertTrue(manager.endSendObject(obj, true));
 
-        File newFile = createNewFile(mainStorageDir, "newFile");
+        File newFile = createNewFileNonZero(mainStorageDir, "newFile");
         manager.flushEvents();
         Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
         Assert.assertEquals(objectsAdded.size(), 0);
@@ -632,11 +679,12 @@
         manager.flushEvents();
         Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(obj.getFormat(), MtpConstants.FORMAT_ASSOCIATION);
         Assert.assertTrue(manager.checkConsistency());
 
         // Check that new dir receives events
-        File newerFile = createNewFile(newFile);
+        File newerFile = createNewFileNonZero(newFile);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
@@ -689,12 +737,13 @@
         Assert.assertEquals(id, 1);
         MtpStorageManager.MtpObject obj = manager.getObject(id);
 
-        File newFile = createNewFile(mainStorageDir, "newFile");
+        File newFile = createNewFileNonZero(mainStorageDir, "newFile");
         Assert.assertTrue(newFile.delete());
         manager.flushEvents();
         Assert.assertTrue(manager.endSendObject(obj, false));
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -713,15 +762,21 @@
         manager.flushEvents();
         Assert.assertTrue(manager.endSendObject(obj, false));
         Assert.assertNotEquals(objectsAdded.get(0).intValue(), id);
+        logMethodValue("objectsInfoChanged.size", objectsInfoChanged.size());
+        if (objectsInfoChanged.size() > 0)
+            Assert.assertNotEquals(objectsInfoChanged.get(0).intValue(), id);
         Assert.assertNull(manager.getObject(id));
         Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
                 newDir.getPath());
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events in new dir
-        createNewFile(newDir);
+        createNewFileNonZero(newDir);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 2);
+        logMethodValue("objectsInfoChanged.size", objectsInfoChanged.size());
+        if (objectsInfoChanged.size() == 1)
+            Assert.assertEquals(objectsAdded.get(1).intValue(), objectsInfoChanged.get(0).intValue());
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -764,13 +819,13 @@
     public void testRemoveObjectDir() {
         logMethodName();
         File newDir = createNewDir(mainStorageDir);
-        createNewFile(createNewDir(newDir));
+        createNewFileNonZero(createNewDir(newDir));
         MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
         Assert.assertTrue(manager.beginRemoveObject(obj));
 
-        createNewFile(newDir);
+        createNewFileNonZero(newDir);
         Assert.assertTrue(FileUtils.deleteContentsAndDir(newDir));
         manager.flushEvents();
         Assert.assertTrue(manager.endRemoveObject(obj, true));
@@ -811,13 +866,16 @@
         Assert.assertTrue(manager.beginRemoveObject(obj));
 
         Assert.assertTrue(newFile.delete());
-        createNewFile(mainStorageDir, newFile.getName());
+        createNewFileNonZero(mainStorageDir, newFile.getName());
         manager.flushEvents();
         Assert.assertTrue(manager.endRemoveObject(obj, true));
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertNull(manager.getObject(id));
         Assert.assertNotEquals(objectsAdded.get(0).intValue(), id);
+        logMethodValue("objectsInfoChanged.size", objectsInfoChanged.size());
+        if (objectsInfoChanged.size() > 0)
+            Assert.assertNotEquals(objectsInfoChanged.get(0).intValue(), objectsAdded.get(0).intValue());
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -850,6 +908,7 @@
         Assert.assertTrue(manager.endRemoveObject(obj, false));
         Assert.assertEquals(manager.getObject(obj.getId()), obj);
         Assert.assertEquals(objectsAdded.size(), 1);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -875,7 +934,7 @@
     @SmallTest
     public void testCopyObjectSuccess() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File newDir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId())
@@ -886,7 +945,7 @@
 
         int id = manager.beginCopyObject(fileObj, dirObj);
         Assert.assertNotEquals(id, -1);
-        createNewFile(newDir, newFile.getName());
+        createNewFileNonZero(newDir, newFile.getName());
         manager.flushEvents();
         MtpStorageManager.MtpObject obj = manager.getObject(id);
         Assert.assertTrue(manager.endCopyObject(obj, true));
@@ -900,9 +959,9 @@
         logMethodName();
         File newDirFrom = createNewDir(mainStorageDir);
         File newDirFrom1 = createNewDir(newDirFrom);
-        File newDirFrom2 = createNewFile(newDirFrom1);
-        File delayedFile = createNewFile(newDirFrom);
-        File deletedFile = createNewFile(newDirFrom);
+        File newDirFrom2 = createNewFileNonZero(newDirFrom1);
+        File delayedFile = createNewFileNonZero(newDirFrom);
+        File deletedFile = createNewFileNonZero(newDirFrom);
         File newDirTo = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject toObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -916,9 +975,9 @@
         Assert.assertNotEquals(id, -1);
         File copiedDir = createNewDir(newDirTo, newDirFrom.getName());
         File copiedDir1 = createNewDir(copiedDir, newDirFrom1.getName());
-        createNewFile(copiedDir1, newDirFrom2.getName());
-        createNewFile(copiedDir, "extraFile");
-        File toDelete = createNewFile(copiedDir, deletedFile.getName());
+        createNewFileNonZero(copiedDir1, newDirFrom2.getName());
+        createNewFileNonZero(copiedDir, "extraFile");
+        File toDelete = createNewFileNonZero(copiedDir, deletedFile.getName());
         manager.flushEvents();
         Assert.assertTrue(toDelete.delete());
         manager.flushEvents();
@@ -927,13 +986,13 @@
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertEquals(objectsRemoved.size(), 1);
 
-        createNewFile(copiedDir, delayedFile.getName());
+        createNewFileNonZero(copiedDir, delayedFile.getName());
         manager.flushEvents();
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events in the visited dir, but not the unvisited dir.
-        createNewFile(copiedDir);
-        createNewFile(copiedDir1);
+        createNewFileNonZero(copiedDir);
+        createNewFileNonZero(copiedDir1);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 2);
         Assert.assertEquals(objectsAdded.size(), 2);
@@ -947,7 +1006,7 @@
     @SmallTest
     public void testCopyObjectFailed() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File newDir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -962,6 +1021,7 @@
         MtpStorageManager.MtpObject obj = manager.getObject(id);
         Assert.assertTrue(manager.endCopyObject(obj, false));
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -969,7 +1029,7 @@
     @SmallTest
     public void testCopyObjectFailedAdded() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File newDir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -989,7 +1049,7 @@
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events in new dir
-        createNewFile(addedDir);
+        createNewFileNonZero(addedDir);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 2);
         Assert.assertTrue(manager.checkConsistency());
@@ -999,7 +1059,7 @@
     @SmallTest
     public void testCopyObjectFailedDeleted() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File newDir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -1010,7 +1070,7 @@
 
         int id = manager.beginCopyObject(fileObj, dirObj);
         Assert.assertNotEquals(id, -1);
-        Assert.assertTrue(createNewFile(newDir, newFile.getName()).delete());
+        Assert.assertTrue(createNewFileNonZero(newDir, newFile.getName()).delete());
         manager.flushEvents();
         MtpStorageManager.MtpObject obj = manager.getObject(id);
         Assert.assertTrue(manager.endCopyObject(obj, false));
@@ -1022,7 +1082,7 @@
     @SmallTest
     public void testRenameObjectSuccess() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
@@ -1033,6 +1093,7 @@
         Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), true));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
 
@@ -1054,6 +1115,7 @@
         Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
 
@@ -1063,6 +1125,7 @@
         createNewFile(renamed);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -1082,13 +1145,14 @@
         Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
 
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events since the dir was visited
-        createNewFile(renamed);
+        createNewFileNonZero(renamed);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertTrue(manager.checkConsistency());
@@ -1098,7 +1162,7 @@
     @SmallTest
     public void testRenameObjectDelayed() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
@@ -1109,6 +1173,7 @@
         manager.flushEvents();
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
 
@@ -1131,13 +1196,14 @@
         manager.flushEvents();
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
 
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events since the dir was visited
-        createNewFile(renamed);
+        createNewFileNonZero(renamed);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertTrue(manager.checkConsistency());
@@ -1147,7 +1213,7 @@
     @SmallTest
     public void testRenameObjectFailed() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
@@ -1155,6 +1221,7 @@
         Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
 
         Assert.assertTrue(manager.checkConsistency());
@@ -1164,7 +1231,7 @@
     @SmallTest
     public void testRenameObjectFailedOldRemoved() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
@@ -1174,6 +1241,7 @@
         Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 1);
 
         Assert.assertTrue(manager.checkConsistency());
@@ -1183,16 +1251,19 @@
     @SmallTest
     public void testRenameObjectFailedNewAdded() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
 
-        createNewFile(mainStorageDir, "renamed");
+        createNewFileNonZero(mainStorageDir, "renamed");
         manager.flushEvents();
         Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
 
         Assert.assertEquals(objectsAdded.size(), 1);
+        logMethodValue("objectsInfoChanged.size", objectsInfoChanged.size());
+        if (objectsInfoChanged.size() > 0)
+            Assert.assertNotEquals(objectsAdded.get(0).intValue(), objectsInfoChanged.get(0).intValue());
         Assert.assertEquals(objectsRemoved.size(), 0);
 
         Assert.assertTrue(manager.checkConsistency());
@@ -1202,7 +1273,7 @@
     @SmallTest
     public void testMoveObjectSuccess() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File dir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -1220,6 +1291,7 @@
                 dirObj, newFile.getName(), true));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(fileObj.getPath().toString(), moved.getPath());
 
@@ -1248,6 +1320,7 @@
                 dirObj, movedDir.getName(), true));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
 
@@ -1257,6 +1330,7 @@
         createNewFile(renamed);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -1283,13 +1357,14 @@
                 dirObj, movedDir.getName(), true));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
 
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events since the dir was visited
-        createNewFile(renamed);
+        createNewFileNonZero(renamed);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertTrue(manager.checkConsistency());
@@ -1299,7 +1374,7 @@
     @SmallTest
     public void testMoveObjectDelayed() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File dir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -1348,13 +1423,14 @@
         manager.flushEvents();
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
 
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events since the dir was visited
-        createNewFile(renamed);
+        createNewFileNonZero(renamed);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertTrue(manager.checkConsistency());
@@ -1364,7 +1440,7 @@
     @SmallTest
     public void testMoveObjectFailed() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File dir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -1379,6 +1455,7 @@
                 dirObj, newFile.getName(), false));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
 
         Assert.assertTrue(manager.checkConsistency());
@@ -1388,7 +1465,7 @@
     @SmallTest
     public void testMoveObjectFailedOldRemoved() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File dir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -1405,6 +1482,7 @@
                 dirObj, newFile.getName(), false));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 1);
 
         Assert.assertTrue(manager.checkConsistency());
@@ -1414,7 +1492,7 @@
     @SmallTest
     public void testMoveObjectFailedNewAdded() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         File dir = createNewDir(mainStorageDir);
         MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).stream()
@@ -1424,7 +1502,7 @@
                 .filter(o -> !o.isDir()).findFirst().get();
         Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
 
-        createNewFile(dir, newFile.getName());
+        createNewFileNonZero(dir, newFile.getName());
         manager.flushEvents();
         Assert.assertTrue(manager.endMoveObject(
                 manager.getStorageRoot(mainMtpStorage.getStorageId()),
@@ -1440,14 +1518,14 @@
     @SmallTest
     public void testMoveObjectXStorageSuccess() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginMoveObject(fileObj,
                 manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
 
         Assert.assertTrue(newFile.delete());
-        File moved = createNewFile(secondaryStorageDir, newFile.getName());
+        File moved = createNewFileNonZero(secondaryStorageDir, newFile.getName());
         manager.flushEvents();
         Assert.assertTrue(manager.endMoveObject(
                 manager.getStorageRoot(mainMtpStorage.getStorageId()),
@@ -1481,6 +1559,7 @@
                 movedDir.getName(), true));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
                 moved.getPath());
@@ -1491,6 +1570,7 @@
         createNewFile(moved);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertTrue(manager.checkConsistency());
     }
 
@@ -1514,6 +1594,7 @@
                 movedDir.getName(), true));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
                 moved.getPath());
@@ -1521,7 +1602,7 @@
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events since the dir was visited
-        createNewFile(moved);
+        createNewFileNonZero(moved);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertTrue(manager.checkConsistency());
@@ -1531,7 +1612,7 @@
     @SmallTest
     public void testMoveObjectXStorageDelayed() {
         logMethodName();
-        File movedFile = createNewFile(mainStorageDir);
+        File movedFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginMoveObject(movedObj,
@@ -1543,7 +1624,7 @@
                 movedFile.getName(), true));
 
         Assert.assertTrue(movedFile.delete());
-        File moved = createNewFile(secondaryStorageDir, movedFile.getName());
+        File moved = createNewFileNonZero(secondaryStorageDir, movedFile.getName());
         manager.flushEvents();
 
         Assert.assertEquals(objectsAdded.size(), 0);
@@ -1575,6 +1656,7 @@
         manager.flushEvents();
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
         Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
                 moved.getPath());
@@ -1582,7 +1664,7 @@
         Assert.assertTrue(manager.checkConsistency());
 
         // Expect events since the dir was visited
-        createNewFile(moved);
+        createNewFileNonZero(moved);
         manager.flushEvents();
         Assert.assertEquals(objectsAdded.size(), 1);
         Assert.assertTrue(manager.checkConsistency());
@@ -1592,7 +1674,7 @@
     @SmallTest
     public void testMoveObjectXStorageFailed() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginMoveObject(fileObj,
@@ -1604,6 +1686,7 @@
                 newFile.getName(), false));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 0);
 
         Assert.assertTrue(manager.checkConsistency());
@@ -1613,7 +1696,7 @@
     @SmallTest
     public void testMoveObjectXStorageFailedOldRemoved() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginMoveObject(fileObj,
@@ -1627,6 +1710,7 @@
                 newFile.getName(), false));
 
         Assert.assertEquals(objectsAdded.size(), 0);
+        Assert.assertEquals(objectsInfoChanged.size(), 0);
         Assert.assertEquals(objectsRemoved.size(), 1);
 
         Assert.assertTrue(manager.checkConsistency());
@@ -1636,13 +1720,13 @@
     @SmallTest
     public void testMoveObjectXStorageFailedNewAdded() {
         logMethodName();
-        File newFile = createNewFile(mainStorageDir);
+        File newFile = createNewFileNonZero(mainStorageDir);
         MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
                 mainMtpStorage.getStorageId()).get(0);
         Assert.assertTrue(manager.beginMoveObject(fileObj,
                 manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
 
-        createNewFile(secondaryStorageDir, newFile.getName());
+        createNewFileNonZero(secondaryStorageDir, newFile.getName());
         manager.flushEvents();
         Assert.assertTrue(manager.endMoveObject(
                 manager.getStorageRoot(mainMtpStorage.getStorageId()),
@@ -1654,4 +1738,4 @@
 
         Assert.assertTrue(manager.checkConsistency());
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 466d02b..0497618 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -106,7 +106,6 @@
                 new AudioModeChangedHandler());
 
         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
-        mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
     }
 
     public void setReceiverHandler(android.os.Handler handler) {
@@ -150,8 +149,7 @@
         for (BluetoothDevice device : bondedDevices) {
             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
             if (cachedDevice == null) {
-                cachedDevice = mDeviceManager.addDevice(device);
-                dispatchDeviceAdded(cachedDevice);
+                mDeviceManager.addDevice(device);
                 deviceAdded = true;
             }
         }
@@ -276,7 +274,6 @@
         public void onReceive(Context context, Intent intent,
                 BluetoothDevice device) {
             short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
-            BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
             String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
             // TODO Pick up UUID. They should be available for 2.1 devices.
             // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
@@ -287,8 +284,6 @@
                         + cachedDevice);
             }
             cachedDevice.setRssi(rssi);
-            cachedDevice.setBtClass(btClass);
-            cachedDevice.setNewName(name);
             cachedDevice.setJustDiscovered(true);
         }
     }
@@ -339,18 +334,9 @@
                     BluetoothDevice.ERROR);
             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
             if (cachedDevice == null) {
-                Log.w(TAG, "CachedBluetoothDevice for device " + device +
-                        " not found, calling readPairedDevices().");
-                if (readPairedDevices()) {
-                    cachedDevice = mDeviceManager.findDevice(device);
-                }
-
-                if (cachedDevice == null) {
-                    Log.w(TAG, "Got bonding state changed for " + device +
-                            ", but we have no record of that device.");
-                    cachedDevice = mDeviceManager.addDevice(device);
-                    dispatchDeviceAdded(cachedDevice);
-                }
+                Log.w(TAG, "Got bonding state changed for " + device +
+                        ", but we have no record of that device.");
+                cachedDevice = mDeviceManager.addDevice(device);
             }
 
             synchronized (mCallbacks) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index c511589..33ee569 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -51,13 +51,9 @@
     private final BluetoothAdapter mLocalAdapter;
     private final LocalBluetoothProfileManager mProfileManager;
     private final BluetoothDevice mDevice;
-    //TODO: consider remove, BluetoothDevice.getName() is already cached
-    private String mName;
     private long mHiSyncId;
     // Need this since there is no method for getting RSSI
     private short mRssi;
-    //TODO: consider remove, BluetoothDevice.getBluetoothClass() is already cached
-    private BluetoothClass mBtClass;
     private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState;
 
     private final List<LocalBluetoothProfile> mProfiles =
@@ -299,7 +295,7 @@
             }
             return;
         }
-        Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
+        Log.i(TAG, "Failed to connect " + profile.toString() + " to " + getName());
     }
 
     private boolean ensurePaired() {
@@ -376,8 +372,6 @@
 
     // TODO: do any of these need to run async on a background thread?
     private void fillData() {
-        fetchName();
-        fetchBtClass();
         updateProfiles();
         fetchActiveDevices();
         migratePhonebookPermissionChoice();
@@ -400,21 +394,15 @@
         return mDevice.getAddress();
     }
 
-    public String getName() {
-        return mName;
-    }
-
     /**
-     * Populate name from BluetoothDevice.ACTION_FOUND intent
+     * Get name from remote device
+     * @return {@link BluetoothDevice#getAliasName()} if
+     * {@link BluetoothDevice#getAliasName()} is not null otherwise return
+     * {@link BluetoothDevice#getAddress()}
      */
-    void setNewName(String name) {
-        if (mName == null) {
-            mName = name;
-            if (mName == null || TextUtils.isEmpty(mName)) {
-                mName = mDevice.getAddress();
-            }
-            dispatchAttributesChanged();
-        }
+    public String getName() {
+        final String aliasName = mDevice.getAliasName();
+        return TextUtils.isEmpty(aliasName) ? getAddress() : aliasName;
     }
 
     /**
@@ -422,9 +410,8 @@
      * @param name new alias name to be set, should never be null
      */
     public void setName(String name) {
-        // Prevent mName to be set to null if setName(null) is called
-        if (name != null && !TextUtils.equals(name, mName)) {
-            mName = name;
+        // Prevent getName() to be set to null if setName(null) is called
+        if (name != null && !TextUtils.equals(name, getName())) {
             mDevice.setAlias(name);
             dispatchAttributesChanged();
         }
@@ -461,19 +448,10 @@
     }
 
     void refreshName() {
-        fetchName();
-        dispatchAttributesChanged();
-    }
-
-    private void fetchName() {
-        mName = mDevice.getAliasName();
-
-        if (TextUtils.isEmpty(mName)) {
-            mName = mDevice.getAddress();
-            if (BluetoothUtils.D) {
-                Log.d(TAG, "Device has no name (yet), use address: " + mName);
-            }
+        if (BluetoothUtils.D) {
+            Log.d(TAG, "Device name: " + getName());
         }
+        dispatchAttributesChanged();
     }
 
     /**
@@ -606,13 +584,6 @@
         return getBondState() == BluetoothDevice.BOND_BONDING;
     }
 
-    /**
-     * Fetches a new value for the cached BT class.
-     */
-    private void fetchBtClass() {
-        mBtClass = mDevice.getBluetoothClass();
-    }
-
     private boolean updateProfiles() {
         ParcelUuid[] uuids = mDevice.getUuids();
         if (uuids == null) return false;
@@ -657,15 +628,6 @@
     }
 
     /**
-     * Refreshes the UI for the BT class, including fetching the latest value
-     * for the class.
-     */
-    void refreshBtClass() {
-        fetchBtClass();
-        dispatchAttributesChanged();
-    }
-
-    /**
      * Refreshes the UI when framework alerts us of a UUID change.
      */
     void onUuidChanged() {
@@ -715,15 +677,8 @@
         }
     }
 
-    void setBtClass(BluetoothClass btClass) {
-        if (btClass != null && mBtClass != btClass) {
-            mBtClass = btClass;
-            dispatchAttributesChanged();
-        }
-    }
-
     public BluetoothClass getBtClass() {
-        return mBtClass;
+        return mDevice.getBluetoothClass();
     }
 
     public List<LocalBluetoothProfile> getProfiles() {
@@ -757,7 +712,7 @@
         }
     }
 
-    private void dispatchAttributesChanged() {
+    void dispatchAttributesChanged() {
         synchronized (mCallbacks) {
             for (Callback callback : mCallbacks) {
                 callback.onDeviceAttributesChanged();
@@ -805,7 +760,7 @@
         if (comparison != 0) return comparison;
 
         // Fallback on name
-        return mName.compareTo(another.mName);
+        return getName().compareTo(another.getName());
     }
 
     public interface Callback {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index f8543fc..47bd853 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -260,7 +260,7 @@
     public synchronized void onBtClassChanged(BluetoothDevice device) {
         CachedBluetoothDevice cachedDevice = findDevice(device);
         if (cachedDevice != null) {
-            cachedDevice.refreshBtClass();
+            cachedDevice.dispatchAttributesChanged();
         }
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 5e417c3..c18db11 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -606,4 +606,35 @@
 
         assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
     }
+
+    @Test
+    public void getName_aliasNameNotNull_returnAliasName() {
+        when(mDevice.getAliasName()).thenReturn(DEVICE_NAME);
+
+        assertThat(mCachedDevice.getName()).isEqualTo(DEVICE_NAME);
+    }
+
+    @Test
+    public void getName_aliasNameIsNull_returnAddress() {
+        when(mDevice.getAliasName()).thenReturn(null);
+
+        assertThat(mCachedDevice.getName()).isEqualTo(DEVICE_ADDRESS);
+    }
+
+    @Test
+    public void setName_setDeviceNameIsNotNull() {
+        final String name = "test name";
+        when(mDevice.getAliasName()).thenReturn(DEVICE_NAME);
+
+        mCachedDevice.setName(name);
+
+        verify(mDevice).setAlias(name);
+    }
+
+    @Test
+    public void setName_setDeviceNameIsNull() {
+        mCachedDevice.setName(null);
+
+        verify(mDevice, never()).setAlias(any());
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
index f223176..e05f9fd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
@@ -241,6 +241,7 @@
         mShadowBluetoothAdapter.setSupportedProfiles(null);
         mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter,
                 mDeviceManager, mEventManager);
+        mEventManager.registerProfileIntentReceiver();
         // Refer to BluetoothControllerImpl, it will call setReceiverHandler after
         // LocalBluetoothProfileManager created.
         mEventManager.setReceiverHandler(null);
diff --git a/packages/SystemUI/res/drawable-ldrtl/ic_sysbar_back_carmode.xml b/packages/SystemUI/res/drawable-ldrtl/ic_sysbar_back_carmode.xml
deleted file mode 100644
index f91190b..0000000
--- a/packages/SystemUI/res/drawable-ldrtl/ic_sysbar_back_carmode.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="44dp"
-        android:height="44dp"
-        android:viewportWidth="44.0"
-        android:viewportHeight="44.0">
-    <path
-        android:pathData="M35,21.94C35,22.78 34.49,23.58 33.64,24.05L12.44,36.13C11.43,36.7 10.58,36.51 10.11,36.25C9.08,35.67 9,34.56 9,34.09L9,9.91C9,9.35 9.08,8.31 10.09,7.75C10.54,7.49 11.34,7.31 12.33,7.86L33.74,19.95C34.51,20.39 35,21.13 35,21.94L35,21.94ZM12.5,32L30.5,21.96L12.5,12L12.5,32Z"
-        android:strokeColor="#00000000"
-        android:fillType="evenOdd"
-        android:fillColor="#F8F9FA"
-        android:strokeWidth="1"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_back_ime_carmode.xml b/packages/SystemUI/res/drawable/ic_sysbar_back_ime_carmode.xml
deleted file mode 100644
index 542ba9b..0000000
--- a/packages/SystemUI/res/drawable/ic_sysbar_back_ime_carmode.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="44dp"
-        android:height="44dp"
-        android:viewportWidth="44.0"
-        android:viewportHeight="44.0">
-    <path
-        android:pathData="M22.06,35C21.22,35 20.42,34.49 19.95,33.64L7.87,12.44C7.3,11.43 7.49,10.58 7.75,10.11C8.33,9.08 9.44,9 9.91,9L34.09,9C34.65,9 35.69,9.08 36.25,10.09C36.51,10.54 36.69,11.34 36.14,12.33L24.05,33.74C23.61,34.51 22.87,35 22.06,35L22.06,35ZM12,12.5L22.04,30.5L32,12.5L12,12.5Z"
-        android:strokeColor="#00000000"
-        android:fillType="evenOdd"
-        android:fillColor="#F8F9FA"
-        android:strokeWidth="1"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/qs_customize_divider.xml b/packages/SystemUI/res/layout/qs_customize_divider.xml
index 51febc7..6fcfa8d 100644
--- a/packages/SystemUI/res/layout/qs_customize_divider.xml
+++ b/packages/SystemUI/res/layout/qs_customize_divider.xml
@@ -23,6 +23,6 @@
     android:gravity="center"
     android:paddingTop="20dp"
     android:paddingBottom="13dp"
-    android:textAppearance="@android:style/TextAppearance.Material.Body2"
+    android:textAppearance="@style/TextAppearance.QSEdit.Headers"
     android:textColor="?android:attr/colorAccent"
     android:text="@string/drag_to_add_tiles" />
diff --git a/packages/SystemUI/res/layout/qs_customize_header.xml b/packages/SystemUI/res/layout/qs_customize_header.xml
new file mode 100644
index 0000000..093f971
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_customize_header.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2018 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.
+-->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/title"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:minHeight="28dp"
+    android:textAppearance="@style/TextAppearance.QSEdit.Headers"
+    android:textColor="?android:attr/colorAccent"
+    android:text="@string/drag_to_rearrange_tiles" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
index 4ce6ef6..bb67c54 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
@@ -43,7 +43,6 @@
             android:layout_width="match_parent"
             android:layout_height="0dp"
             android:layout_weight="1"
-            android:paddingTop="28dp"
             android:paddingLeft="@dimen/qs_tile_layout_margin_side"
             android:paddingRight="@dimen/qs_tile_layout_margin_side"
             android:paddingBottom="28dp"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c113745d..2deec5e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1869,6 +1869,9 @@
     <!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
     <string name="drag_to_add_tiles">Hold and drag to add tiles</string>
 
+    <!-- Label for header of customize QS [CHAR LIMIT=60] -->
+    <string name="drag_to_rearrange_tiles">Hold and drag to rearrange tiles</string>
+
     <!-- Label for area where tiles can be dragged in to [CHAR LIMIT=60] -->
     <string name="drag_to_remove_tiles">Drag here to remove</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c0b50ea..47ec5cd 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -495,6 +495,11 @@
            parent="@*android:style/TextAppearance.Material.Notification.Info">
     </style>
 
+    <style name="TextAppearance.QSEdit.Headers"
+        parent="@*android:style/TextAppearance.Material.Body2">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
     <style name="edit_theme" parent="qs_theme">
         <item name="android:colorBackground">?android:attr/colorSecondary</item>
     </style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index ebfadd8..1c46f12 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -17,7 +17,6 @@
 package com.android.systemui.shared.recents;
 
 import android.graphics.Rect;
-import com.android.systemui.shared.system.GraphicBufferCompat;
 
 /**
  * Temporary callbacks into SystemUI.
@@ -26,9 +25,10 @@
 
     /**
      * Proxies SurfaceControl.screenshotToBuffer().
+     * @Removed
+     * GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer,
+     *             int maxLayer, boolean useIdentityTransform, int rotation) = 0;
      */
-    GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer,
-            int maxLayer, boolean useIdentityTransform, int rotation) = 0;
 
     /**
      * Begins screen pinning on the provided {@param taskId}.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java
deleted file mode 100644
index 66b8fed..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.shared.system;
-
-import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Wraps the internal graphic buffer.
- */
-public class GraphicBufferCompat implements Parcelable {
-
-    private GraphicBuffer mBuffer;
-
-    public GraphicBufferCompat(GraphicBuffer buffer) {
-        mBuffer = buffer;
-    }
-
-    public GraphicBufferCompat(Parcel in) {
-        mBuffer = GraphicBuffer.CREATOR.createFromParcel(in);
-    }
-
-    public Bitmap toBitmap() {
-        return mBuffer != null
-                ? Bitmap.createHardwareBitmap(mBuffer)
-                : null;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        mBuffer.writeToParcel(dest, flags);
-    }
-
-    public static final Parcelable.Creator<GraphicBufferCompat> CREATOR
-            = new Parcelable.Creator<GraphicBufferCompat>() {
-        public GraphicBufferCompat createFromParcel(Parcel in) {
-            return new GraphicBufferCompat(in);
-        }
-
-        public GraphicBufferCompat[] newArray(int size) {
-            return new GraphicBufferCompat[size];
-        }
-    };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index bc1552e..ad853f1 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -32,7 +32,6 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.SurfaceControl;
 
 import com.android.systemui.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.recents.events.EventBus;
@@ -41,7 +40,6 @@
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.GraphicBufferCompat;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.CallbackController;
@@ -94,20 +92,6 @@
 
     private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
 
-        public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer,
-                int maxLayer, boolean useIdentityTransform, int rotation) {
-            if (!verifyCaller("screenshot")) {
-                return null;
-            }
-            long token = Binder.clearCallingIdentity();
-            try {
-                return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width,
-                        height, minLayer, maxLayer, useIdentityTransform, rotation));
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
         public void startScreenPinning(int taskId) {
             if (!verifyCaller("startScreenPinning")) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 10b92f7..92f5cae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -62,6 +62,7 @@
     private static final int TYPE_TILE = 0;
     private static final int TYPE_EDIT = 1;
     private static final int TYPE_ACCESSIBLE_DROP = 2;
+    private static final int TYPE_HEADER = 3;
     private static final int TYPE_DIVIDER = 4;
 
     private static final long EDIT_ID = 10000;
@@ -112,7 +113,7 @@
 
     public void saveSpecs(QSTileHost host) {
         List<String> newSpecs = new ArrayList<>();
-        for (int i = 0; i < mTiles.size() && mTiles.get(i) != null; i++) {
+        for (int i = 1; i < mTiles.size() && mTiles.get(i) != null; i++) {
             newSpecs.add(mTiles.get(i).spec);
         }
         host.changeTiles(mCurrentSpecs, newSpecs);
@@ -145,6 +146,7 @@
         }
         mOtherTiles = new ArrayList<TileInfo>(mAllTiles);
         mTiles.clear();
+        mTiles.add(null);
         for (int i = 0; i < mCurrentSpecs.size(); i++) {
             final TileInfo tile = getAndRemoveOther(mCurrentSpecs.get(i));
             if (tile != null) {
@@ -177,6 +179,9 @@
 
     @Override
     public int getItemViewType(int position) {
+        if (position == 0) {
+            return TYPE_HEADER;
+        }
         if (mAccessibilityAction == ACTION_ADD && position == mEditIndex - 1) {
             return TYPE_ACCESSIBLE_DROP;
         }
@@ -193,6 +198,9 @@
     public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
         final Context context = parent.getContext();
         LayoutInflater inflater = LayoutInflater.from(context);
+        if (viewType == TYPE_HEADER) {
+            return new Holder(inflater.inflate(R.layout.qs_customize_header, parent, false));
+        }
         if (viewType == TYPE_DIVIDER) {
             return new Holder(inflater.inflate(R.layout.qs_customize_tile_divider, parent, false));
         }
@@ -218,6 +226,9 @@
 
     @Override
     public void onBindViewHolder(final Holder holder, int position) {
+        if (holder.getItemViewType() == TYPE_HEADER) {
+            return;
+        }
         if (holder.getItemViewType() == TYPE_DIVIDER) {
             holder.itemView.setVisibility(mTileDividerIndex < mTiles.size() - 1 ? View.VISIBLE
                     : View.INVISIBLE);
@@ -243,7 +254,7 @@
             holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
             holder.mTileView.setContentDescription(mContext.getString(
                     R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel,
-                    position + 1));
+                    position));
             holder.mTileView.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
@@ -274,13 +285,13 @@
                     R.string.accessibility_qs_edit_add_tile_label, info.state.label);
         } else if (mAccessibilityAction == ACTION_ADD) {
             info.state.contentDescription = mContext.getString(
-                    R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel, position + 1);
+                    R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel, position);
         } else if (mAccessibilityAction == ACTION_MOVE) {
             info.state.contentDescription = mContext.getString(
-                    R.string.accessibility_qs_edit_tile_move, mAccessibilityFromLabel, position + 1);
+                    R.string.accessibility_qs_edit_tile_move, mAccessibilityFromLabel, position);
         } else {
             info.state.contentDescription = mContext.getString(
-                    R.string.accessibility_qs_edit_tile_label, position + 1, info.state.label);
+                    R.string.accessibility_qs_edit_tile_label, position, info.state.label);
         }
         holder.mTileView.handleStateChanged(info.state);
         holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem);
@@ -403,11 +414,12 @@
     }
 
     private void updateDividerLocations() {
-        // The first null is the edit tiles label, the second null is the tile divider.
-        // If there is no second null, then there are no non-system tiles.
+        // The first null is the header label (index 0) so we can skip it,
+        // the second null is the edit tiles label, the third null is the tile divider.
+        // If there is no third null, then there are no non-system tiles.
         mEditIndex = -1;
         mTileDividerIndex = mTiles.size();
-        for (int i = 0; i < mTiles.size(); i++) {
+        for (int i = 1; i < mTiles.size(); i++) {
             if (mTiles.get(i) == null) {
                 if (mEditIndex == -1) {
                     mEditIndex = i;
@@ -486,7 +498,7 @@
         @Override
         public int getSpanSize(int position) {
             final int type = getItemViewType(position);
-            return type == TYPE_EDIT || type == TYPE_DIVIDER ? 3 : 1;
+            return type == TYPE_EDIT || type == TYPE_DIVIDER || type == TYPE_HEADER ? 3 : 1;
         }
     };
 
@@ -511,7 +523,8 @@
             for (int i = 0; i < childCount; i++) {
                 final View child = parent.getChildAt(i);
                 final ViewHolder holder = parent.getChildViewHolder(child);
-                if (holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) {
+                if (holder.getAdapterPosition() == 0 ||
+                        holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) {
                     continue;
                 }
 
@@ -569,6 +582,9 @@
         @Override
         public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
                 ViewHolder target) {
+            if (target.getAdapterPosition() == 0){
+                return false;
+            }
             if (!canRemoveTiles() && current.getAdapterPosition() < mEditIndex) {
                 return target.getAdapterPosition() < mEditIndex;
             }
@@ -577,12 +593,17 @@
 
         @Override
         public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
-            if (viewHolder.getItemViewType() == TYPE_EDIT || viewHolder.getItemViewType() == TYPE_DIVIDER) {
-                return makeMovementFlags(0, 0);
+            switch (viewHolder.getItemViewType()) {
+                case TYPE_EDIT:
+                case TYPE_DIVIDER:
+                case TYPE_HEADER:
+                    // Fall through
+                    return makeMovementFlags(0, 0);
+                default:
+                    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN
+                            | ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT;
+                    return makeMovementFlags(dragFlags, 0);
             }
-            int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT
-                    | ItemTouchHelper.LEFT;
-            return makeMovementFlags(dragFlags, 0);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 3e41cd2..0d9c623 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -123,9 +123,9 @@
     private Rect mTmpRect = new Rect();
 
     private KeyButtonDrawable mBackIcon;
-    private KeyButtonDrawable mBackCarModeIcon, mBackLandCarModeIcon;
-    private KeyButtonDrawable mBackAltCarModeIcon, mBackAltLandCarModeIcon;
-    private KeyButtonDrawable mHomeDefaultIcon, mHomeCarModeIcon;
+    private KeyButtonDrawable mHomeDefaultIcon;
+    private KeyButtonDrawable mBackCarModeIcon;
+    private KeyButtonDrawable mHomeCarModeIcon;
     private KeyButtonDrawable mRecentIcon;
     private KeyButtonDrawable mDockedIcon;
     private KeyButtonDrawable mImeIcon;
@@ -461,16 +461,9 @@
                 && ((mOverviewProxyService.getInteractionFlags() & FLAG_DISABLE_QUICK_SCRUB) == 0);
     }
 
-    // TODO(b/80003212): change car mode icons to vector icons.
     private void updateCarModeIcons(Context ctx) {
-        mBackCarModeIcon = getDrawable(ctx,
-                R.drawable.ic_sysbar_back_carmode, R.drawable.ic_sysbar_back_carmode);
-        mBackLandCarModeIcon = mBackCarModeIcon;
-        mBackAltCarModeIcon = getDrawable(ctx,
-                R.drawable.ic_sysbar_back_ime_carmode, R.drawable.ic_sysbar_back_ime_carmode);
-        mBackAltLandCarModeIcon = mBackAltCarModeIcon;
-        mHomeCarModeIcon = getDrawable(ctx,
-                R.drawable.ic_sysbar_home_carmode, R.drawable.ic_sysbar_home_carmode);
+        mBackCarModeIcon = getDrawable(ctx, R.drawable.ic_sysbar_back_carmode);
+        mHomeCarModeIcon = getDrawable(ctx, R.drawable.ic_sysbar_home_carmode);
     }
 
     private void reloadNavIcons() {
@@ -575,11 +568,9 @@
                 darkContext.getDrawable(icon), hasShadow);
     }
 
-    private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int lightIcon,
-            @DrawableRes int darkIcon) {
-        // Legacy image icons using separate light and dark images will not support shadows
-        return KeyButtonDrawable.create(ctx, ctx.getDrawable(lightIcon),
-            ctx.getDrawable(darkIcon), false /* hasShadow */);
+    private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon) {
+        // Legacy image icons using a single image will not support shadows
+        return KeyButtonDrawable.create(ctx, ctx.getDrawable(icon), null, false /* hasShadow */);
     }
 
     private TintedKeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon,
@@ -594,16 +585,8 @@
         super.setLayoutDirection(layoutDirection);
     }
 
-    private KeyButtonDrawable getBackIconWithAlt(boolean carMode, boolean landscape) {
-        return landscape
-                ? carMode ? mBackAltLandCarModeIcon : mBackIcon
-                : carMode ? mBackAltCarModeIcon : mBackIcon;
-    }
-
-    private KeyButtonDrawable getBackIcon(boolean carMode, boolean landscape) {
-        return landscape
-                ? carMode ? mBackLandCarModeIcon : mBackIcon
-                : carMode ? mBackCarModeIcon : mBackIcon;
+    private KeyButtonDrawable getBackIcon(boolean carMode) {
+        return carMode ? mBackCarModeIcon : mBackIcon;
     }
 
     public void setNavigationIconHints(int hints) {
@@ -643,12 +626,10 @@
         // to recent icon is not required.
         final boolean useAltBack =
                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
-        KeyButtonDrawable backIcon = useAltBack
-                ? getBackIconWithAlt(mUseCarModeUi, mVertical)
-                : getBackIcon(mUseCarModeUi, mVertical);
+        KeyButtonDrawable backIcon = getBackIcon(mUseCarModeUi);
+        orientBackButton(backIcon);
         KeyButtonDrawable homeIcon = mUseCarModeUi ? mHomeCarModeIcon : mHomeDefaultIcon;
         if (!mUseCarModeUi) {
-            orientBackButton(backIcon);
             orientHomeButton(homeIcon);
         }
         getHomeButton().setImageDrawable(homeIcon);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NoisyVelocityTracker.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NoisyVelocityTracker.java
index 214dda2..9c9b929 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NoisyVelocityTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NoisyVelocityTracker.java
@@ -60,7 +60,7 @@
         if (mEventBuf.size() == MAX_EVENTS) {
             mEventBuf.remove();
         }
-        mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
+        mEventBuf.add(new MotionEventCopy(event.getRawX(), event.getRawY(), event.getEventTime()));
     }
 
     public void computeCurrentVelocity(int units) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 9a26709..bef34f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -227,13 +227,7 @@
     }
 
     private void trackMovement(MotionEvent event) {
-        // Add movement to velocity tracker using raw screen X and Y coordinates instead
-        // of window coordinates because the window frame may be moving at the same time.
-        float deltaX = event.getRawX() - event.getX();
-        float deltaY = event.getRawY() - event.getY();
-        event.offsetLocation(deltaX, deltaY);
         if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
-        event.offsetLocation(-deltaX, -deltaY);
     }
 
     public void setTouchAndAnimationDisabled(boolean disabled) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 75b3556..0eff7f5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -151,6 +151,8 @@
     // Otherwise WFD is enabled according to the value of config_enableWifiDisplay.
     private static final String FORCE_WIFI_DISPLAY_ENABLE = "persist.debug.wfd.enable";
 
+    private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
+
     private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
 
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
@@ -243,6 +245,15 @@
     // device).
     private Point mStableDisplaySize = new Point();
 
+    // Whether the system has finished booting or not.
+    private boolean mSystemReady;
+
+    // The top inset of the default display.
+    // This gets persisted so that the boot animation knows how to transition from the display's
+    // full size to the size configured by the user. Right now we only persist and animate the top
+    // inset, but theoretically we could do it for all of them.
+    private int mDefaultDisplayTopInset;
+
     // Viewports of the default display and the display that should receive touch
     // input from an external source.  Used by the input system.
     private final DisplayViewport mDefaultViewport = new DisplayViewport();
@@ -301,6 +312,7 @@
         Resources resources = mContext.getResources();
         mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
+        mDefaultDisplayTopInset = SystemProperties.getInt(PROP_DEFAULT_DISPLAY_TOP_INSET, -1);
         float[] lux = getFloatArray(resources.obtainTypedArray(
                 com.android.internal.R.array.config_minimumBrightnessCurveLux));
         float[] nits = getFloatArray(resources.obtainTypedArray(
@@ -311,6 +323,8 @@
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
         mCurrentUserId = UserHandle.USER_SYSTEM;
+
+        mSystemReady = false;
     }
 
     public void setupSchedulerPolicies() {
@@ -400,6 +414,10 @@
         synchronized (mSyncRoot) {
             mSafeMode = safeMode;
             mOnlyCore = onlyCore;
+            mSystemReady = true;
+            // Just in case the top inset changed before the system was ready. At this point, any
+            // relevant configuration should be in place.
+            recordTopInsetLocked(mLogicalDisplays.get(Display.DEFAULT_DISPLAY));
         }
 
         mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
@@ -457,7 +475,7 @@
             LogicalDisplay display = mLogicalDisplays.get(displayId);
             if (display != null) {
                 if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
-                    sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                    handleLogicalDisplayChanged(displayId, display);
                     scheduleTraversalLocked(false);
                 }
             }
@@ -938,6 +956,13 @@
         scheduleTraversalLocked(false);
     }
 
+    private void handleLogicalDisplayChanged(int displayId, @NonNull LogicalDisplay display) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            recordTopInsetLocked(display);
+        }
+        sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+    }
+
     private void applyGlobalDisplayStateLocked(List<Runnable> workQueue) {
         final int count = mDisplayDevices.size();
         for (int i = 0; i < count; i++) {
@@ -991,6 +1016,7 @@
         configureColorModeLocked(display, device);
         if (isDefault) {
             recordStableDisplayStatsIfNeededLocked(display);
+            recordTopInsetLocked(display);
         }
 
         mLogicalDisplays.put(displayId, display);
@@ -1039,6 +1065,21 @@
         }
     }
 
+    private void recordTopInsetLocked(@Nullable LogicalDisplay d) {
+        // We must only persist the inset after boot has completed, otherwise we will end up
+        // overwriting the persisted value before the masking flag has been loaded from the
+        // resource overlay.
+        if (!mSystemReady || d == null) {
+            return;
+        }
+        int topInset = d.getInsets().top;
+        if (topInset == mDefaultDisplayTopInset) {
+            return;
+        }
+        mDefaultDisplayTopInset = topInset;
+        SystemProperties.set(PROP_DEFAULT_DISPLAY_TOP_INSET, Integer.toString(topInset));
+    }
+
     private void setStableDisplaySizeLocked(int width, int height) {
         mStableDisplaySize = new Point(width, height);
         try {
@@ -1118,7 +1159,7 @@
                 sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
                 changed = true;
             } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
-                sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                handleLogicalDisplayChanged(displayId, display);
                 changed = true;
             }
         }
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 373de63..5b7c520 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerInternal;
+import android.os.SystemProperties;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -57,6 +58,8 @@
  * </p>
  */
 final class LogicalDisplay {
+    private static final String PROP_MASKING_INSET_TOP = "persist.sys.displayinset.top";
+
     private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
 
     // The layer stack we use when the display has been blanked to prevent any
@@ -297,6 +300,17 @@
     }
 
     /**
+     * Return the insets currently applied to the display.
+     *
+     * Note that the base DisplayInfo already takes these insets into account, so if you want to
+     * find out the <b>true</b> size of the display, you need to add them back to the logical
+     * dimensions.
+     */
+    public Rect getInsets() {
+        return getMaskingInsets(mPrimaryDisplayDeviceInfo);
+    }
+
+    /**
      * Returns insets in ROTATION_0 for areas that are masked.
      */
     private static Rect getMaskingInsets(DisplayDeviceInfo deviceInfo) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index f082271..c738701 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -22,11 +22,14 @@
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.SIGNATURE_MATCH;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityThread;
 import android.app.IActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -34,6 +37,7 @@
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManagerInternal;
@@ -269,13 +273,30 @@
 
     @Override
     public void onBootPhase(int phase) {
-        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+        if (phase == PHASE_SYSTEM_SERVICES_READY && mInitCompleteSignal != null) {
             ConcurrentUtils.waitForFutureNoInterrupt(mInitCompleteSignal,
                     "Wait for OverlayManagerService init");
             mInitCompleteSignal = null;
         }
     }
 
+    public void updateSystemUiContext() {
+        if (mInitCompleteSignal != null) {
+            ConcurrentUtils.waitForFutureNoInterrupt(mInitCompleteSignal,
+                    "Wait for OverlayManagerService init");
+            mInitCompleteSignal = null;
+        }
+
+        final ApplicationInfo ai;
+        try {
+            ai = mPackageManager.mPackageManager.getApplicationInfo("android",
+                    GET_SHARED_LIBRARY_FILES, UserHandle.USER_SYSTEM);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+        ActivityThread.currentActivityThread().handleSystemApplicationInfoChanged(ai);
+    }
+
     private void initIfNeeded() {
         final UserManager um = getContext().getSystemService(UserManager.class);
         final List<UserInfo> users = um.getUsers(true /*excludeDying*/);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fc2813b..18bed54 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8866,15 +8866,16 @@
                     + " better than this " + pkg.getLongVersionCode());
         }
 
-        // Verify certificates against what was last scanned. If there was an upgrade or this is an
-        // updated priv app, we will force re-collecting certificate.
-        final boolean forceCollect = mIsUpgrade ||
-                PackageManagerServiceUtils.isApkVerificationForced(disabledPkgSetting);
+        // Verify certificates against what was last scanned. If there was an upgrade and this is an
+        // app in a system partition, or if this is an updated priv app, we will force re-collecting
+        // certificate.
+        final boolean forceCollect = (mIsUpgrade && scanSystemPartition)
+                || PackageManagerServiceUtils.isApkVerificationForced(disabledPkgSetting);
         // Full APK verification can be skipped during certificate collection, only if the file is
         // in verified partition, or can be verified on access (when apk verity is enabled). In both
         // cases, only data in Signing Block is verified instead of the whole file.
-        final boolean skipVerify = ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) ||
-                (forceCollect && canSkipFullPackageVerification(pkg));
+        final boolean skipVerify = scanSystemPartition
+                || (forceCollect && canSkipFullPackageVerification(pkg));
         collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
 
         // Reset profile if the application version is changed
@@ -17351,11 +17352,11 @@
             final File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
             final File privilegedProductServicesAppDir =
                     new File(Environment.getProductServicesDirectory(), "priv-app");
-            return path.startsWith(privilegedAppDir.getCanonicalPath())
-                    || path.startsWith(privilegedVendorAppDir.getCanonicalPath())
-                    || path.startsWith(privilegedOdmAppDir.getCanonicalPath())
-                    || path.startsWith(privilegedProductAppDir.getCanonicalPath())
-                    || path.startsWith(privilegedProductServicesAppDir.getCanonicalPath());
+            return path.startsWith(privilegedAppDir.getCanonicalPath() + "/")
+                    || path.startsWith(privilegedVendorAppDir.getCanonicalPath() + "/")
+                    || path.startsWith(privilegedOdmAppDir.getCanonicalPath() + "/")
+                    || path.startsWith(privilegedProductAppDir.getCanonicalPath() + "/")
+                    || path.startsWith(privilegedProductServicesAppDir.getCanonicalPath() + "/");
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
@@ -17364,7 +17365,7 @@
 
     static boolean locationIsOem(String path) {
         try {
-            return path.startsWith(Environment.getOemDirectory().getCanonicalPath());
+            return path.startsWith(Environment.getOemDirectory().getCanonicalPath() + "/");
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
@@ -17373,8 +17374,8 @@
 
     static boolean locationIsVendor(String path) {
         try {
-            return path.startsWith(Environment.getVendorDirectory().getCanonicalPath())
-                    || path.startsWith(Environment.getOdmDirectory().getCanonicalPath());
+            return path.startsWith(Environment.getVendorDirectory().getCanonicalPath() + "/")
+                    || path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/");
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
@@ -17383,7 +17384,7 @@
 
     static boolean locationIsProduct(String path) {
         try {
-            return path.startsWith(Environment.getProductDirectory().getCanonicalPath());
+            return path.startsWith(Environment.getProductDirectory().getCanonicalPath() + "/");
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
@@ -17392,7 +17393,8 @@
 
     static boolean locationIsProductServices(String path) {
         try {
-            return path.startsWith(Environment.getProductServicesDirectory().getCanonicalPath());
+            return path.startsWith(
+              Environment.getProductServicesDirectory().getCanonicalPath() + "/");
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c82c242..2f6fca4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources.Theme;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
+import android.hardware.display.DisplayManagerInternal;
 import android.os.BaseBundle;
 import android.os.Binder;
 import android.os.Build;
@@ -694,9 +695,17 @@
 
         // Manages Overlay packages
         traceBeginAndSlog("StartOverlayManagerService");
-        mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
+        OverlayManagerService overlayManagerService = new OverlayManagerService(
+                mSystemContext, installer);
+        mSystemServiceManager.startService(overlayManagerService);
         traceEnd();
 
+        if (SystemProperties.getInt("persist.sys.displayinset.top", 0) > 0) {
+            // DisplayManager needs the overlay immediately.
+            overlayManagerService.updateSystemUiContext();
+            LocalServices.getService(DisplayManagerInternal.class).onOverlayChanged();
+        }
+
         // The sensor service needs access to package manager service, app ops
         // service, and permissions service, therefore we start it after them.
         // Start sensor service in a separate thread. Completion should be checked
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index e379cd0..111fb74 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -107,4 +107,24 @@
         // TODO: test that sendApplicationHiddenForUser() actually fills in
         // broadcastUsers
     }
+
+    @Test
+    public void testPartitions() throws Exception {
+        String[] partitions = { "system", "vendor", "odm", "oem", "product", "product_services" };
+        String[] appdir = { "app", "priv-app" };
+        for (int i = 0; i < partitions.length; i++) {
+            for (int j = 0; j < appdir.length; j++) {
+                String canonical = new File("/" + partitions[i]).getCanonicalPath();
+                String path = String.format("%s/%s/A.apk", canonical, appdir[j]);
+
+                Assert.assertEquals(j == 1 && i != 3,
+                    PackageManagerService.locationIsPrivileged(path));
+
+                Assert.assertEquals(i == 1 || i == 2, PackageManagerService.locationIsVendor(path));
+                Assert.assertEquals(i == 3, PackageManagerService.locationIsOem(path));
+                Assert.assertEquals(i == 4, PackageManagerService.locationIsProduct(path));
+                Assert.assertEquals(i == 5, PackageManagerService.locationIsProductServices(path));
+            }
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a45d70b..97068a6 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -163,10 +163,20 @@
     /**
      * Flag indicating whether radio is to be restarted on error PDP_FAIL_REGULAR_DEACTIVATION
      * This is false by default.
+     *
+     * @deprecated Use {@link #KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY} instead
      */
-    public static final String
-            KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL =
-                    "restart_radio_on_pdp_fail_regular_deactivation_bool";
+    @Deprecated
+    public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL =
+            "restart_radio_on_pdp_fail_regular_deactivation_bool";
+
+    /**
+     * A list of failure cause codes that will trigger a modem restart when telephony receiving
+     * one of those during data setup. The cause codes are defined in 3GPP TS 24.008 Annex I and
+     * TS 24.301 Annex B.
+     */
+    public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY =
+            "radio_restart_failure_causes_int_array";
 
     /**
      * If true, enable vibration (haptic feedback) for key presses in the EmergencyDialer activity.
@@ -2124,6 +2134,7 @@
         sDefaults.putBoolean(KEY_WORLD_PHONE_BOOL, false);
         sDefaults.putBoolean(KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
         sDefaults.putBoolean(KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL, false);
+        sDefaults.putIntArray(KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY, new int[]{});
         sDefaults.putInt(KEY_VOLTE_REPLACEMENT_RAT_INT, 0);
         sDefaults.putString(KEY_DEFAULT_SIM_CALL_MANAGER_STRING, "");
         sDefaults.putString(KEY_VVM_DESTINATION_NUMBER_STRING, "");
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
new file mode 100644
index 0000000..9c80cb7
--- /dev/null
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.telephony.data.IQualifiedNetworksServiceCallback;
+
+/**
+ * {@hide}
+ */
+interface IQualifiedNetworksService
+{
+    oneway void createNetworkAvailabilityUpdater(int slotId, IQualifiedNetworksServiceCallback callback);
+    oneway void removeNetworkAvailabilityUpdater(int slotId);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
similarity index 64%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl
rename to telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
index f9450ad..e8e1f01 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2018 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.
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package android.telephony.data;
 
-parcelable GraphicBufferCompat;
\ No newline at end of file
+/**
+ * The qualified networks service call back interface
+ * @hide
+ */
+oneway interface IQualifiedNetworksServiceCallback
+{
+    void onQualifiedNetworkTypesChanged(int apnType, in int[] qualifiedNetworkTypesList);
+}
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
new file mode 100644
index 0000000..bb89f19
--- /dev/null
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.Rlog;
+import android.telephony.data.ApnSetting.ApnType;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Base class of the qualified networks service. Services that extend QualifiedNetworksService must
+ * register the service in their AndroidManifest to be detected by the framework. They must be
+ * protected by the permission "android.permission.BIND_TELEPHONY_QUALIFIED_NETWORKS_SERVICE".
+ * The qualified networks service definition in the manifest must follow the following format:
+ * ...
+ * <service android:name=".xxxQualifiedNetworksService"
+ *     android:permission="android.permission.BIND_TELEPHONY_QUALIFIED_NETWORKS_SERVICE" >
+ *     <intent-filter>
+ *         <action android:name="android.telephony.data.QualifiedNetworksService" />
+ *     </intent-filter>
+ * </service>
+ * @hide
+ */
+@SystemApi
+public abstract class QualifiedNetworksService extends Service {
+    private static final String TAG = QualifiedNetworksService.class.getSimpleName();
+
+    public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE =
+            "android.telephony.data.QualifiedNetworksService";
+
+    private static final int QNS_CREATE_NETWORK_AVAILABILITY_UPDATER                = 1;
+    private static final int QNS_REMOVE_NETWORK_AVAILABILITY_UPDATER                = 2;
+    private static final int QNS_REMOVE_ALL_NETWORK_AVAILABILITY_UPDATERS           = 3;
+    private static final int QNS_UPDATE_QUALIFIED_NETWORKS                          = 4;
+
+    private final HandlerThread mHandlerThread;
+
+    private final QualifiedNetworksServiceHandler mHandler;
+
+    private final SparseArray<NetworkAvailabilityUpdater> mUpdaters = new SparseArray<>();
+
+    /** @hide */
+    @VisibleForTesting
+    public final IQualifiedNetworksServiceWrapper mBinder = new IQualifiedNetworksServiceWrapper();
+
+    /**
+     * The abstract class of the network availability updater implementation. The vendor qualified
+     * network service must extend this class to report the available networks for data
+     * connection setup. Note that each instance of network availability updater is associated with
+     * one physical SIM slot.
+     */
+    public abstract class NetworkAvailabilityUpdater implements AutoCloseable {
+        private final int mSlotIndex;
+
+        private IQualifiedNetworksServiceCallback mCallback;
+
+        /**
+         * Qualified networks for each APN type. Key is the {@link ApnType}, value is the array
+         * of available networks.
+         */
+        private SparseArray<int[]> mQualifiedNetworkTypesList = new SparseArray<>();
+
+        /**
+         * Constructor
+         * @param slotIndex SIM slot index the network availability updater associated with.
+         */
+        public NetworkAvailabilityUpdater(int slotIndex) {
+            mSlotIndex = slotIndex;
+        }
+
+        /**
+         * @return SIM slot index the network availability updater associated with.
+         */
+        public final int getSlotIndex() {
+            return mSlotIndex;
+        }
+
+        private void registerForQualifiedNetworkTypesChanged(
+                IQualifiedNetworksServiceCallback callback) {
+            mCallback = callback;
+
+            // Force sending the qualified networks upon registered.
+            if (mCallback != null) {
+                for (int i = 0; i < mQualifiedNetworkTypesList.size(); i++) {
+                    try {
+                        mCallback.onQualifiedNetworkTypesChanged(
+                                mQualifiedNetworkTypesList.keyAt(i),
+                                mQualifiedNetworkTypesList.valueAt(i));
+                    } catch (RemoteException e) {
+                        loge("Failed to call onQualifiedNetworksChanged. " + e);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Update the qualified networks list. Network availability updater must invoke this method
+         * whenever the qualified networks changes. If this method is never invoked for certain
+         * APN type, then frameworks will always use the default (i.e. cellular) data and network
+         * service.
+         *
+         * @param apnType APN type of the qualified networks
+         * @param qualifiedNetworkTypes List of network types which are qualified for data
+         * connection setup for {@link @apnType} in the preferred order. Each element in the array
+         * is a {@link AccessNetworkType}. An empty list or null indicates no networks are qualified
+         * for data setup.
+         */
+        public final void updateQualifiedNetworkTypes(@ApnType int apnType,
+                                                      int[] qualifiedNetworkTypes) {
+            mHandler.obtainMessage(QNS_UPDATE_QUALIFIED_NETWORKS, mSlotIndex, apnType,
+                    qualifiedNetworkTypes).sendToTarget();
+        }
+
+        private void onUpdateQualifiedNetworkTypes(@ApnType int apnType,
+                                                   int[] qualifiedNetworkTypes) {
+            mQualifiedNetworkTypesList.put(apnType, qualifiedNetworkTypes);
+            if (mCallback != null) {
+                try {
+                    mCallback.onQualifiedNetworkTypesChanged(apnType, qualifiedNetworkTypes);
+                } catch (RemoteException e) {
+                    loge("Failed to call onQualifiedNetworksChanged. " + e);
+                }
+            }
+        }
+
+        /**
+         * Called when the qualified networks updater is removed. The extended class should
+         * implement this method to perform clean up works.
+         */
+        @Override
+        public abstract void close();
+    }
+
+    private class QualifiedNetworksServiceHandler extends Handler {
+        QualifiedNetworksServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            IQualifiedNetworksServiceCallback callback;
+            final int slotIndex = message.arg1;
+            NetworkAvailabilityUpdater updater = mUpdaters.get(slotIndex);
+
+            switch (message.what) {
+                case QNS_CREATE_NETWORK_AVAILABILITY_UPDATER:
+                    if (mUpdaters.get(slotIndex) != null) {
+                        loge("Network availability updater for slot " + slotIndex
+                                + " already existed.");
+                        return;
+                    }
+
+                    updater = createNetworkAvailabilityUpdater(slotIndex);
+                    if (updater != null) {
+                        mUpdaters.put(slotIndex, updater);
+
+                        callback = (IQualifiedNetworksServiceCallback) message.obj;
+                        updater.registerForQualifiedNetworkTypesChanged(callback);
+                    } else {
+                        loge("Failed to create network availability updater. slot index = "
+                                + slotIndex);
+                    }
+                    break;
+
+                case QNS_REMOVE_NETWORK_AVAILABILITY_UPDATER:
+                    if (updater != null) {
+                        updater.close();
+                        mUpdaters.remove(slotIndex);
+                    }
+                    break;
+
+                case QNS_REMOVE_ALL_NETWORK_AVAILABILITY_UPDATERS:
+                    for (int i = 0; i < mUpdaters.size(); i++) {
+                        updater = mUpdaters.get(i);
+                        if (updater != null) {
+                            updater.close();
+                        }
+                    }
+                    mUpdaters.clear();
+                    break;
+
+                case QNS_UPDATE_QUALIFIED_NETWORKS:
+                    if (updater == null) break;
+                    updater.onUpdateQualifiedNetworkTypes(message.arg2, (int[]) message.obj);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Default constructor.
+     */
+    public QualifiedNetworksService() {
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+
+        mHandler = new QualifiedNetworksServiceHandler(mHandlerThread.getLooper());
+        log("Qualified networks service created");
+    }
+
+    /**
+     * Create the instance of {@link NetworkAvailabilityUpdater}. Vendor qualified network service
+     * must override this method to facilitate the creation of {@link NetworkAvailabilityUpdater}
+     * instances. The system will call this method after binding the qualified networks service for
+     * each active SIM slot index.
+     *
+     * @param slotIndex SIM slot index the qualified networks service associated with.
+     * @return Qualified networks service instance
+     */
+    public abstract NetworkAvailabilityUpdater createNetworkAvailabilityUpdater(int slotIndex);
+
+    /** @hide */
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (intent == null || !QUALIFIED_NETWORKS_SERVICE_INTERFACE.equals(intent.getAction())) {
+            loge("Unexpected intent " + intent);
+            return null;
+        }
+        return mBinder;
+    }
+
+    /** @hide */
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mHandler.obtainMessage(QNS_REMOVE_ALL_NETWORK_AVAILABILITY_UPDATERS).sendToTarget();
+        return false;
+    }
+
+    /** @hide */
+    @Override
+    public void onDestroy() {
+        mHandlerThread.quit();
+    }
+
+    /**
+     * A wrapper around IQualifiedNetworksService that forwards calls to implementations of
+     * {@link QualifiedNetworksService}.
+     */
+    private class IQualifiedNetworksServiceWrapper extends IQualifiedNetworksService.Stub {
+        @Override
+        public void createNetworkAvailabilityUpdater(int slotIndex,
+                                                     IQualifiedNetworksServiceCallback callback) {
+            mHandler.obtainMessage(QNS_CREATE_NETWORK_AVAILABILITY_UPDATER, slotIndex, 0,
+                    callback).sendToTarget();
+        }
+
+        @Override
+        public void removeNetworkAvailabilityUpdater(int slotIndex) {
+            mHandler.obtainMessage(QNS_REMOVE_NETWORK_AVAILABILITY_UPDATER, slotIndex, 0)
+                    .sendToTarget();
+        }
+    }
+
+    private void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(TAG, s);
+    }
+}
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index 861efd5..69392d6 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -474,9 +474,9 @@
                 if (value.dataType == Res_value::TYPE_NULL) {
                     printf("=(null)");
                 } else if (value.dataType == Res_value::TYPE_REFERENCE) {
-                    printf("=@0x%x", (int)value.data);
+                    printf("=@0x%08x", (int)value.data);
                 } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
-                    printf("=?0x%x", (int)value.data);
+                    printf("=?0x%08x", (int)value.data);
                 } else if (value.dataType == Res_value::TYPE_STRING) {
                     printf("=\"%s\"",
                             ResTable::normalizeForOutput(String8(block->getAttributeStringValue(i,