diff options
author | Steve McKay <smckay@google.com> | 2015-11-18 14:56:50 -0800 |
---|---|---|
committer | Steve McKay <smckay@google.com> | 2015-11-19 15:56:05 -0800 |
commit | c6a4cd8c0f35a7e9d126ab09924f8f1f8422182a (patch) | |
tree | a7850cc689a5945f99121122451f011402abbf89 | |
parent | c38a5d7da10102b0664749f5720b6c50631e133d (diff) |
Add "Home" directory support.
Update FilesActivityUiTests to verify Home is present
and that clicking a root sets the title accordingly.
Guard addition of WRITABLE flag with a volume test.
Bug: 25147243
Change-Id: Ic20372737cae08a82af0aade0c0dbbd8c22d5f34
7 files changed, 112 insertions, 23 deletions
diff --git a/packages/DocumentsUI/res/drawable/ic_root_home.xml b/packages/DocumentsUI/res/drawable/ic_root_home.xml new file mode 100644 index 000000000000..0a258ac65019 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_root_home.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="#000000" + android:pathData="M20 6h-8l-2-2H4c-1.1 0-1.99 .9 -1.99 2L2 18c0 1.1 .9 2 2 2h16c1.1 0 2-.9 +2-2V8c0-1.1-.9-2-2-2zm-5 3c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm4 +8h-8v-1c0-1.33 2.67-2 4-2s4 .67 4 2v1z" /> + <path + android:pathData="M0 0h24v24H0z" /> +</vector> diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index beff196509b2..4c844c422ff5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -297,7 +297,7 @@ public class RootsFragment extends Fragment { for (final RootInfo root : roots) { final RootItem item = new RootItem(root); - if (root.isLibrary()) { + if (root.isLibrary() || root.isHome()) { libraries.add(item); } else { others.add(item); diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java index 723700ded6c6..ae5644d7fc3b 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -52,7 +52,7 @@ public class RootInfo implements Durable, Parcelable { public static final int TYPE_DOWNLOADS = 5; public static final int TYPE_LOCAL = 6; public static final int TYPE_MTP = 7; - public static final int TYPE_CLOUD = 8; + public static final int TYPE_OTHER = 8; public String authority; public String rootId; @@ -168,7 +168,10 @@ public class RootInfo implements Durable, Parcelable { derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null; // TODO: remove these special case icons - if (isExternalStorage()) { + if (isHome()) { + derivedIcon = R.drawable.ic_root_home; + derivedType = TYPE_LOCAL; + } else if (isExternalStorage()) { derivedIcon = R.drawable.ic_root_sdcard; derivedType = TYPE_LOCAL; } else if (isDownloads()) { @@ -188,7 +191,7 @@ public class RootInfo implements Durable, Parcelable { } else if (isMtp()) { derivedType = TYPE_MTP; } else { - derivedType = TYPE_CLOUD; + derivedType = TYPE_OTHER; } } @@ -196,6 +199,13 @@ public class RootInfo implements Durable, Parcelable { return authority == null && rootId == null; } + public boolean isHome() { + // Note that "home" is the expected root id for the auto-created + // user home directory on external storage. The "home" value should + // match ExternalStorageProvider.ROOT_ID_HOME. + return isExternalStorage() && "home".equals(rootId); + } + public boolean isExternalStorage() { return "com.android.externalstorage.documents".equals(authority); } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java index ba91c83b95e8..71d8b34f6773 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java @@ -125,6 +125,7 @@ public class FilesActivityUiTest extends InstrumentationTestCase { "Videos", "Audio", "Downloads", + "Home", ROOT_0_ID, ROOT_1_ID); } @@ -136,6 +137,13 @@ public class FilesActivityUiTest extends InstrumentationTestCase { mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv"); } + public void testRootClickSetsWindowTitle() throws Exception { + initTestFiles(); + + mBot.openRoot("Home"); + mBot.assertWindowTitle("Home"); + } + public void testFilesList_LiveUpdate() throws Exception { initTestFiles(); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java index 5c09794a3e28..ecad0617c179 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java @@ -16,6 +16,8 @@ package com.android.documentsui; +import static junit.framework.Assert.assertEquals; + import android.support.test.uiautomator.By; import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiDevice; @@ -80,6 +82,20 @@ class UiBot { mDevice.waitForIdle(); } + void assertWindowTitle(String expected) { + // Turns out the title field on a window does not have + // an id associated with it at runtime (which confuses the hell out of me) + // In code we address this via "android.R.id.title". + UiObject2 o = find(By.text(expected)); + // It's a bit of a conceit that we then *assert* that the title + // is the value that we used to identify the UiObject2. + // If the preceeding lookup fails, this'll choke with an NPE. + // But given the issue described in the comment above, we're + // going to do it anyway. Because we shouldn't be looking up + // the uiobject by it's expected content :| + assertEquals(expected, o.getText()); + } + void assertHasRoots(String... labels) throws UiObjectNotFoundException { List<String> missing = new ArrayList<>(); for (String label : labels) { diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml index f1c1adefc291..e48436ecb8ba 100644 --- a/packages/ExternalStorageProvider/res/values/strings.xml +++ b/packages/ExternalStorageProvider/res/values/strings.xml @@ -20,6 +20,6 @@ <!-- Title for documents backend that offers internal storage. [CHAR LIMIT=24] --> <string name="root_internal_storage">Internal storage</string> - <!-- Title for documents backend that offers documents. [CHAR LIMIT=24] --> - <string name="root_documents">Documents</string> + <!-- Title for user home dir. [CHAR LIMIT=24] --> + <string name="root_home">Home</string> </resources> diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index fcd45f223825..2cedc7238434 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -85,9 +85,11 @@ public class ExternalStorageProvider extends DocumentsProvider { public String docId; public File visiblePath; public File path; + public boolean reportAvailableBytes = true; } private static final String ROOT_ID_PRIMARY_EMULATED = "primary"; + private static final String ROOT_ID_HOME = "home"; private StorageManager mStorageManager; private Handler mHandler; @@ -118,6 +120,7 @@ public class ExternalStorageProvider extends DocumentsProvider { private void updateVolumesLocked() { mRoots.clear(); + VolumeInfo primaryVolume = null; final int userId = UserHandle.myUserId(); final List<VolumeInfo> volumes = mStorageManager.getVolumes(); for (VolumeInfo volume : volumes) { @@ -126,6 +129,9 @@ public class ExternalStorageProvider extends DocumentsProvider { final String rootId; final String title; if (volume.getType() == VolumeInfo.TYPE_EMULATED) { + // save off the primary volume for subsequent "Home" dir initialization. + primaryVolume = volume; + // We currently only support a single emulated volume mounted at // a time, and it's always considered the primary rootId = ROOT_ID_PRIMARY_EMULATED; @@ -152,25 +158,58 @@ public class ExternalStorageProvider extends DocumentsProvider { continue; } + final RootInfo root = new RootInfo(); + mRoots.put(rootId, root); + + root.rootId = rootId; + root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED + | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD; + + // Dunno when this would NOT be the case, but never hurts to be correct. + if (volume.isMountedWritable()) { + root.flags |= Root.FLAG_SUPPORTS_CREATE; + } + root.title = title; + if (volume.getType() == VolumeInfo.TYPE_PUBLIC) { + root.flags |= Root.FLAG_HAS_SETTINGS; + } + if (volume.isVisibleForRead(userId)) { + root.visiblePath = volume.getPathForUser(userId); + } else { + root.visiblePath = null; + } + root.path = volume.getInternalPathForUser(userId); try { - final RootInfo root = new RootInfo(); - mRoots.put(rootId, root); - - root.rootId = rootId; - root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED - | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD; - root.title = title; - if (volume.getType() == VolumeInfo.TYPE_PUBLIC) { - root.flags |= Root.FLAG_HAS_SETTINGS; - } - if (volume.isVisibleForRead(userId)) { - root.visiblePath = volume.getPathForUser(userId); - } else { - root.visiblePath = null; - } - root.path = volume.getInternalPathForUser(userId); root.docId = getDocIdForFile(root.path); + } catch (FileNotFoundException e) { + throw new IllegalStateException(e); + } + } + + // Finally, if primary storage is available we add the "Home" directory, + // creating it as needed. + if (primaryVolume != null && primaryVolume.isVisible()) { + final RootInfo root = new RootInfo(); + root.rootId = ROOT_ID_HOME; + mRoots.put(root.rootId, root); + root.title = getContext().getString(R.string.root_home); + // Only report bytes on *volumes*...as a matter of policy. + root.reportAvailableBytes = false; + root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH + | Root.FLAG_SUPPORTS_IS_CHILD; + + // Dunno when this would NOT be the case, but never hurts to be correct. + if (primaryVolume.isMountedWritable()) { + root.flags |= Root.FLAG_SUPPORTS_CREATE; + } + + root.visiblePath = new File( + primaryVolume.getPathForUser(userId), root.rootId); + root.path = new File( + primaryVolume.getInternalPathForUser(userId), root.rootId); + try { + root.docId = getDocIdForFile(root.path); } catch (FileNotFoundException e) { throw new IllegalStateException(e); } @@ -312,7 +351,8 @@ public class ExternalStorageProvider extends DocumentsProvider { row.add(Root.COLUMN_FLAGS, root.flags); row.add(Root.COLUMN_TITLE, root.title); row.add(Root.COLUMN_DOCUMENT_ID, root.docId); - row.add(Root.COLUMN_AVAILABLE_BYTES, root.path.getFreeSpace()); + row.add(Root.COLUMN_AVAILABLE_BYTES, + root.reportAvailableBytes ? root.path.getFreeSpace() : -1); } } return result; |