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) {
}
}