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
This commit is contained in:
15
packages/DocumentsUI/res/drawable/ic_root_home.xml
Normal file
15
packages/DocumentsUI/res/drawable/ic_root_home.xml
Normal file
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user