Merge changes I21b77905,Ia98ec539

* changes:
  VolumeShaper: Use VolumeAutomation interface and unhide
  VolumeShaper: Remove PlayerProxy constructor
diff --git a/api/current.txt b/api/current.txt
index a57882a..d53a4b8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12576,6 +12576,7 @@
     method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
     method public static final android.graphics.ColorSpace.Adaptation[] values();
     enum_constant public static final android.graphics.ColorSpace.Adaptation BRADFORD;
+    enum_constant public static final android.graphics.ColorSpace.Adaptation CIECAT02;
     enum_constant public static final android.graphics.ColorSpace.Adaptation VON_KRIES;
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index f78d2f4..e5de7dc 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -13212,6 +13212,7 @@
     method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
     method public static final android.graphics.ColorSpace.Adaptation[] values();
     enum_constant public static final android.graphics.ColorSpace.Adaptation BRADFORD;
+    enum_constant public static final android.graphics.ColorSpace.Adaptation CIECAT02;
     enum_constant public static final android.graphics.ColorSpace.Adaptation VON_KRIES;
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 91497be..cff6ffe 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -12613,6 +12613,7 @@
     method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
     method public static final android.graphics.ColorSpace.Adaptation[] values();
     enum_constant public static final android.graphics.ColorSpace.Adaptation BRADFORD;
