Loading src/com/android/documentsui/AbstractActionHandler.java +2 −2 Original line number Diff line number Diff line Loading @@ -497,7 +497,7 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA // First try managing the document; we expect manager to filter // based on authority, so we don't grant. Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT); manage.setData(doc.derivedUri); manage.setData(doc.getDocumentUri()); try { doc.userId.startActivityAsUser(mActivity, manage); return true; Loading Loading @@ -542,7 +542,7 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA protected Intent buildViewIntent(DocumentInfo doc) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(doc.derivedUri, doc.mimeType); intent.setDataAndType(doc.getDocumentUri(), doc.mimeType); // Downloads has traditionally added the WRITE permission // in the TrampolineActivity. Since this behavior is long Loading src/com/android/documentsui/files/QuickViewIntentBuilder.java +38 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.documentsui.files; import static com.android.documentsui.base.DocumentInfo.getCursorInt; import static com.android.documentsui.base.DocumentInfo.getCursorString; import static com.android.documentsui.base.Shared.MAX_DOCS_IN_INTENT; import static com.android.documentsui.base.SharedMinimal.DEBUG; Loading @@ -41,6 +42,7 @@ import com.android.documentsui.Model; import com.android.documentsui.R; import com.android.documentsui.base.DebugFlags; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.UserId; import com.android.documentsui.roots.RootCursorWrapper; import java.util.ArrayList; Loading Loading @@ -184,7 +186,9 @@ public final class QuickViewIntentBuilder { String mimeType; String id; String authority; UserId userId; Uri uri; boolean hasNonMatchingDocumentUser = false; // Cursor's are not guaranteed to be immutable. Hence, traverse it only once. for (int i = 0; i < siblingIds.length; i++) { Loading @@ -209,18 +213,49 @@ public final class QuickViewIntentBuilder { continue; } userId = UserId.of(getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID)); if (!userId.equals(mDocument.userId)) { // If there is any document in the model does not have the same user as // mDocument, we will not add any siblings and the user for security reason. // Although the quick view package is trusted, the trusted quick view package may // not notice it is a cross-profile uri and may allow other app to handle this uri. if (DEBUG) { Log.d(TAG, "Skipping document from the other user. modelId: " + siblingIds[i]); } hasNonMatchingDocumentUser = true; continue; } id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); if (UserId.CURRENT_USER.equals(userId)) { uri = DocumentsContract.buildDocumentUri(authority, id); uris.add(uri); } else { uri = userId.buildDocumentUriAsUser(authority, id); } if (id.equals(mDocument.documentId)) { uris.add(uri); documentLocation = uris.size() - 1; // Position in "uris", not in the model. if (DEBUG) { Log.d(TAG, "Found starting point for QV. " + documentLocation); } } else if (!hasNonMatchingDocumentUser) { uris.add(uri); } } if (!uris.isEmpty() && hasNonMatchingDocumentUser) { if (DEBUG) { Log.d(TAG, "Remove all other uris except the document uri"); } Uri documentUri = uris.get(documentLocation); uris.clear(); uris.add(documentUri); return 0; // index of the item in a singleton list is 0. } return documentLocation; Loading tests/common/com/android/documentsui/testing/TestModel.java +9 −2 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ public class TestModel extends Model { static final String[] COLUMNS = new String[]{ RootCursorWrapper.COLUMN_AUTHORITY, RootCursorWrapper.COLUMN_USER_ID, Document.COLUMN_DOCUMENT_ID, Document.COLUMN_FLAGS, Document.COLUMN_DISPLAY_NAME, Loading Loading @@ -103,9 +104,10 @@ public class TestModel extends Model { flags); } public DocumentInfo createDocument(String name, String mimeType, int flags) { public DocumentInfo createDocumentForUser(String name, String mimeType, int flags, UserId userId) { DocumentInfo doc = new DocumentInfo(); doc.userId = mUserId; doc.userId = userId; doc.authority = mAuthority; doc.documentId = Integer.toString(++mLastId); doc.derivedUri = DocumentsContract.buildDocumentUri(doc.authority, doc.documentId); Loading @@ -119,10 +121,15 @@ public class TestModel extends Model { return doc; } public DocumentInfo createDocument(String name, String mimeType, int flags) { return createDocumentForUser(name, mimeType, flags, mUserId); } private void addToCursor(DocumentInfo doc) { MatrixCursor.RowBuilder row = mCursor.newRow(); row.add(Document.COLUMN_DOCUMENT_ID, doc.documentId); row.add(RootCursorWrapper.COLUMN_AUTHORITY, doc.authority); row.add(RootCursorWrapper.COLUMN_USER_ID, doc.userId); row.add(Document.COLUMN_DISPLAY_NAME, doc.displayName); row.add(Document.COLUMN_MIME_TYPE, doc.mimeType); row.add(Document.COLUMN_FLAGS, doc.flags); Loading tests/unit/com/android/documentsui/files/QuickViewIntentBuilderTest.java +62 −0 Original line number Diff line number Diff line package com.android.documentsui.files; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.QuickViewConstants; Loading @@ -15,8 +18,10 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.testing.TestEnv; import com.android.documentsui.testing.TestPackageManager; import com.android.documentsui.testing.TestProvidersAccess; import com.android.documentsui.testing.TestResources; import org.junit.Before; Loading Loading @@ -94,4 +99,61 @@ public class QuickViewIntentBuilderTest { assertEquals("Unexpected features set: " + features, 1, features.size()); assertTrue(features.contains(QuickViewConstants.FEATURE_VIEW)); } @Test public void testBuild() { mEnv.model.reset(); DocumentInfo previewDoc = mEnv.model.createFile("a.png", 0); mEnv.model.createFile("b.png", 0); mEnv.model.createFile("c.png", 0); mEnv.model.update(); QuickViewIntentBuilder builder = new QuickViewIntentBuilder(mContext, mRes, previewDoc, mEnv.model, true); Intent intent = builder.build(); ClipData clip = intent.getClipData(); assertThat(clip.getItemAt(intent.getIntExtra(Intent.EXTRA_INDEX, -1)).getUri()) .isEqualTo(previewDoc.getDocumentUri()); assertThat(clip.getItemCount()).isEqualTo(3); } @Test public void testBuild_excludeFolder() { mEnv.model.reset(); mEnv.model.createFolder("folder"); mEnv.model.createFolder("does not count"); mEnv.model.createFile("a.png", 0); DocumentInfo previewDoc = mEnv.model.createFile("b.png", 0); mEnv.model.createFile("c.png", 0); mEnv.model.update(); QuickViewIntentBuilder builder = new QuickViewIntentBuilder(mContext, mRes, previewDoc, mEnv.model, true); Intent intent = builder.build(); ClipData clip = intent.getClipData(); assertThat(clip.getItemAt(intent.getIntExtra(Intent.EXTRA_INDEX, -1)).getUri()) .isEqualTo(previewDoc.getDocumentUri()); assertThat(clip.getItemCount()).isEqualTo(3); } @Test public void testBuild_twoProfiles_containsOnlyPreviewDocument() { mEnv.model.reset(); mEnv.model.createDocumentForUser("a.txt", "text/plain", 0, TestProvidersAccess.OtherUser.USER_ID); DocumentInfo previewDoc = mEnv.model.createFile("b.png", 0); mEnv.model.createFile("c.png", 0); mEnv.model.update(); QuickViewIntentBuilder builder = new QuickViewIntentBuilder(mContext, mRes, previewDoc, mEnv.model, true); Intent intent = builder.build(); ClipData clip = intent.getClipData(); assertThat(clip.getItemAt(intent.getIntExtra(Intent.EXTRA_INDEX, -1)).getUri()) .isEqualTo(previewDoc.getDocumentUri()); assertThat(clip.getItemCount()).isEqualTo(1); } } tests/unit/com/android/documentsui/picker/ActionHandlerTest.java +8 −4 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.documentsui.Injector; import com.android.documentsui.R; import com.android.documentsui.TestUserIdManager; import com.android.documentsui.UserIdManager; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.Lookup; import com.android.documentsui.base.RootInfo; Loading Loading @@ -683,11 +684,14 @@ public class ActionHandlerTest { @Test public void testPreviewItem_onOtherUser() throws Exception { if (VersionUtils.isAtLeastR()) { mActivity.resources.setQuickViewerPackage("corptropolis.viewer"); mActivity.currentRoot = TestProvidersAccess.OtherUser.DOWNLOADS; mEnv.model.reset(); DocumentInfo otherUserDoc = mEnv.model.createDocumentForUser("a.png", "image/png", /* flags= */ 0, TestProvidersAccess.OtherUser.USER_ID); mEnv.model.update(); mHandler.onDocumentOpened(TestEnv.OtherUser.FILE_PNG, ActionHandler.VIEW_TYPE_PREVIEW, mHandler.onDocumentOpened(otherUserDoc, ActionHandler.VIEW_TYPE_PREVIEW, ActionHandler.VIEW_TYPE_REGULAR, true); mActivity.assertActivityAsUserStarted(Intent.ACTION_QUICK_VIEW, TestProvidersAccess.OtherUser.USER_HANDLE); Loading Loading
src/com/android/documentsui/AbstractActionHandler.java +2 −2 Original line number Diff line number Diff line Loading @@ -497,7 +497,7 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA // First try managing the document; we expect manager to filter // based on authority, so we don't grant. Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT); manage.setData(doc.derivedUri); manage.setData(doc.getDocumentUri()); try { doc.userId.startActivityAsUser(mActivity, manage); return true; Loading Loading @@ -542,7 +542,7 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA protected Intent buildViewIntent(DocumentInfo doc) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(doc.derivedUri, doc.mimeType); intent.setDataAndType(doc.getDocumentUri(), doc.mimeType); // Downloads has traditionally added the WRITE permission // in the TrampolineActivity. Since this behavior is long Loading
src/com/android/documentsui/files/QuickViewIntentBuilder.java +38 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.documentsui.files; import static com.android.documentsui.base.DocumentInfo.getCursorInt; import static com.android.documentsui.base.DocumentInfo.getCursorString; import static com.android.documentsui.base.Shared.MAX_DOCS_IN_INTENT; import static com.android.documentsui.base.SharedMinimal.DEBUG; Loading @@ -41,6 +42,7 @@ import com.android.documentsui.Model; import com.android.documentsui.R; import com.android.documentsui.base.DebugFlags; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.UserId; import com.android.documentsui.roots.RootCursorWrapper; import java.util.ArrayList; Loading Loading @@ -184,7 +186,9 @@ public final class QuickViewIntentBuilder { String mimeType; String id; String authority; UserId userId; Uri uri; boolean hasNonMatchingDocumentUser = false; // Cursor's are not guaranteed to be immutable. Hence, traverse it only once. for (int i = 0; i < siblingIds.length; i++) { Loading @@ -209,18 +213,49 @@ public final class QuickViewIntentBuilder { continue; } userId = UserId.of(getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID)); if (!userId.equals(mDocument.userId)) { // If there is any document in the model does not have the same user as // mDocument, we will not add any siblings and the user for security reason. // Although the quick view package is trusted, the trusted quick view package may // not notice it is a cross-profile uri and may allow other app to handle this uri. if (DEBUG) { Log.d(TAG, "Skipping document from the other user. modelId: " + siblingIds[i]); } hasNonMatchingDocumentUser = true; continue; } id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); if (UserId.CURRENT_USER.equals(userId)) { uri = DocumentsContract.buildDocumentUri(authority, id); uris.add(uri); } else { uri = userId.buildDocumentUriAsUser(authority, id); } if (id.equals(mDocument.documentId)) { uris.add(uri); documentLocation = uris.size() - 1; // Position in "uris", not in the model. if (DEBUG) { Log.d(TAG, "Found starting point for QV. " + documentLocation); } } else if (!hasNonMatchingDocumentUser) { uris.add(uri); } } if (!uris.isEmpty() && hasNonMatchingDocumentUser) { if (DEBUG) { Log.d(TAG, "Remove all other uris except the document uri"); } Uri documentUri = uris.get(documentLocation); uris.clear(); uris.add(documentUri); return 0; // index of the item in a singleton list is 0. } return documentLocation; Loading
tests/common/com/android/documentsui/testing/TestModel.java +9 −2 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ public class TestModel extends Model { static final String[] COLUMNS = new String[]{ RootCursorWrapper.COLUMN_AUTHORITY, RootCursorWrapper.COLUMN_USER_ID, Document.COLUMN_DOCUMENT_ID, Document.COLUMN_FLAGS, Document.COLUMN_DISPLAY_NAME, Loading Loading @@ -103,9 +104,10 @@ public class TestModel extends Model { flags); } public DocumentInfo createDocument(String name, String mimeType, int flags) { public DocumentInfo createDocumentForUser(String name, String mimeType, int flags, UserId userId) { DocumentInfo doc = new DocumentInfo(); doc.userId = mUserId; doc.userId = userId; doc.authority = mAuthority; doc.documentId = Integer.toString(++mLastId); doc.derivedUri = DocumentsContract.buildDocumentUri(doc.authority, doc.documentId); Loading @@ -119,10 +121,15 @@ public class TestModel extends Model { return doc; } public DocumentInfo createDocument(String name, String mimeType, int flags) { return createDocumentForUser(name, mimeType, flags, mUserId); } private void addToCursor(DocumentInfo doc) { MatrixCursor.RowBuilder row = mCursor.newRow(); row.add(Document.COLUMN_DOCUMENT_ID, doc.documentId); row.add(RootCursorWrapper.COLUMN_AUTHORITY, doc.authority); row.add(RootCursorWrapper.COLUMN_USER_ID, doc.userId); row.add(Document.COLUMN_DISPLAY_NAME, doc.displayName); row.add(Document.COLUMN_MIME_TYPE, doc.mimeType); row.add(Document.COLUMN_FLAGS, doc.flags); Loading
tests/unit/com/android/documentsui/files/QuickViewIntentBuilderTest.java +62 −0 Original line number Diff line number Diff line package com.android.documentsui.files; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.QuickViewConstants; Loading @@ -15,8 +18,10 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.testing.TestEnv; import com.android.documentsui.testing.TestPackageManager; import com.android.documentsui.testing.TestProvidersAccess; import com.android.documentsui.testing.TestResources; import org.junit.Before; Loading Loading @@ -94,4 +99,61 @@ public class QuickViewIntentBuilderTest { assertEquals("Unexpected features set: " + features, 1, features.size()); assertTrue(features.contains(QuickViewConstants.FEATURE_VIEW)); } @Test public void testBuild() { mEnv.model.reset(); DocumentInfo previewDoc = mEnv.model.createFile("a.png", 0); mEnv.model.createFile("b.png", 0); mEnv.model.createFile("c.png", 0); mEnv.model.update(); QuickViewIntentBuilder builder = new QuickViewIntentBuilder(mContext, mRes, previewDoc, mEnv.model, true); Intent intent = builder.build(); ClipData clip = intent.getClipData(); assertThat(clip.getItemAt(intent.getIntExtra(Intent.EXTRA_INDEX, -1)).getUri()) .isEqualTo(previewDoc.getDocumentUri()); assertThat(clip.getItemCount()).isEqualTo(3); } @Test public void testBuild_excludeFolder() { mEnv.model.reset(); mEnv.model.createFolder("folder"); mEnv.model.createFolder("does not count"); mEnv.model.createFile("a.png", 0); DocumentInfo previewDoc = mEnv.model.createFile("b.png", 0); mEnv.model.createFile("c.png", 0); mEnv.model.update(); QuickViewIntentBuilder builder = new QuickViewIntentBuilder(mContext, mRes, previewDoc, mEnv.model, true); Intent intent = builder.build(); ClipData clip = intent.getClipData(); assertThat(clip.getItemAt(intent.getIntExtra(Intent.EXTRA_INDEX, -1)).getUri()) .isEqualTo(previewDoc.getDocumentUri()); assertThat(clip.getItemCount()).isEqualTo(3); } @Test public void testBuild_twoProfiles_containsOnlyPreviewDocument() { mEnv.model.reset(); mEnv.model.createDocumentForUser("a.txt", "text/plain", 0, TestProvidersAccess.OtherUser.USER_ID); DocumentInfo previewDoc = mEnv.model.createFile("b.png", 0); mEnv.model.createFile("c.png", 0); mEnv.model.update(); QuickViewIntentBuilder builder = new QuickViewIntentBuilder(mContext, mRes, previewDoc, mEnv.model, true); Intent intent = builder.build(); ClipData clip = intent.getClipData(); assertThat(clip.getItemAt(intent.getIntExtra(Intent.EXTRA_INDEX, -1)).getUri()) .isEqualTo(previewDoc.getDocumentUri()); assertThat(clip.getItemCount()).isEqualTo(1); } }
tests/unit/com/android/documentsui/picker/ActionHandlerTest.java +8 −4 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.documentsui.Injector; import com.android.documentsui.R; import com.android.documentsui.TestUserIdManager; import com.android.documentsui.UserIdManager; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.Lookup; import com.android.documentsui.base.RootInfo; Loading Loading @@ -683,11 +684,14 @@ public class ActionHandlerTest { @Test public void testPreviewItem_onOtherUser() throws Exception { if (VersionUtils.isAtLeastR()) { mActivity.resources.setQuickViewerPackage("corptropolis.viewer"); mActivity.currentRoot = TestProvidersAccess.OtherUser.DOWNLOADS; mEnv.model.reset(); DocumentInfo otherUserDoc = mEnv.model.createDocumentForUser("a.png", "image/png", /* flags= */ 0, TestProvidersAccess.OtherUser.USER_ID); mEnv.model.update(); mHandler.onDocumentOpened(TestEnv.OtherUser.FILE_PNG, ActionHandler.VIEW_TYPE_PREVIEW, mHandler.onDocumentOpened(otherUserDoc, ActionHandler.VIEW_TYPE_PREVIEW, ActionHandler.VIEW_TYPE_REGULAR, true); mActivity.assertActivityAsUserStarted(Intent.ACTION_QUICK_VIEW, TestProvidersAccess.OtherUser.USER_HANDLE); Loading