DocumentsActivity.java revision 1482789374fb8da3abea8f6f4f272a67205b95db
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.documentsui; 18 19import static com.android.documentsui.DirectoryFragment.getCursorString; 20 21import android.app.ActionBar; 22import android.app.ActionBar.OnNavigationListener; 23import android.app.Activity; 24import android.app.FragmentManager; 25import android.app.FragmentManager.BackStackEntry; 26import android.app.FragmentManager.OnBackStackChangedListener; 27import android.content.ClipData; 28import android.content.ContentResolver; 29import android.content.Context; 30import android.content.Intent; 31import android.content.pm.PackageManager; 32import android.content.pm.ProviderInfo; 33import android.content.pm.ResolveInfo; 34import android.database.Cursor; 35import android.graphics.drawable.Drawable; 36import android.net.Uri; 37import android.os.Bundle; 38import android.provider.DocumentsContract; 39import android.provider.DocumentsContract.DocumentColumns; 40import android.util.Log; 41import android.view.LayoutInflater; 42import android.view.Menu; 43import android.view.MenuItem; 44import android.view.View; 45import android.view.ViewGroup; 46import android.widget.BaseAdapter; 47import android.widget.TextView; 48 49import java.util.Arrays; 50import java.util.List; 51 52public class DocumentsActivity extends Activity { 53 private static final String TAG = "Documents"; 54 55 // TODO: fragment to show recently opened documents 56 // TODO: pull actionbar icon from current backend 57 58 private static final int ACTION_OPEN = 1; 59 private static final int ACTION_CREATE = 2; 60 61 private int mAction; 62 private String[] mAcceptMimes; 63 64 private final DisplayState mDisplayState = new DisplayState(); 65 66 private boolean mIgnoreNextNavigation; 67 68 private Uri mCurrentDir; 69 private boolean mCurrentSupportsCreate; 70 71 @Override 72 public void onCreate(Bundle icicle) { 73 super.onCreate(icicle); 74 75 final Intent intent = getIntent(); 76 final String action = intent.getAction(); 77 if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { 78 mAction = ACTION_OPEN; 79 mDisplayState.allowMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); 80 } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { 81 mAction = ACTION_CREATE; 82 mDisplayState.allowMultiple = false; 83 } 84 85 if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { 86 mAcceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); 87 } else { 88 mAcceptMimes = new String[] { intent.getType() }; 89 } 90 91 if (mimeMatches("image/*", mAcceptMimes)) { 92 mDisplayState.mode = DisplayState.MODE_GRID; 93 } else { 94 mDisplayState.mode = DisplayState.MODE_LIST; 95 } 96 97 setResult(Activity.RESULT_CANCELED); 98 setContentView(R.layout.activity); 99 100 getFragmentManager().addOnBackStackChangedListener(mStackListener); 101 BackendFragment.show(getFragmentManager()); 102 103 updateActionBar(); 104 105 if (mAction == ACTION_CREATE) { 106 final String mimeType = getIntent().getType(); 107 final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE); 108 SaveFragment.show(getFragmentManager(), mimeType, title); 109 } 110 } 111 112 public void updateActionBar() { 113 final FragmentManager fm = getFragmentManager(); 114 final ActionBar actionBar = getActionBar(); 115 116 if (fm.getBackStackEntryCount() > 0) { 117 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); 118 actionBar.setDisplayShowHomeEnabled(true); 119 actionBar.setDisplayHomeAsUpEnabled(true); 120 actionBar.setTitle(null); 121 actionBar.setListNavigationCallbacks(mStackAdapter, mNavigationListener); 122 actionBar.setSelectedNavigationItem(mStackAdapter.getCount() - 1); 123 mIgnoreNextNavigation = true; 124 125 } else { 126 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); 127 actionBar.setDisplayShowHomeEnabled(false); 128 actionBar.setDisplayHomeAsUpEnabled(false); 129 130 if (mAction == ACTION_OPEN) { 131 actionBar.setTitle(R.string.title_open); 132 } else if (mAction == ACTION_CREATE) { 133 actionBar.setTitle(R.string.title_save); 134 } 135 } 136 } 137 138 @Override 139 public boolean onCreateOptionsMenu(Menu menu) { 140 super.onCreateOptionsMenu(menu); 141 getMenuInflater().inflate(R.menu.activity, menu); 142 return true; 143 } 144 145 @Override 146 public boolean onPrepareOptionsMenu(Menu menu) { 147 super.onPrepareOptionsMenu(menu); 148 149 final MenuItem createDir = menu.findItem(R.id.menu_create_dir); 150 createDir.setVisible(mAction == ACTION_CREATE); 151 createDir.setEnabled(mCurrentSupportsCreate); 152 153 return true; 154 } 155 156 @Override 157 public boolean onOptionsItemSelected(MenuItem item) { 158 final int id = item.getItemId(); 159 if (id == android.R.id.home) { 160 getFragmentManager().popBackStack(); 161 updateActionBar(); 162 } else if (id == R.id.menu_create_dir) { 163 // TODO: show dialog to create directory 164 } 165 return super.onOptionsItemSelected(item); 166 } 167 168 private OnBackStackChangedListener mStackListener = new OnBackStackChangedListener() { 169 @Override 170 public void onBackStackChanged() { 171 updateActionBar(); 172 } 173 }; 174 175 private BaseAdapter mStackAdapter = new BaseAdapter() { 176 @Override 177 public int getCount() { 178 return getFragmentManager().getBackStackEntryCount(); 179 } 180 181 @Override 182 public Object getItem(int position) { 183 return getFragmentManager().getBackStackEntryAt(position); 184 } 185 186 @Override 187 public long getItemId(int position) { 188 return getFragmentManager().getBackStackEntryAt(position).getId(); 189 } 190 191 @Override 192 public View getView(int position, View convertView, ViewGroup parent) { 193 if (convertView == null) { 194 convertView = LayoutInflater.from(parent.getContext()) 195 .inflate(android.R.layout.simple_dropdown_item_1line, parent, false); 196 } 197 198 final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(position); 199 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); 200 text1.setText(entry.getBreadCrumbTitle()); 201 202 return convertView; 203 } 204 }; 205 206 private OnNavigationListener mNavigationListener = new OnNavigationListener() { 207 @Override 208 public boolean onNavigationItemSelected(int itemPosition, long itemId) { 209 if (mIgnoreNextNavigation) { 210 mIgnoreNextNavigation = false; 211 return false; 212 } 213 214 getFragmentManager().popBackStack((int) itemId, 0); 215 return true; 216 } 217 }; 218 219 public DisplayState getDisplayState() { 220 return mDisplayState; 221 } 222 223 public void onDirectoryChanged(Uri uri, int flags) { 224 mCurrentDir = uri; 225 mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0; 226 227 if (mAction == ACTION_CREATE) { 228 final FragmentManager fm = getFragmentManager(); 229 SaveFragment.get(fm).setSaveEnabled(mCurrentSupportsCreate); 230 } 231 232 invalidateOptionsMenu(); 233 } 234 235 public void onBackendPicked(ProviderInfo info) { 236 final Uri uri = DocumentsContract.buildDocumentUri( 237 info.authority, DocumentsContract.ROOT_GUID); 238 final CharSequence displayName = info.loadLabel(getPackageManager()); 239 DirectoryFragment.show(getFragmentManager(), uri, displayName.toString()); 240 } 241 242 public void onDocumentPicked(Document doc) { 243 final FragmentManager fm = getFragmentManager(); 244 if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) { 245 // Nested directory picked, recurse using new fragment 246 DirectoryFragment.show(fm, doc.uri, doc.displayName); 247 } else if (mAction == ACTION_OPEN) { 248 // Explicit file picked, return 249 onFinished(doc.uri); 250 } else if (mAction == ACTION_CREATE) { 251 // Overwrite current filename 252 SaveFragment.get(fm).setDisplayName(doc.displayName); 253 } 254 } 255 256 public void onDocumentsPicked(List<Document> docs) { 257 final int size = docs.size(); 258 final Uri[] uris = new Uri[size]; 259 for (int i = 0; i < size; i++) { 260 uris[i] = docs.get(i).uri; 261 } 262 onFinished(uris); 263 } 264 265 public void onSaveRequested(String mimeType, String displayName) { 266 // TODO: create file, confirming before overwriting 267 final Uri uri = null; 268 onFinished(uri); 269 } 270 271 private void onFinished(Uri... uris) { 272 Log.d(TAG, "onFinished() " + Arrays.toString(uris)); 273 274 final Intent intent = new Intent(); 275 if (uris.length == 1) { 276 intent.setData(uris[0]); 277 } else if (uris.length > 1) { 278 final ContentResolver resolver = getContentResolver(); 279 final ClipData clipData = new ClipData(null, mAcceptMimes, new ClipData.Item(uris[0])); 280 for (int i = 1; i < uris.length; i++) { 281 clipData.addItem(new ClipData.Item(uris[i])); 282 } 283 intent.setClipData(clipData); 284 } 285 286 intent.addFlags( 287 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); 288 if (mAction == ACTION_CREATE) { 289 intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 290 } 291 292 setResult(Activity.RESULT_OK, intent); 293 finish(); 294 } 295 296 public static class DisplayState { 297 public int mode; 298 public int sortBy; 299 public boolean allowMultiple; 300 301 public static final int MODE_LIST = 0; 302 public static final int MODE_GRID = 1; 303 304 public static final int SORT_BY_NAME = 0; 305 public static final int SORT_BY_DATE = 1; 306 } 307 308 public static class Document { 309 public Uri uri; 310 public String mimeType; 311 public String displayName; 312 313 public static Document fromCursor(String authority, Cursor cursor) { 314 final Document doc = new Document(); 315 final String guid = getCursorString(cursor, DocumentColumns.GUID); 316 doc.uri = DocumentsContract.buildDocumentUri(authority, guid); 317 doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); 318 doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); 319 return doc; 320 } 321 } 322 323 public static boolean mimeMatches(String filter, String[] tests) { 324 for (String test : tests) { 325 if (mimeMatches(filter, test)) { 326 return true; 327 } 328 } 329 return false; 330 } 331 332 public static boolean mimeMatches(String filter, String test) { 333 if (filter.equals(test)) { 334 return true; 335 } else if ("*/*".equals(filter)) { 336 return true; 337 } else if (filter.endsWith("/*")) { 338 return filter.regionMatches(0, test, 0, filter.indexOf('/')); 339 } else { 340 return false; 341 } 342 } 343 344 public static Drawable resolveDocumentIcon(Context context, String mimeType) { 345 // TODO: allow backends to provide custom MIME icons 346 if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { 347 return context.getResources().getDrawable(R.drawable.ic_dir); 348 } else { 349 final PackageManager pm = context.getPackageManager(); 350 final Intent intent = new Intent(Intent.ACTION_VIEW); 351 intent.setType(mimeType); 352 353 final ResolveInfo info = pm.resolveActivity( 354 intent, PackageManager.MATCH_DEFAULT_ONLY); 355 if (info != null) { 356 return info.loadIcon(pm); 357 } else { 358 return null; 359 } 360 } 361 } 362} 363