Merge "Create unique files, root ordering, UI bugs." into klp-dev
This commit is contained in:
@@ -26,6 +26,8 @@ import android.os.RemoteException;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
|
||||
import dalvik.system.CloseGuard;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -49,6 +51,8 @@ public class ContentProviderClient {
|
||||
private final boolean mStable;
|
||||
private boolean mReleased;
|
||||
|
||||
private final CloseGuard mGuard = CloseGuard.get();
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -58,6 +62,7 @@ public class ContentProviderClient {
|
||||
mContentResolver = contentResolver;
|
||||
mPackageName = contentResolver.mPackageName;
|
||||
mStable = stable;
|
||||
mGuard.open("release");
|
||||
}
|
||||
|
||||
/** See {@link ContentProvider#query ContentProvider.query} */
|
||||
@@ -324,6 +329,7 @@ public class ContentProviderClient {
|
||||
throw new IllegalStateException("Already released");
|
||||
}
|
||||
mReleased = true;
|
||||
mGuard.close();
|
||||
if (mStable) {
|
||||
return mContentResolver.releaseProvider(mContentProvider);
|
||||
} else {
|
||||
@@ -332,6 +338,13 @@ public class ContentProviderClient {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
if (mGuard != null) {
|
||||
mGuard.warnIfOpen();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a reference to the {@link ContentProvider} that is associated with this
|
||||
* client. If the {@link ContentProvider} is running in a different process then
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
<!-- TODO: allow rotation when state saving is in better shape -->
|
||||
<activity
|
||||
android:name=".DocumentsActivity"
|
||||
android:theme="@style/Theme">
|
||||
android:theme="@style/Theme"
|
||||
android:icon="@drawable/ic_doc_text">
|
||||
<intent-filter android:priority="100">
|
||||
<action android:name="android.intent.action.OPEN_DOCUMENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -87,8 +87,8 @@ import libcore.io.IoUtils;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class DocumentsActivity extends Activity {
|
||||
@@ -96,6 +96,8 @@ public class DocumentsActivity extends Activity {
|
||||
|
||||
private static final String EXTRA_STATE = "state";
|
||||
|
||||
private static final int CODE_FORWARD = 42;
|
||||
|
||||
private boolean mShowAsDialog;
|
||||
|
||||
private SearchView mSearchView;
|
||||
@@ -843,11 +845,24 @@ public class DocumentsActivity extends Activity {
|
||||
|
||||
public void onAppPicked(ResolveInfo info) {
|
||||
final Intent intent = new Intent(getIntent());
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
intent.setComponent(new ComponentName(
|
||||
info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
|
||||
startActivity(intent);
|
||||
finish();
|
||||
startActivityForResult(intent, CODE_FORWARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
Log.d(TAG, "onActivityResult() code=" + resultCode);
|
||||
|
||||
// Only relay back results when not canceled; otherwise stick around to
|
||||
// let the user pick another app/backend.
|
||||
if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
|
||||
setResult(resultCode, data);
|
||||
finish();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDocumentPicked(DocumentInfo doc) {
|
||||
|
||||
@@ -52,6 +52,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
private static final boolean LOGD = true;
|
||||
|
||||
public static final int MAX_OUTSTANDING_RECENTS = 2;
|
||||
|
||||
@@ -63,7 +64,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
/**
|
||||
* Maximum documents from a single root.
|
||||
*/
|
||||
public static final int MAX_DOCS_FROM_ROOT = 24;
|
||||
public static final int MAX_DOCS_FROM_ROOT = 64;
|
||||
|
||||
private static final ExecutorService sExecutor = buildExecutor();
|
||||
|
||||
@@ -194,6 +195,11 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
}
|
||||
}
|
||||
|
||||
if (LOGD) {
|
||||
Log.d(TAG, "Found " + cursors.size() + " of " + mTasks.size() + " recent queries done");
|
||||
Log.d(TAG, sExecutor.toString());
|
||||
}
|
||||
|
||||
final DirectoryResult result = new DirectoryResult();
|
||||
result.sortOrder = SORT_ORDER_LAST_MODIFIED;
|
||||
|
||||
|
||||
@@ -195,12 +195,9 @@ public class RecentsCreateFragment extends Fragment {
|
||||
|
||||
final SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
builder.append(stack.root.title);
|
||||
appendDrawable(builder, crumb);
|
||||
for (int i = stack.size() - 2; i >= 0; i--) {
|
||||
appendDrawable(builder, crumb);
|
||||
builder.append(stack.get(i).displayName);
|
||||
if (i > 0) {
|
||||
appendDrawable(builder, crumb);
|
||||
}
|
||||
}
|
||||
title.setText(builder);
|
||||
title.setEllipsize(TruncateAt.MIDDLE);
|
||||
|
||||
@@ -179,6 +179,8 @@ public class RootsCache {
|
||||
final Multimap<String, RootInfo> roots = ArrayListMultimap.create();
|
||||
final HashSet<String> stoppedAuthorities = Sets.newHashSet();
|
||||
|
||||
roots.put(mRecentsRoot.authority, mRecentsRoot);
|
||||
|
||||
final ContentResolver resolver = mContext.getContentResolver();
|
||||
final PackageManager pm = mContext.getPackageManager();
|
||||
final List<ProviderInfo> providers = pm.queryContentProviders(
|
||||
|
||||
@@ -253,6 +253,7 @@ public class RootsFragment extends Fragment {
|
||||
}
|
||||
|
||||
private static class SectionedRootsAdapter extends SectionedListAdapter {
|
||||
private final RootsAdapter mRecent;
|
||||
private final RootsAdapter mServices;
|
||||
private final RootsAdapter mShortcuts;
|
||||
private final RootsAdapter mDevices;
|
||||
@@ -260,12 +261,18 @@ public class RootsFragment extends Fragment {
|
||||
|
||||
public SectionedRootsAdapter(
|
||||
Context context, Collection<RootInfo> roots, Intent includeApps) {
|
||||
mRecent = new RootsAdapter(context);
|
||||
mServices = new RootsAdapter(context);
|
||||
mShortcuts = new RootsAdapter(context);
|
||||
mDevices = new RootsAdapter(context);
|
||||
mApps = new AppsAdapter(context);
|
||||
|
||||
for (RootInfo root : roots) {
|
||||
if (root.authority == null) {
|
||||
mRecent.add(root);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (root.rootType) {
|
||||
case Root.ROOT_TYPE_SERVICE:
|
||||
mServices.add(root);
|
||||
@@ -297,15 +304,18 @@ public class RootsFragment extends Fragment {
|
||||
mShortcuts.sort(comp);
|
||||
mDevices.sort(comp);
|
||||
|
||||
if (mRecent.getCount() > 0) {
|
||||
addSection(mRecent);
|
||||
}
|
||||
if (mServices.getCount() > 0) {
|
||||
addSection(mServices);
|
||||
}
|
||||
if (mShortcuts.getCount() > 0) {
|
||||
addSection(mShortcuts);
|
||||
}
|
||||
if (mDevices.getCount() > 0) {
|
||||
addSection(mDevices);
|
||||
}
|
||||
if (mServices.getCount() > 0) {
|
||||
addSection(mServices);
|
||||
}
|
||||
if (mApps.getCount() > 0) {
|
||||
addSection(mApps);
|
||||
}
|
||||
@@ -315,12 +325,6 @@ public class RootsFragment extends Fragment {
|
||||
public static class RootComparator implements Comparator<RootInfo> {
|
||||
@Override
|
||||
public int compare(RootInfo lhs, RootInfo rhs) {
|
||||
if (lhs.authority == null) {
|
||||
return -1;
|
||||
} else if (rhs.authority == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
|
||||
if (score != 0) {
|
||||
return score;
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public class SettingsActivity extends Activity {
|
||||
private static final String KEY_ADVANCED_DEVICES = "advancedDevices";
|
||||
@@ -47,9 +48,19 @@ public class SettingsActivity extends Activity {
|
||||
final ActionBar bar = getActionBar();
|
||||
if (bar != null) {
|
||||
bar.setDisplayShowHomeEnabled(false);
|
||||
bar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
@@ -181,7 +181,7 @@ public class DocumentInfo implements Durable, Parcelable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Document{name=" + displayName + ", docId=" + documentId + "}";
|
||||
return "Document{docId=" + documentId + ", name=" + displayName + "}";
|
||||
}
|
||||
|
||||
public boolean isCreateSupported() {
|
||||
|
||||
@@ -185,7 +185,7 @@ public class RootInfo implements Durable, Parcelable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Root{title=" + title + ", rootId=" + rootId + "}";
|
||||
return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";
|
||||
}
|
||||
|
||||
public Drawable loadIcon(Context context) {
|
||||
|
||||
@@ -96,25 +96,6 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
final String rootId = "documents";
|
||||
final File path = Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOCUMENTS);
|
||||
mIdToPath.put(rootId, path);
|
||||
|
||||
final RootInfo root = new RootInfo();
|
||||
root.rootId = rootId;
|
||||
root.rootType = Root.ROOT_TYPE_SHORTCUT;
|
||||
root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY
|
||||
| Root.FLAG_SUPPORTS_SEARCH;
|
||||
root.title = getContext().getString(R.string.root_documents);
|
||||
root.docId = getDocIdForFile(path);
|
||||
mRoots.add(root);
|
||||
mIdToRoot.put(rootId, root);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -230,14 +211,23 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
public String createDocument(String docId, String mimeType, String displayName)
|
||||
throws FileNotFoundException {
|
||||
final File parent = getFileForDocId(docId);
|
||||
displayName = validateDisplayName(mimeType, displayName);
|
||||
File file;
|
||||
|
||||
final File file = new File(parent, displayName);
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
file = new File(parent, displayName);
|
||||
if (!file.mkdir()) {
|
||||
throw new IllegalStateException("Failed to mkdir " + file);
|
||||
}
|
||||
} else {
|
||||
displayName = removeExtension(mimeType, displayName);
|
||||
file = new File(parent, addExtension(mimeType, displayName));
|
||||
|
||||
// If conflicting file, try adding counter suffix
|
||||
int n = 0;
|
||||
while (file.exists() && n++ < 32) {
|
||||
file = new File(parent, addExtension(mimeType, displayName + " (" + n + ")"));
|
||||
}
|
||||
|
||||
try {
|
||||
if (!file.createNewFile()) {
|
||||
throw new IllegalStateException("Failed to touch " + file);
|
||||
@@ -354,20 +344,31 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
private static String validateDisplayName(String mimeType, String displayName) {
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
return displayName;
|
||||
} else {
|
||||
// Try appending meaningful extension if needed
|
||||
if (!mimeType.equals(getTypeForName(displayName))) {
|
||||
final String extension = MimeTypeMap.getSingleton()
|
||||
.getExtensionFromMimeType(mimeType);
|
||||
if (extension != null) {
|
||||
displayName += "." + extension;
|
||||
}
|
||||
/**
|
||||
* Remove file extension from name, but only if exact MIME type mapping
|
||||
* exists. This means we can reapply the extension later.
|
||||
*/
|
||||
private static String removeExtension(String mimeType, String name) {
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1);
|
||||
final String nameMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mimeType.equals(nameMime)) {
|
||||
return name.substring(0, lastDot);
|
||||
}
|
||||
|
||||
return displayName;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file extension to name, but only if exact MIME type mapping exists.
|
||||
*/
|
||||
private static String addExtension(String mimeType, String name) {
|
||||
final String extension = MimeTypeMap.getSingleton()
|
||||
.getExtensionFromMimeType(mimeType);
|
||||
if (extension != null) {
|
||||
return name + "." + extension;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user