+    enum_constant public static final android.graphics.ColorSpace.Adaptation CIECAT02;
     enum_constant public static final android.graphics.ColorSpace.Adaptation VON_KRIES;
   }
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index dd3b054..ade03e1 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2183,6 +2183,11 @@
     }
 
     void onTouchUpEvent(MotionEvent event) {
+        if (getSelectionActionModeHelper().resetOriginalSelection(
+                getTextView().getOffsetForPosition(event.getX(), event.getY()))) {
+            return;
+        }
+
         boolean selectAllGotFocus = mSelectAllOnFocus && mTextView.didTouchFocusSelect();
         hideCursorAndSpanControllers();
         stopTextActionMode();
@@ -3916,7 +3921,7 @@
         @Override
         public void onDestroyActionMode(ActionMode mode) {
             // Clear mTextActionMode not to recursively destroy action mode by clearing selection.
-            getSelectionActionModeHelper().cancelAsyncTask();
+            getSelectionActionModeHelper().onDestroyActionMode();
             mTextActionMode = null;
             Callback customCallback = getCustomCallback();
             if (customCallback != null) {
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 770d9ee..6790532 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -54,6 +54,8 @@
     private TextClassificationResult mTextClassificationResult;
     private AsyncTask mTextClassificationAsyncTask;
 
+    private final SelectionInfo mSelectionInfo = new SelectionInfo();
+
     SelectionActionModeHelper(@NonNull Editor editor) {
         mEditor = Preconditions.checkNotNull(editor);
         final TextView textView = mEditor.getTextView();
@@ -94,12 +96,12 @@
         }
     }
 
-    public void cancelAsyncTask() {
-        if (mTextClassificationAsyncTask != null) {
-            mTextClassificationAsyncTask.cancel(true);
-            mTextClassificationAsyncTask = null;
+    public boolean resetOriginalSelection(int textIndex) {
+        if (mSelectionInfo.resetOriginalSelection(textIndex, mEditor.getTextView().getText())) {
+            invalidateActionModeAsync();
+            return true;
         }
-        mTextClassificationResult = null;
+        return false;
     }
 
     @Nullable
@@ -107,12 +109,28 @@
         return mTextClassificationResult;
     }
 
+    public void onDestroyActionMode() {
+        mSelectionInfo.onSelectionDestroyed();
+        cancelAsyncTask();
+    }
+
+    private void cancelAsyncTask() {
+        if (mTextClassificationAsyncTask != null) {
+            mTextClassificationAsyncTask.cancel(true);
+            mTextClassificationAsyncTask = null;
+        }
+        mTextClassificationResult = null;
+    }
+
     private boolean isNoOpTextClassifier() {
         return mEditor.getTextView().getTextClassifier() == TextClassifier.NO_OP;
     }
 
     private void startActionMode(@Nullable SelectionResult result) {
-        final CharSequence text = mEditor.getTextView().getText();
+        final TextView textView = mEditor.getTextView();
+        final CharSequence text = textView.getText();
+        mSelectionInfo.setOriginalSelection(
+                textView.getSelectionStart(), textView.getSelectionEnd());
         if (result != null && text instanceof Spannable) {
             Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
             mTextClassificationResult = result.mResult;
@@ -124,6 +142,9 @@
             if (controller != null) {
                 controller.show();
             }
+            if (result != null) {
+                mSelectionInfo.onSelectionStarted(result.mStart, result.mEnd);
+            }
         }
         mEditor.setRestartActionModeOnNextRefresh(false);
         mTextClassificationAsyncTask = null;
@@ -135,6 +156,8 @@
         if (actionMode != null) {
             actionMode.invalidate();
         }
+        final TextView textView = mEditor.getTextView();
+        mSelectionInfo.onSelectionUpdated(textView.getSelectionStart(), textView.getSelectionEnd());
         mTextClassificationAsyncTask = null;
     }
 
@@ -145,6 +168,56 @@
     }
 
     /**
+     * Holds information about the selection and uses it to decide on whether or not to update
+     * the selection when resetOriginalSelection is called.
+     * The expected UX here is to allow the user to re-snap the selection back to the original word
+     * that was selected with one tap on that word.
+     */
+    private static final class SelectionInfo {
+
+        private int mOriginalStart;
+        private int mOriginalEnd;
+        private int mSelectionStart;
+        private int mSelectionEnd;
+
+        private boolean mResetOriginal;
+
+        public void setOriginalSelection(int selectionStart, int selectionEnd) {
+            mOriginalStart = selectionStart;
+            mOriginalEnd = selectionEnd;
+            mResetOriginal = false;
+        }
+
+        public void onSelectionStarted(int selectionStart, int selectionEnd) {
+            // Set the reset flag to true if the selection changed.
+            mSelectionStart = selectionStart;
+            mSelectionEnd = selectionEnd;
+            mResetOriginal = mSelectionStart != mOriginalStart || mSelectionEnd != mOriginalEnd;
+        }
+
+        public void onSelectionUpdated(int selectionStart, int selectionEnd) {
+            // If the selection did not change, maintain the reset state. Otherwise, disable reset.
+            mResetOriginal &= selectionStart == mSelectionStart && selectionEnd == mSelectionEnd;
+        }
+
+        public void onSelectionDestroyed() {
+            mResetOriginal = false;
+        }
+
+        public boolean resetOriginalSelection(int textIndex, CharSequence text) {
+            if (mResetOriginal
+                    && textIndex >= mOriginalStart && textIndex <= mOriginalEnd
+                    && text instanceof Spannable) {
+                Selection.setSelection((Spannable) text, mOriginalStart, mOriginalEnd);
+                // Only allow a reset once.
+                mResetOriginal = false;
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
      * AsyncTask for running a query on a background thread and returning the result on the
      * UiThread. The AsyncTask times out after a specified time, returning a null result if the
      * query has not yet returned.
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
new file mode 100644
index 0000000..3ac5a72
--- /dev/null
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -0,0 +1,487 @@
+/*
+ * 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.internal.content;
+
+import android.annotation.CallSuper;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.FileObserver;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
+ * files.
+ */
+public abstract class FileSystemProvider extends DocumentsProvider {
+
+    private static final String TAG = "FileSystemProvider";
+
+    private static final boolean LOG_INOTIFY = false;
+
+    private String[] mDefaultProjection;
+
+    @GuardedBy("mObservers")
+    private final ArrayMap<File, DirectoryObserver> mObservers = new ArrayMap<>();
+
+    private Handler mHandler;
+
+    protected abstract File getFileForDocId(String docId, boolean visible)
+            throws FileNotFoundException;
+
+    protected abstract String getDocIdForFile(File file) throws FileNotFoundException;
+
+    protected abstract Uri buildNotificationUri(String docId);
+
+    @Override
+    public boolean onCreate() {
+        throw new UnsupportedOperationException(
+                "Subclass should override this and call onCreate(defaultDocumentProjection)");
+    }
+
+    @CallSuper
+    protected void onCreate(String[] defaultProjection) {
+        mHandler = new Handler();
+        mDefaultProjection = defaultProjection;
+    }
+
+    @Override
+    public boolean isChildDocument(String parentDocId, String docId) {
+        try {
+            final File parent = getFileForDocId(parentDocId).getCanonicalFile();
+            final File doc = getFileForDocId(docId).getCanonicalFile();
+            return FileUtils.contains(parent, doc);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(
+                    "Failed to determine if " + docId + " is child of " + parentDocId + ": " + e);
+        }
+    }
+
+    protected final List<String> findDocumentPath(File parent, File doc)
+            throws FileNotFoundException {
+
+        if (!doc.exists()) {
+            throw new FileNotFoundException(doc + " is not found.");
+        }
+
+        if (!FileUtils.contains(parent, doc)) {
+            throw new FileNotFoundException(doc + " is not found under " + parent);
+        }
+
+        LinkedList<String> path = new LinkedList<>();
+        while (doc != null && FileUtils.contains(parent, doc)) {
+            path.addFirst(getDocIdForFile(doc));
+
+            doc = doc.getParentFile();
+        }
+
+        return path;
+    }
+
+    @Override
+    public String createDocument(String docId, String mimeType, String displayName)
+            throws FileNotFoundException {
+        displayName = FileUtils.buildValidFatFilename(displayName);
+
+        final File parent = getFileForDocId(docId);
+        if (!parent.isDirectory()) {
+            throw new IllegalArgumentException("Parent document isn't a directory");
+        }
+
+        final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+            if (!file.mkdir()) {
+                throw new IllegalStateException("Failed to mkdir " + file);
+            }
+        } else {
+            try {
+                if (!file.createNewFile()) {
+                    throw new IllegalStateException("Failed to touch " + file);
+                }
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to touch " + file + ": " + e);
+            }
+        }
+
+        return getDocIdForFile(file);
+    }
+
+    @Override
+    public String renameDocument(String docId, String displayName) throws FileNotFoundException {
+        // Since this provider treats renames as generating a completely new
+        // docId, we're okay with letting the MIME type change.
+        displayName = FileUtils.buildValidFatFilename(displayName);
+
+        final File before = getFileForDocId(docId);
+        final File after = FileUtils.buildUniqueFile(before.getParentFile(), displayName);
+        final File visibleFileBefore = getFileForDocId(docId, true);
+        if (!before.renameTo(after)) {
+            throw new IllegalStateException("Failed to rename to " + after);
+        }
+        removeFromMediaStore(visibleFileBefore);
+
+        final String afterDocId = getDocIdForFile(after);
+        scanFile(getFileForDocId(afterDocId, true));
+
+        if (!TextUtils.equals(docId, afterDocId)) {
+            return afterDocId;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void deleteDocument(String docId) throws FileNotFoundException {
+        final File file = getFileForDocId(docId);
+        final File visibleFile = getFileForDocId(docId, true);
+
+        final boolean isDirectory = file.isDirectory();
+        if (isDirectory) {
+            FileUtils.deleteContents(file);
+        }
+        if (!file.delete()) {
+            throw new IllegalStateException("Failed to delete " + file);
+        }
+
+        removeFromMediaStore(visibleFile);
+    }
+
+    @Override
+    public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
+            String targetParentDocumentId)
+            throws FileNotFoundException {
+        final File before = getFileForDocId(sourceDocumentId);
+        final File after = new File(getFileForDocId(targetParentDocumentId), before.getName());
+        final File visibleFileBefore = getFileForDocId(sourceDocumentId, true);
+
+        if (after.exists()) {
+            throw new IllegalStateException("Already exists " + after);
+        }
+        if (!before.renameTo(after)) {
+            throw new IllegalStateException("Failed to move to " + after);
+        }
+
+        // Notify media store to update its content
+        removeFromMediaStore(visibleFileBefore);
+        final String docId = getDocIdForFile(after);
+        scanFile(getFileForDocId(docId, true));
+
+        return docId;
+    }
+
+    private void removeFromMediaStore(File visibleFile) throws FileNotFoundException {
+        if (visibleFile != null) {
+            final ContentResolver resolver = getContext().getContentResolver();
+            final Uri externalUri = MediaStore.Files.getContentUri("external");
+
+            // Remove media store entries for any files inside this directory, using
+            // path prefix match. Logic borrowed from MtpDatabase.
+            if (visibleFile.isDirectory()) {
+                final String path = visibleFile.getAbsolutePath() + "/";
+                resolver.delete(externalUri,
+                        "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
+                        new String[] { path + "%", Integer.toString(path.length()), path });
+            }
+
+            // Remove media store entry for this exact file.
+            final String path = visibleFile.getAbsolutePath();
+            resolver.delete(externalUri,
+                    "_data LIKE ?1 AND lower(_data)=lower(?2)",
+                    new String[] { path, path });
+        }
+    }
+
+    @Override
+    public Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
+        includeFile(result, documentId, null);
+        return result;
+    }
+
+    @Override
+    public Cursor queryChildDocuments(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException {
+
+        final File parent = getFileForDocId(parentDocumentId);
+        final MatrixCursor result = new DirectoryCursor(
+                resolveProjection(projection), parentDocumentId, parent);
+        for (File file : parent.listFiles()) {
+            includeFile(result, null, file);
+        }
+        return result;
+    }
+
+    /**
+     * Searches documents under the given folder.
+     *
+     * To avoid runtime explosion only returns the at most 23 items.
+     *
+     * @param folder the root folder where recursive search begins
+     * @param query the search condition used to match file names
+     * @param projection projection of the returned cursor
+     * @param exclusion absolute file paths to exclude from result
+     * @return cursor containing search result
+     * @throws FileNotFoundException when root folder doesn't exist or search fails
+     */
+    protected final Cursor querySearchDocuments(
+            File folder, String query, String[] projection, Set<String> exclusion)
+            throws FileNotFoundException {
+
+        query = query.toLowerCase();
+        final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
+        final LinkedList<File> pending = new LinkedList<>();
+        pending.add(folder);
+        while (!pending.isEmpty() && result.getCount() < 24) {
+            final File file = pending.removeFirst();
+            if (file.isDirectory()) {
+                for (File child : file.listFiles()) {
+                    pending.add(child);
+                }
+            }
+            if (file.getName().toLowerCase().contains(query)
+                    && !exclusion.contains(file.getAbsolutePath())) {
+                includeFile(result, null, file);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        final File file = getFileForDocId(documentId);
+        return getTypeForFile(file);
+    }
+
+    @Override
+    public ParcelFileDescriptor openDocument(
+            String documentId, String mode, CancellationSignal signal)
+            throws FileNotFoundException {
+        final File file = getFileForDocId(documentId);
+        final File visibleFile = getFileForDocId(documentId, true);
+
+        final int pfdMode = ParcelFileDescriptor.parseMode(mode);
+        if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
+            return ParcelFileDescriptor.open(file, pfdMode);
+        } else {
+            try {
+                // When finished writing, kick off media scanner
+                return ParcelFileDescriptor.open(
+                        file, pfdMode, mHandler, (IOException e) -> scanFile(visibleFile));
+            } catch (IOException e) {
+                throw new FileNotFoundException("Failed to open for writing: " + e);
+            }
+        }
+    }
+
+    private void scanFile(File visibleFile) {
+        final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+        intent.setData(Uri.fromFile(visibleFile));
+        getContext().sendBroadcast(intent);
+    }
+
+    @Override
+    public AssetFileDescriptor openDocumentThumbnail(
+            String documentId, Point sizeHint, CancellationSignal signal)
+            throws FileNotFoundException {
+        final File file = getFileForDocId(documentId);
+        return DocumentsContract.openImageThumbnail(file);
+    }
+
+    protected RowBuilder includeFile(MatrixCursor result, String docId, File file)
+            throws FileNotFoundException {
+        if (docId == null) {
+            docId = getDocIdForFile(file);
+        } else {
+            file = getFileForDocId(docId);
+        }
+
+        int flags = 0;
+
+        if (file.canWrite()) {
+            if (file.isDirectory()) {
+                flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
+                flags |= Document.FLAG_SUPPORTS_DELETE;
+                flags |= Document.FLAG_SUPPORTS_RENAME;
+                flags |= Document.FLAG_SUPPORTS_MOVE;
+            } else {
+                flags |= Document.FLAG_SUPPORTS_WRITE;
+                flags |= Document.FLAG_SUPPORTS_DELETE;
+                flags |= Document.FLAG_SUPPORTS_RENAME;
+                flags |= Document.FLAG_SUPPORTS_MOVE;
+            }
+        }
+
+        final String mimeType = getTypeForFile(file);
+        final String displayName = file.getName();
+        if (mimeType.startsWith("image/")) {
+            flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
+        }
+
+        final RowBuilder row = result.newRow();
+        row.add(Document.COLUMN_DOCUMENT_ID, docId);
+        row.add(Document.COLUMN_DISPLAY_NAME, displayName);
+        row.add(Document.COLUMN_SIZE, file.length());
+        row.add(Document.COLUMN_MIME_TYPE, mimeType);
+        row.add(Document.COLUMN_FLAGS, flags);
+
+        // Only publish dates reasonably after epoch
+        long lastModified = file.lastModified();
+        if (lastModified > 31536000000L) {
+            row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
+        }
+
+        // Return the row builder just in case any subclass want to add more stuff to it.
+        return row;
+    }
+
+    private static String getTypeForFile(File file) {
+        if (file.isDirectory()) {
+            return Document.MIME_TYPE_DIR;
+        } else {
+            return getTypeForName(file.getName());
+        }
+    }
+
+    private static String getTypeForName(String name) {
+        final int lastDot = name.lastIndexOf('.');
+        if (lastDot >= 0) {
+            final String extension = name.substring(lastDot + 1).toLowerCase();
+            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+            if (mime != null) {
+                return mime;
+            }
+        }
+
+        return "application/octet-stream";
+    }
+
+    protected final File getFileForDocId(String docId) throws FileNotFoundException {
+        return getFileForDocId(docId, false);
+    }
+
+    private String[] resolveProjection(String[] projection) {
+        return projection == null ? mDefaultProjection : projection;
+    }
+
+    private void startObserving(File file, Uri notifyUri) {
+        synchronized (mObservers) {
+            DirectoryObserver observer = mObservers.get(file);
+            if (observer == null) {
+                observer = new DirectoryObserver(
+                        file, getContext().getContentResolver(), notifyUri);
+                observer.startWatching();
+                mObservers.put(file, observer);
+            }
+            observer.mRefCount++;
+
+            if (LOG_INOTIFY) Log.d(TAG, "after start: " + observer);
+        }
+    }
+
+    private void stopObserving(File file) {
+        synchronized (mObservers) {
+            DirectoryObserver observer = mObservers.get(file);
+            if (observer == null) return;
+
+            observer.mRefCount--;
+            if (observer.mRefCount == 0) {
+                mObservers.remove(file);
+                observer.stopWatching();
+            }
+
+            if (LOG_INOTIFY) Log.d(TAG, "after stop: " + observer);
+        }
+    }
+
+    private static class DirectoryObserver extends FileObserver {
+        private static final int NOTIFY_EVENTS = ATTRIB | CLOSE_WRITE | MOVED_FROM | MOVED_TO
+                | CREATE | DELETE | DELETE_SELF | MOVE_SELF;
+
+        private final File mFile;
+        private final ContentResolver mResolver;
+        private final Uri mNotifyUri;
+
+        private int mRefCount = 0;
+
+        public DirectoryObserver(File file, ContentResolver resolver, Uri notifyUri) {
+            super(file.getAbsolutePath(), NOTIFY_EVENTS);
+            mFile = file;
+            mResolver = resolver;
+            mNotifyUri = notifyUri;
+        }
+
+        @Override
+        public void onEvent(int event, String path) {
+            if ((event & NOTIFY_EVENTS) != 0) {
+                if (LOG_INOTIFY) Log.d(TAG, "onEvent() " + event + " at " + path);
+                mResolver.notifyChange(mNotifyUri, null, false);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "DirectoryObserver{file=" + mFile.getAbsolutePath() + ", ref=" + mRefCount + "}";
+        }
+    }
+
+    private class DirectoryCursor extends MatrixCursor {
+        private final File mFile;
+
+        public DirectoryCursor(String[] columnNames, String docId, File file) {
+            super(columnNames);
+
+            final Uri notifyUri = buildNotificationUri(docId);
+            setNotificationUri(getContext().getContentResolver(), notifyUri);
+
+            mFile = file;
+            startObserving(mFile, notifyUri);
+        }
+
+        @Override
+        public void close() {
+            super.close();
+            stopObserving(mFile);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 43005e6..919cf99 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -115,7 +115,6 @@
         setEnabled(itemData.isEnabled());
         setSubMenuArrowVisible(itemData.hasSubMenu());
         setContentDescription(itemData.getContentDescription());
-        setTooltipText(itemData.getTooltipText());
     }
 
     public void setForceShowIcon(boolean forceShow) {
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 817b186..79b0cd1 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -1195,6 +1195,8 @@
                     }
 
                     setButtonTagAndClickListener(menuItemButton, menuItem);
+                    // Set tooltips for main panel items, but not overflow items (b/35726766).
+                    menuItemButton.setTooltipText(menuItem.getTooltipText());
                     mMainPanel.addView(menuItemButton);
                     final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
                     params.width = menuItemButtonWidth + extraPadding / 2;
@@ -1635,7 +1637,6 @@
                 buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
             }
         }
-        menuItemButton.setTooltipText(menuItem.getTooltipText());
         final CharSequence contentDescription = menuItem.getContentDescription();
         if (TextUtils.isEmpty(contentDescription)) {
             menuItemButton.setContentDescription(menuItem.getTitle());
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index ec00c45..b1a433c 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -805,7 +805,8 @@
      */
     public enum Adaptation {
         /**
-         * Bradford matrix for the von Kries chromatic adaptation transform.
+         * Bradford chromatic adaptation transform, as defined in the
+         * CIECAM97s color appearance model.
          */
         BRADFORD(new float[] {
                  0.8951f, -0.7502f,  0.0389f,
@@ -813,12 +814,21 @@
                 -0.1614f,  0.0367f,  1.0296f
         }),
         /**
-         * von Kries matrix for the von Kries chromatic adaptation transform.
+         * von Kries chromatic adaptation transform.
          */
         VON_KRIES(new float[] {
                  0.40024f, -0.22630f, 0.00000f,
                  0.70760f,  1.16532f, 0.00000f,
                 -0.08081f,  0.04570f, 0.91822f
+        }),
+        /**
+         * CIECAT02 chromatic adaption transform, as defined in the
+         * CIECAM02 color appearance model.
+         */
+        CIECAT02(new float[] {
+                 0.7328f, -0.7036f,  0.0030f,
+                 0.4296f,  1.6975f,  0.0136f,
+                -0.1624f,  0.0061f,  0.9834f
         });
 
         final float[] mTransform;
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 22a5b7f..3cc9f65e 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -56,6 +56,7 @@
 import android.webkit.MimeTypeMap;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.FileSystemProvider;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.File;
@@ -63,15 +64,15 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 
-public class ExternalStorageProvider extends DocumentsProvider {
+public class ExternalStorageProvider extends FileSystemProvider {
     private static final String TAG = "ExternalStorage";
 
     private static final boolean DEBUG = false;
-    private static final boolean LOG_INOTIFY = false;
 
     public static final String AUTHORITY = "com.android.externalstorage.documents";
 
@@ -105,20 +106,17 @@
     private static final String ROOT_ID_HOME = "home";
 
     private StorageManager mStorageManager;
-    private Handler mHandler;
 
     private final Object mRootsLock = new Object();
 
     @GuardedBy("mRootsLock")
     private ArrayMap<String, RootInfo> mRoots = new ArrayMap<>();
 
-    @GuardedBy("mObservers")
-    private ArrayMap<File, DirectoryObserver> mObservers = new ArrayMap<>();
-
     @Override
     public boolean onCreate() {
+        super.onCreate(DEFAULT_DOCUMENT_PROJECTION);
+
         mStorageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
-        mHandler = new Handler();
 
         updateVolumes();
         return true;
@@ -274,11 +272,8 @@
         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     }
 
-    private static String[] resolveDocumentProjection(String[] projection) {
-        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
-    }
-
-    private String getDocIdForFile(File file) throws FileNotFoundException {
+    @Override
+    protected String getDocIdForFile(File file) throws FileNotFoundException {
         return getDocIdForFileMaybeCreate(file, false);
     }
 
@@ -344,11 +339,8 @@
         return mostSpecificRoot;
     }
 
-    private File getFileForDocId(String docId) throws FileNotFoundException {
-        return getFileForDocId(docId, false);
-    }
-
-    private File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
+    @Override
+    protected File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
         return buildFile(root, docId, visible);
     }
@@ -393,48 +385,9 @@
         return target;
     }
 
-    private void includeFile(MatrixCursor result, String docId, File file)
-            throws FileNotFoundException {
-        if (docId == null) {
-            docId = getDocIdForFile(file);
-        } else {
-            file = getFileForDocId(docId);
-        }
-
-        int flags = 0;
-
-        if (file.canWrite()) {
-            if (file.isDirectory()) {
-                flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
-                flags |= Document.FLAG_SUPPORTS_DELETE;
-                flags |= Document.FLAG_SUPPORTS_RENAME;
-                flags |= Document.FLAG_SUPPORTS_MOVE;
-            } else {
-                flags |= Document.FLAG_SUPPORTS_WRITE;
-                flags |= Document.FLAG_SUPPORTS_DELETE;
-                flags |= Document.FLAG_SUPPORTS_RENAME;
-                flags |= Document.FLAG_SUPPORTS_MOVE;
-            }
-        }
-
-        final String mimeType = getTypeForFile(file);
-        final String displayName = file.getName();
-        if (mimeType.startsWith("image/")) {
-            flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
-        }
-
-        final RowBuilder row = result.newRow();
-        row.add(Document.COLUMN_DOCUMENT_ID, docId);
-        row.add(Document.COLUMN_DISPLAY_NAME, displayName);
-        row.add(Document.COLUMN_SIZE, file.length());
-        row.add(Document.COLUMN_MIME_TYPE, mimeType);
-        row.add(Document.COLUMN_FLAGS, flags);
-
-        // Only publish dates reasonably after epoch
-        long lastModified = file.lastModified();
-        if (lastModified > 31536000000L) {
-            row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
-        }
+    @Override
+    protected Uri buildNotificationUri(String docId) {
+        return DocumentsContract.buildChildDocumentsUri(AUTHORITY, docId);
     }
 
     @Override
@@ -455,22 +408,8 @@
     }
 
     @Override
-    public boolean isChildDocument(String parentDocId, String docId) {
-        try {
-            final File parent = getFileForDocId(parentDocId).getCanonicalFile();
-            final File doc = getFileForDocId(docId).getCanonicalFile();
-            return FileUtils.contains(parent, doc);
-        } catch (IOException e) {
-            throw new IllegalArgumentException(
-                    "Failed to determine if " + docId + " is child of " + parentDocId + ": " + e);
-        }
-    }
-
-    @Override
     public Path findDocumentPath(String childDocId, @Nullable String parentDocId)
             throws FileNotFoundException {
-        LinkedList<String> path = new LinkedList<>();
-
         final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
         final RootInfo root = resolvedDocId.first;
         File child = resolvedDocId.second;
@@ -479,49 +418,7 @@
                         ? root.path
                         : getFileForDocId(parentDocId);
 
-        if (!child.exists()) {
-            throw new FileNotFoundException(childDocId + " is not found.");
-        }
-
-        if (!child.getAbsolutePath().startsWith(parent.getAbsolutePath())) {
-            throw new FileNotFoundException(childDocId + " is not found under " + parentDocId);
-        }
-
-        while (child != null && child.getAbsolutePath().startsWith(parent.getAbsolutePath())) {
-            path.addFirst(getDocIdForFile(child));
-
-            child = child.getParentFile();
-        }
-
-        return new Path(parentDocId == null ? root.rootId : null, path);
-    }
-
-    @Override
-    public String createDocument(String docId, String mimeType, String displayName)
-            throws FileNotFoundException {
-        displayName = FileUtils.buildValidFatFilename(displayName);
-
-        final File parent = getFileForDocId(docId);
-        if (!parent.isDirectory()) {
-            throw new IllegalArgumentException("Parent document isn't a directory");
-        }
-
-        final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
-        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
-            if (!file.mkdir()) {
-                throw new IllegalStateException("Failed to mkdir " + file);
-            }
-        } else {
-            try {
-                if (!file.createNewFile()) {
-                    throw new IllegalStateException("Failed to touch " + file);
-                }
-            } catch (IOException e) {
-                throw new IllegalStateException("Failed to touch " + file + ": " + e);
-            }
-        }
-
-        return getDocIdForFile(file);
+        return new Path(parentDocId == null ? root.rootId : null, findDocumentPath(parent, child));
     }
 
     private Uri getDocumentUri(String path, List<UriPermission> accessUriPermissions)
@@ -587,120 +484,14 @@
     }
 
     @Override
-    public String renameDocument(String docId, String displayName) throws FileNotFoundException {
-        // Since this provider treats renames as generating a completely new
-        // docId, we're okay with letting the MIME type change.
-        displayName = FileUtils.buildValidFatFilename(displayName);
-
-        final File before = getFileForDocId(docId);
-        final File after = FileUtils.buildUniqueFile(before.getParentFile(), displayName);
-        if (!before.renameTo(after)) {
-            throw new IllegalStateException("Failed to rename to " + after);
-        }
-        final String afterDocId = getDocIdForFile(after);
-        if (!TextUtils.equals(docId, afterDocId)) {
-            return afterDocId;
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public void deleteDocument(String docId) throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
-        final File visibleFile = getFileForDocId(docId, true);
-
-        final boolean isDirectory = file.isDirectory();
-        if (isDirectory) {
-            FileUtils.deleteContents(file);
-        }
-        if (!file.delete()) {
-            throw new IllegalStateException("Failed to delete " + file);
-        }
-
-        if (visibleFile != null) {
-            final ContentResolver resolver = getContext().getContentResolver();
-            final Uri externalUri = MediaStore.Files.getContentUri("external");
-
-            // Remove media store entries for any files inside this directory, using
-            // path prefix match. Logic borrowed from MtpDatabase.
-            if (isDirectory) {
-                final String path = visibleFile.getAbsolutePath() + "/";
-                resolver.delete(externalUri,
-                        "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
-                        new String[] { path + "%", Integer.toString(path.length()), path });
-            }
-
-            // Remove media store entry for this exact file.
-            final String path = visibleFile.getAbsolutePath();
-            resolver.delete(externalUri,
-                    "_data LIKE ?1 AND lower(_data)=lower(?2)",
-                    new String[] { path, path });
-        }
-    }
-
-    @Override
-    public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
-            String targetParentDocumentId)
-            throws FileNotFoundException {
-        final File before = getFileForDocId(sourceDocumentId);
-        final File after = new File(getFileForDocId(targetParentDocumentId), before.getName());
-
-        if (after.exists()) {
-            throw new IllegalStateException("Already exists " + after);
-        }
-        if (!before.renameTo(after)) {
-            throw new IllegalStateException("Failed to move to " + after);
-        }
-        return getDocIdForFile(after);
-    }
-
-    @Override
-    public Cursor queryDocument(String documentId, String[] projection)
-            throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
-        includeFile(result, documentId, null);
-        return result;
-    }
-
-    @Override
-    public Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        final File parent = getFileForDocId(parentDocumentId);
-        final MatrixCursor result = new DirectoryCursor(
-                resolveDocumentProjection(projection), parentDocumentId, parent);
-        for (File file : parent.listFiles()) {
-            includeFile(result, null, file);
-        }
-        return result;
-    }
-
-    @Override
     public Cursor querySearchDocuments(String rootId, String query, String[] projection)
             throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
-
-        query = query.toLowerCase();
         final File parent;
         synchronized (mRootsLock) {
             parent = mRoots.get(rootId).path;
         }
 
-        final LinkedList<File> pending = new LinkedList<>();
-        pending.add(parent);
-        while (!pending.isEmpty() && result.getCount() < 24) {
-            final File file = pending.removeFirst();
-            if (file.isDirectory()) {
-                for (File child : file.listFiles()) {
-                    pending.add(child);
-                }
-            }
-            if (file.getName().toLowerCase().contains(query)) {
-                includeFile(result, null, file);
-            }
-        }
-        return result;
+        return querySearchDocuments(parent, query, projection, Collections.emptySet());
     }
 
     @Override
@@ -722,48 +513,6 @@
     }
 
     @Override
-    public String getDocumentType(String documentId) throws FileNotFoundException {
-        final File file = getFileForDocId(documentId);
-        return getTypeForFile(file);
-    }
-
-    @Override
-    public ParcelFileDescriptor openDocument(
-            String documentId, String mode, CancellationSignal signal)
-            throws FileNotFoundException {
-        final File file = getFileForDocId(documentId);
-        final File visibleFile = getFileForDocId(documentId, true);
-
-        final int pfdMode = ParcelFileDescriptor.parseMode(mode);
-        if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
-            return ParcelFileDescriptor.open(file, pfdMode);
-        } else {
-            try {
-                // When finished writing, kick off media scanner
-                return ParcelFileDescriptor.open(file, pfdMode, mHandler, new OnCloseListener() {
-                    @Override
-                    public void onClose(IOException e) {
-                        final Intent intent = new Intent(
-                                Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
-                        intent.setData(Uri.fromFile(visibleFile));
-                        getContext().sendBroadcast(intent);
-                    }
-                });
-            } catch (IOException e) {
-                throw new FileNotFoundException("Failed to open for writing: " + e);
-            }
-        }
-    }
-
-    @Override
-    public AssetFileDescriptor openDocumentThumbnail(
-            String documentId, Point sizeHint, CancellationSignal signal)
-            throws FileNotFoundException {
-        final File file = getFileForDocId(documentId);
-        return DocumentsContract.openImageThumbnail(file);
-    }
-
-    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
         synchronized (mRootsLock) {
@@ -826,107 +575,4 @@
         }
         return bundle;
     }
-
-    private static String getTypeForFile(File file) {
-        if (file.isDirectory()) {
-            return Document.MIME_TYPE_DIR;
-        } else {
-            return getTypeForName(file.getName());
-        }
-    }
-
-    private static String getTypeForName(String name) {
-        final int lastDot = name.lastIndexOf('.');
-        if (lastDot >= 0) {
-            final String extension = name.substring(lastDot + 1).toLowerCase();
-            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
-            if (mime != null) {
-                return mime;
-            }
-        }
-
-        return "application/octet-stream";
-    }
-
-    private void startObserving(File file, Uri notifyUri) {
-        synchronized (mObservers) {
-            DirectoryObserver observer = mObservers.get(file);
-            if (observer == null) {
-                observer = new DirectoryObserver(
-                        file, getContext().getContentResolver(), notifyUri);
-                observer.startWatching();
-                mObservers.put(file, observer);
-            }
-            observer.mRefCount++;
-
-            if (LOG_INOTIFY) Log.d(TAG, "after start: " + observer);
-        }
-    }
-
-    private void stopObserving(File file) {
-        synchronized (mObservers) {
-            DirectoryObserver observer = mObservers.get(file);
-            if (observer == null) return;
-
-            observer.mRefCount--;
-            if (observer.mRefCount == 0) {
-                mObservers.remove(file);
-                observer.stopWatching();
-            }
-
-            if (LOG_INOTIFY) Log.d(TAG, "after stop: " + observer);
-        }
-    }
-
-    private static class DirectoryObserver extends FileObserver {
-        private static final int NOTIFY_EVENTS = ATTRIB | CLOSE_WRITE | MOVED_FROM | MOVED_TO
-                | CREATE | DELETE | DELETE_SELF | MOVE_SELF;
-
-        private final File mFile;
-        private final ContentResolver mResolver;
-        private final Uri mNotifyUri;
-
-        private int mRefCount = 0;
-
-        public DirectoryObserver(File file, ContentResolver resolver, Uri notifyUri) {
-            super(file.getAbsolutePath(), NOTIFY_EVENTS);
-            mFile = file;
-            mResolver = resolver;
-            mNotifyUri = notifyUri;
-        }
-
-        @Override
-        public void onEvent(int event, String path) {
-            if ((event & NOTIFY_EVENTS) != 0) {
-                if (LOG_INOTIFY) Log.d(TAG, "onEvent() " + event + " at " + path);
-                mResolver.notifyChange(mNotifyUri, null, false);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "DirectoryObserver{file=" + mFile.getAbsolutePath() + ", ref=" + mRefCount + "}";
-        }
-    }
-
-    private class DirectoryCursor extends MatrixCursor {
-        private final File mFile;
-
-        public DirectoryCursor(String[] columnNames, String docId, File file) {
-            super(columnNames);
-
-            final Uri notifyUri = DocumentsContract.buildChildDocumentsUri(
-                    AUTHORITY, docId);
-            setNotificationUri(getContext().getContentResolver(), notifyUri);
-
-            mFile = file;
-            startObserving(mFile, notifyUri);
-        }
-
-        @Override
-        public void close() {
-            super.close();
-            stopObserving(mFile);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6157f57..17045b4b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18710,7 +18710,7 @@
         Slog.w(TAG, "Shame on you for calling a hidden API. Shame!");
         try {
             observer.onGetStatsCompleted(null, false);
-        } catch (RemoteException ignored) {
+        } catch (Throwable ignored) {
         }
     }