From c6a4cd8c0f35a7e9d126ab09924f8f1f8422182a Mon Sep 17 00:00:00 2001 From: Steve McKay Date: Wed, 18 Nov 2015 14:56:50 -0800 Subject: [PATCH] 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 --- .../DocumentsUI/res/drawable/ic_root_home.xml | 15 ++++ .../android/documentsui/RootsFragment.java | 2 +- .../android/documentsui/model/RootInfo.java | 16 +++- .../documentsui/FilesActivityUiTest.java | 8 ++ .../src/com/android/documentsui/UiBot.java | 16 ++++ .../res/values/strings.xml | 4 +- .../ExternalStorageProvider.java | 74 ++++++++++++++----- 7 files changed, 112 insertions(+), 23 deletions(-) create mode 100644 packages/DocumentsUI/res/drawable/ic_root_home.xml 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 0000000000000..0a258ac650196 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_root_home.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index beff196509b2d..4c844c422ff56 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 723700ded6c6d..ae5644d7fc3b7 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 ba91c83b95e81..71d8b34f67731 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 5c09794a3e28d..ecad0617c179a 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 missing = new ArrayList<>(); for (String label : labels) { diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml index f1c1adefc291d..e48436ecb8ba7 100644 --- a/packages/ExternalStorageProvider/res/values/strings.xml +++ b/packages/ExternalStorageProvider/res/values/strings.xml @@ -20,6 +20,6 @@ Internal storage - - Documents + + Home diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index fcd45f223825f..2cedc7238434f 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 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;