Merge "ExifInterface: add RAW input stream support" into nyc-dev
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 406dc2b..6959137 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2395,17 +2395,22 @@
dragSourceEnd += shift;
}
- // Delete original selection
- mTextView.deleteText_internal(dragSourceStart, dragSourceEnd);
+ mUndoInputFilter.setForceMerge(true);
+ try {
+ // Delete original selection
+ mTextView.deleteText_internal(dragSourceStart, dragSourceEnd);
- // Make sure we do not leave two adjacent spaces.
- final int prevCharIdx = Math.max(0, dragSourceStart - 1);
- final int nextCharIdx = Math.min(mTextView.getText().length(), dragSourceStart + 1);
- if (nextCharIdx > prevCharIdx + 1) {
- CharSequence t = mTextView.getTransformedText(prevCharIdx, nextCharIdx);
- if (Character.isSpaceChar(t.charAt(0)) && Character.isSpaceChar(t.charAt(1))) {
- mTextView.deleteText_internal(prevCharIdx, prevCharIdx + 1);
+ // Make sure we do not leave two adjacent spaces.
+ final int prevCharIdx = Math.max(0, dragSourceStart - 1);
+ final int nextCharIdx = Math.min(mTextView.getText().length(), dragSourceStart + 1);
+ if (nextCharIdx > prevCharIdx + 1) {
+ CharSequence t = mTextView.getTransformedText(prevCharIdx, nextCharIdx);
+ if (Character.isSpaceChar(t.charAt(0)) && Character.isSpaceChar(t.charAt(1))) {
+ mTextView.deleteText_internal(prevCharIdx, prevCharIdx + 1);
+ }
}
+ } finally {
+ mUndoInputFilter.setForceMerge(false);
}
}
}
@@ -5497,6 +5502,9 @@
// rotates the screen during composition.
private boolean mHasComposition;
+ // Whether to merge events into one operation.
+ private boolean mForceMerge;
+
public UndoInputFilter(Editor editor) {
mEditor = editor;
}
@@ -5511,6 +5519,10 @@
mHasComposition = parcel.readInt() != 0;
}
+ public void setForceMerge(boolean forceMerge) {
+ mForceMerge = forceMerge;
+ }
+
/**
* Signals that a user-triggered edit is starting.
*/
@@ -5570,7 +5582,7 @@
// Otherwise the user inserted the composition.
String newText = TextUtils.substring(source, start, end);
EditOperation edit = new EditOperation(mEditor, "", dstart, newText);
- recordEdit(edit, false /* forceMerge */);
+ recordEdit(edit, mForceMerge);
return true;
}
@@ -5584,7 +5596,7 @@
// the initial input filters run (e.g. a credit card formatter that adds spaces to a
// string). This results in multiple filter() calls for what the user considers to be
// a single operation. Always undo the whole set of changes in one step.
- final boolean forceMerge = isInTextWatcher();
+ final boolean forceMerge = mForceMerge || isInTextWatcher();
// Build a new operation with all the information from this edit.
String newText = TextUtils.substring(source, start, end);
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 5dae4a8..844eadb 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -44,6 +44,7 @@
import com.android.frameworks.coretests.R;
+import android.support.test.espresso.action.EspressoKey;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
@@ -172,6 +173,12 @@
onView(withId(R.id.textview)).check(hasSelection(""));
assertNoSelectionHandles();
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
+
+ // Test undo returns to the original state.
+ onView(withId(R.id.textview)).perform(pressKey(
+ (new EspressoKey.Builder()).withCtrlPressed(true).withKeyCode(KeyEvent.KEYCODE_Z)
+ .build()));
+ onView(withId(R.id.textview)).check(matches(withText(text)));
}
@SmallTest
diff --git a/core/tests/systemproperties/Android.mk b/core/tests/systemproperties/Android.mk
index 0c20876..b512396 100644
--- a/core/tests/systemproperties/Android.mk
+++ b/core/tests/systemproperties/Android.mk
@@ -9,7 +9,7 @@
$(call all-java-files-under, src)
LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib
+LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCoreSystemPropertiesTests
LOCAL_JAVA_LANGUAGE_VERSION := 1.8
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 3642155..fe2796c 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -365,7 +365,7 @@
if (options == null) {
throw new IllegalArgumentException("options are null");
}
- subscribeInternal(parentId, options, callback);
+ subscribeInternal(parentId, new Bundle(options), callback);
}
/**
@@ -1116,7 +1116,7 @@
}
}
mCallbacks.add(callback);
- mOptionsList.add(options == null ? null : new Bundle(options));
+ mOptionsList.add(options);
}
public boolean removeCallback(Bundle options) {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 203d6dc..72ad2f6 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -32,6 +32,7 @@
import android.media.MediaFile;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
+import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
@@ -40,8 +41,9 @@
import com.android.internal.util.Preconditions;
import java.io.FileNotFoundException;
-import java.io.IOException;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
/**
* Database for MTP objects.
@@ -606,7 +608,7 @@
* @param deviceId Device to find documents.
* @return Identifier of found document or null.
*/
- public @Nullable Identifier getUnmappedDocumentsParent(int deviceId) {
+ @Nullable Identifier getUnmappedDocumentsParent(int deviceId) {
final String fromClosure =
TABLE_DOCUMENTS + " AS child INNER JOIN " +
TABLE_DOCUMENTS + " AS parent ON " +
@@ -643,6 +645,65 @@
}
}
+ /**
+ * Removes metadata except for data used by outgoingPersistedUriPermissions.
+ */
+ void cleanDatabase(Uri[] outgoingPersistedUris) {
+ mDatabase.beginTransaction();
+ try {
+ final Set<String> ids = new HashSet<>();
+ for (final Uri uri : outgoingPersistedUris) {
+ String documentId = DocumentsContract.getDocumentId(uri);
+ while (documentId != null) {
+ if (ids.contains(documentId)) {
+ break;
+ }
+ ids.add(documentId);
+ try (final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(COLUMN_PARENT_DOCUMENT_ID),
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null)) {
+ documentId = cursor.moveToNext() ? cursor.getString(0) : null;
+ }
+ }
+ }
+ deleteDocumentsAndRoots(
+ Document.COLUMN_DOCUMENT_ID + " NOT IN " + getIdList(ids), null);
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ int getLastBootCount() {
+ try (final Cursor cursor = mDatabase.query(
+ TABLE_LAST_BOOT_COUNT, strings(COLUMN_VALUE), null, null, null, null, null)) {
+ if (cursor.moveToNext()) {
+ return cursor.getInt(0);
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ void setLastBootCount(int value) {
+ Preconditions.checkArgumentNonnegative(value, "Boot count must not be negative.");
+ mDatabase.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(COLUMN_VALUE, value);
+ mDatabase.delete(TABLE_LAST_BOOT_COUNT, null, null);
+ mDatabase.insert(TABLE_LAST_BOOT_COUNT, null, values);
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
private static class OpenHelper extends SQLiteOpenHelper {
public OpenHelper(Context context, int flags) {
super(context,
@@ -655,12 +716,14 @@
public void onCreate(SQLiteDatabase db) {
db.execSQL(QUERY_CREATE_DOCUMENTS);
db.execSQL(QUERY_CREATE_ROOT_EXTRA);
+ db.execSQL(QUERY_CREATE_LAST_BOOT_COUNT);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL("DROP TABLE " + TABLE_DOCUMENTS);
- db.execSQL("DROP TABLE " + TABLE_ROOT_EXTRA);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_DOCUMENTS);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_ROOT_EXTRA);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_LAST_BOOT_COUNT);
onCreate(db);
}
}
@@ -818,4 +881,16 @@
}
return results;
}
+
+ private static String getIdList(Set<String> ids) {
+ String result = "(";
+ for (final String id : ids) {
+ if (result.length() > 1) {
+ result += ",";
+ }
+ result += id;
+ }
+ result += ")";
+ return result;
+ }
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index ff4b89f..6d98e34 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -30,7 +30,7 @@
* Class containing MtpDatabase constants.
*/
class MtpDatabaseConstants {
- static final int DATABASE_VERSION = 4;
+ static final int DATABASE_VERSION = 5;
static final String DATABASE_NAME = "database";
static final int FLAG_DATABASE_IN_MEMORY = 1;
@@ -48,6 +48,11 @@
static final String TABLE_ROOT_EXTRA = "RootExtra";
/**
+ * Table containing last boot count.
+ */
+ static final String TABLE_LAST_BOOT_COUNT = "LastBootCount";
+
+ /**
* 'FROM' closure of joining TABLE_DOCUMENTS and TABLE_ROOT_EXTRA.
*/
static final String JOIN_ROOTS = createJoinFromClosure(
@@ -62,7 +67,13 @@
static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id";
static final String COLUMN_DOCUMENT_TYPE = "document_type";
static final String COLUMN_ROW_STATE = "row_state";
- static final String COLUMN_MAPPING_KEY = "column_mapping_key";
+ static final String COLUMN_MAPPING_KEY = "mapping_key";
+
+ /**
+ * Value for TABLE_LAST_BOOT_COUNT.
+ * Type: INTEGER
+ */
+ static final String COLUMN_VALUE = "value";
/**
* The state represents that the row has a valid object handle.
@@ -133,6 +144,9 @@
Root.COLUMN_CAPACITY_BYTES + " INTEGER," +
Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);";
+ static final String QUERY_CREATE_LAST_BOOT_COUNT =
+ "CREATE TABLE " + TABLE_LAST_BOOT_COUNT + " (value INTEGER NOT NULL);";
+
/**
* Map for columns names to provide DocumentContract.Root compatible columns.
* @see SQLiteQueryBuilder#setProjectionMap(Map)
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 7211253..d4d4591 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -17,6 +17,7 @@
package com.android.mtp;
import android.content.ContentResolver;
+import android.content.UriPermission;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.Cursor;
@@ -25,6 +26,7 @@
import android.media.MediaFile;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
+import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -33,6 +35,8 @@
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -42,6 +46,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -95,6 +100,21 @@
mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
mAppFuse = new AppFuse(TAG, new AppFuseCallback());
mIntentSender = new ServiceIntentSender(getContext());
+
+ // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
+ // after booting.
+ final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1);
+ final int lastBootCount = mDatabase.getLastBootCount();
+ if (bootCount != -1 && bootCount != lastBootCount) {
+ mDatabase.setLastBootCount(bootCount);
+ final List<UriPermission> permissions = mResolver.getOutgoingPersistedUriPermissions();
+ final Uri[] uris = new Uri[permissions.size()];
+ for (int i = 0; i < permissions.size(); i++) {
+ uris[i] = permissions.get(i).getUri();
+ }
+ mDatabase.cleanDatabase(uris);
+ }
+
// TODO: Mount AppFuse on demands.
try {
mAppFuse.mount(getContext().getSystemService(StorageManager.class));
@@ -122,6 +142,7 @@
mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
mAppFuse = new AppFuse(TAG, new AppFuseCallback());
mIntentSender = intentSender;
+
// TODO: Mount AppFuse on demands.
try {
mAppFuse.mount(storageManager);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index f9e8225..e49a935 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -19,6 +19,7 @@
import android.database.Cursor;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
+import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
@@ -26,6 +27,7 @@
import android.test.suitebuilder.annotation.SmallTest;
import java.io.FileNotFoundException;
+import java.util.Arrays;
import static android.provider.DocumentsContract.Document.*;
import static com.android.mtp.MtpDatabase.strings;
@@ -1023,6 +1025,62 @@
assertFalse(mDatabase.getMapper().stopAddingDocuments(null));
}
+ public void testSetBootCount() {
+ assertEquals(0, mDatabase.getLastBootCount());
+ mDatabase.setLastBootCount(10);
+ assertEquals(10, mDatabase.getLastBootCount());
+ try {
+ mDatabase.setLastBootCount(-1);
+ fail();
+ } catch (IllegalArgumentException e) {}
+ }
+
+ public void testCleanDatabase() throws FileNotFoundException {
+ // Add tree.
+ addTestDevice();
+ addTestStorage("1");
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
+ createDocument(100, "apple.txt", MtpConstants.FORMAT_TEXT, 1024),
+ createDocument(101, "orange.txt", MtpConstants.FORMAT_TEXT, 1024),
+ });
+ mDatabase.getMapper().stopAddingDocuments("2");
+
+ // Disconnect the device.
+ mDatabase.getMapper().startAddingDocuments(null);
+ mDatabase.getMapper().stopAddingDocuments(null);
+
+ // Clean database.
+ mDatabase.cleanDatabase(new Uri[] {
+ DocumentsContract.buildDocumentUri(MtpDocumentsProvider.AUTHORITY, "3")
+ });
+
+ // Add tree again.
+ addTestDevice();
+ addTestStorage("1");
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
+ createDocument(100, "apple.txt", MtpConstants.FORMAT_TEXT, 1024),
+ createDocument(101, "orange.txt", MtpConstants.FORMAT_TEXT, 1024),
+ });
+ mDatabase.getMapper().stopAddingDocuments("2");
+
+ try (final Cursor cursor = mDatabase.queryChildDocuments(
+ strings(COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME), "2")) {
+ assertEquals(2, cursor.getCount());
+
+ // Persistent uri uses the same ID.
+ cursor.moveToNext();
+ assertEquals("3", cursor.getString(0));
+ assertEquals("apple.txt", cursor.getString(1));
+
+ // Others does not.
+ cursor.moveToNext();
+ assertEquals("5", cursor.getString(0));
+ assertEquals("orange.txt", cursor.getString(1));
+ }
+ }
+
private void addTestDevice() throws FileNotFoundException {
TestUtil.addTestDevice(mDatabase);
}
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 6a255e5..e27f69e 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -819,7 +819,11 @@
// server-to-server packets, e.g. for relays.
if (!isPacketToOrFromClient(udpSrcPort, udpDstPort) &&
!isPacketServerToServer(udpSrcPort, udpDstPort)) {
- return null;
+ // This should almost never happen because we use SO_ATTACH_FILTER on the packet
+ // socket to drop packets that don't have the right source ports. However, it's
+ // possible that a packet arrives between when the socket is bound and when the
+ // filter is set. http://b/26696823 .
+ throw new ParseException("Unexpected UDP ports %d->%d", udpSrcPort, udpDstPort);
}
}
diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
index 2a967e6..c322ab8 100644
--- a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
@@ -558,6 +558,39 @@
}
@SmallTest
+ public void testUdpInvalidDstPort() throws Exception {
+ final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+ // Ethernet header.
+ "9cd917000000001c2e0000000800" +
+ // IP header.
+ "45a00148000040003d115087d18194fb0a0f7af2" +
+ // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
+ // NOTE: The destination port is a non-DHCP port.
+ "0043aaaa01341268" +
+ // BOOTP header.
+ "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
+ // MAC address.
+ "9cd91700000000000000000000000000" +
+ // Server name.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // File.
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ // Options.
+ "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
+ "d18180060f0777766d2e6564751c040a0fffffff000000"
+ ).toCharArray(), false));
+
+ try {
+ DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
+ fail("Packet with invalid dst port did not throw ParseException");
+ } catch (ParseException expected) {}
+ }
+
+ @SmallTest
public void testMultipleRouters() throws Exception {
final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
// Ethernet header.