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
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 0000000..0a258ac
--- /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 beff196..4c844c4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -297,7 +297,7 @@
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 723700d..ae5644d 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 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 @@
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 @@
} else if (isMtp()) {
derivedType = TYPE_MTP;
} else {
- derivedType = TYPE_CLOUD;
+ derivedType = TYPE_OTHER;
}
}
@@ -196,6 +199,13 @@
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 ba91c83..71d8b34 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 @@
"Videos",
"Audio",
"Downloads",
+ "Home",
ROOT_0_ID,
ROOT_1_ID);
}
@@ -136,6 +137,13 @@
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 5c09794..ecad061 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 @@
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 f1c1ade..e48436e 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 fcd45f2..2cedc72 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -85,9 +85,11 @@
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 @@
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 @@
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 @@
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 @@
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;