2 * Copyright (C) 2012 The CyanogenMod Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.cyanogenmod.filemanager.activities;
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.content.ActivityNotFoundException;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.DialogInterface.OnCancelListener;
28 import android.content.DialogInterface.OnDismissListener;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.res.Configuration;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.storage.StorageVolume;
36 import android.util.DisplayMetrics;
37 import android.util.Log;
38 import android.view.View;
39 import android.view.ViewGroup.LayoutParams;
40 import android.widget.AdapterView;
41 import android.widget.AdapterView.OnItemClickListener;
42 import android.widget.FrameLayout;
43 import android.widget.ListPopupWindow;
44 import android.widget.Toast;
46 import com.cyanogenmod.filemanager.R;
47 import com.cyanogenmod.filemanager.adapters.CheckableListAdapter;
48 import com.cyanogenmod.filemanager.adapters.CheckableListAdapter.CheckableItem;
49 import com.cyanogenmod.filemanager.console.ConsoleBuilder;
50 import com.cyanogenmod.filemanager.model.FileSystemObject;
51 import com.cyanogenmod.filemanager.preferences.DisplayRestrictions;
52 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
53 import com.cyanogenmod.filemanager.preferences.Preferences;
54 import com.cyanogenmod.filemanager.ui.ThemeManager;
55 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
56 import com.cyanogenmod.filemanager.ui.widgets.Breadcrumb;
57 import com.cyanogenmod.filemanager.ui.widgets.ButtonItem;
58 import com.cyanogenmod.filemanager.ui.widgets.NavigationView;
59 import com.cyanogenmod.filemanager.ui.widgets.NavigationView.OnDirectoryChangedListener;
60 import com.cyanogenmod.filemanager.ui.widgets.NavigationView.OnFilePickedListener;
61 import com.cyanogenmod.filemanager.util.DialogHelper;
62 import com.cyanogenmod.filemanager.util.ExceptionUtil;
63 import com.cyanogenmod.filemanager.util.FileHelper;
64 import com.cyanogenmod.filemanager.util.MediaHelper;
65 import com.cyanogenmod.filemanager.util.MimeTypeHelper;
66 import com.cyanogenmod.filemanager.util.StorageHelper;
69 import java.util.ArrayList;
70 import java.util.HashMap;
71 import java.util.List;
75 * The activity for allow to use a {@link NavigationView} like, to pick a file from other
78 public class PickerActivity extends Activity
79 implements OnCancelListener, OnDismissListener, OnFilePickedListener, OnDirectoryChangedListener {
81 private static final String TAG = "PickerActivity"; //$NON-NLS-1$
83 private static boolean DEBUG = false;
85 private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
87 public void onReceive(Context context, Intent intent) {
89 if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) {
97 private static final int RESULT_CROP_IMAGE = 1;
99 // The component that holds the crop operation. We use Gallery3d because we are confidence
100 // of his input parameters
101 private static final ComponentName CROP_COMPONENT =
103 "com.android.gallery3d", //$NON-NLS-1$
104 "com.android.gallery3d.filtershow.crop.CropActivity"); //$NON-NLS-1$
106 // Gallery crop editor action
107 private static final String ACTION_CROP = "com.android.camera.action.CROP"; //$NON-NLS-1$
109 // Extra data for Gallery CROP action
110 private static final String EXTRA_CROP = "crop"; //$NON-NLS-1$
112 // Scheme for file and directory picking
113 private static final String FILE_URI_SCHEME = "file"; //$NON-NLS-1$
114 private static final String FOLDER_URI_SCHEME = "folder"; //$NON-NLS-1$
115 private static final String DIRECTORY_URI_SCHEME = "directory"; //$NON-NLS-1$
117 FileSystemObject mFso; // The picked item
118 FileSystemObject mCurrentDirectory;
119 private AlertDialog mDialog;
120 private Handler mHandler;
124 NavigationView mNavigationView;
125 private View mRootView;
131 protected void onCreate(Bundle state) {
133 Log.d(TAG, "PickerActivity.onCreate"); //$NON-NLS-1$
136 // Register the broadcast receiver
137 IntentFilter filter = new IntentFilter();
138 filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED);
139 registerReceiver(this.mNotificationReceiver, filter);
141 // Initialize the activity
145 super.onCreate(state);
152 protected void onDestroy() {
154 Log.d(TAG, "PickerActivity.onDestroy"); //$NON-NLS-1$
157 // Unregister the receiver
159 unregisterReceiver(this.mNotificationReceiver);
160 } catch (Throwable ex) {
164 //All destroy. Continue
172 public void onConfigurationChanged(Configuration newConfig) {
173 super.onConfigurationChanged(newConfig);
178 * Method that displays a dialog with a {@link NavigationView} to select the
181 private void init() {
182 final boolean pickingDirectory;
183 final Intent intent = getIntent();
185 if (isFilePickIntent(intent)) {
187 Log.d(TAG, "PickerActivity: got file pick intent: " + String.valueOf(intent)); //$NON-NLS-1$
188 pickingDirectory = false;
189 } else if (isDirectoryPickIntent(getIntent())) {
191 Log.d(TAG, "PickerActivity: got folder pick intent: " + String.valueOf(intent)); //$NON-NLS-1$
192 pickingDirectory = true;
194 Log.d(TAG, "PickerActivity got unrecognized intent: " + String.valueOf(intent)); //$NON-NLS-1$
195 setResult(Activity.RESULT_CANCELED);
200 // Display restrictions
201 Map<DisplayRestrictions, Object> restrictions = new HashMap<DisplayRestrictions, Object>();
202 //- Mime/Type restriction
203 String mimeType = getIntent().getType();
204 if (mimeType != null) {
205 if (!MimeTypeHelper.isMimeTypeKnown(this, mimeType)) {
208 "Mime type %s unknown, falling back to wildcard.", //$NON-NLS-1$
210 mimeType = MimeTypeHelper.ALL_MIME_TYPES;
212 restrictions.put(DisplayRestrictions.MIME_TYPE_RESTRICTION, mimeType);
214 // Other restrictions
215 Bundle extras = getIntent().getExtras();
216 Log.d(TAG, "PickerActivity. extras: " + String.valueOf(extras)); //$NON-NLS-1$
217 if (extras != null) {
219 if (extras.containsKey(android.provider.MediaStore.Audio.Media.EXTRA_MAX_BYTES)) {
221 extras.getLong(android.provider.MediaStore.Audio.Media.EXTRA_MAX_BYTES);
222 restrictions.put(DisplayRestrictions.SIZE_RESTRICTION, Long.valueOf(size));
224 //-- Local filesystems only
225 if (extras.containsKey(Intent.EXTRA_LOCAL_ONLY)) {
226 boolean localOnly = extras.getBoolean(Intent.EXTRA_LOCAL_ONLY);
228 DisplayRestrictions.LOCAL_FILESYSTEM_ONLY_RESTRICTION,
229 Boolean.valueOf(localOnly));
232 if (pickingDirectory) {
233 restrictions.put(DisplayRestrictions.DIRECTORY_ONLY_RESTRICTION, Boolean.TRUE);
236 // Create or use the console
237 if (!initializeConsole()) {
238 // Something when wrong. Display a message and exit
239 DialogHelper.showToast(this, R.string.msgs_cant_create_console, Toast.LENGTH_SHORT);
244 // Create the root file
245 this.mRootView = getLayoutInflater().inflate(R.layout.picker, null, false);
246 this.mRootView.post(new Runnable() {
254 Breadcrumb breadcrumb = (Breadcrumb)this.mRootView.findViewById(R.id.breadcrumb_view);
255 // Set the free disk space warning level of the breadcrumb widget
256 String fds = Preferences.getSharedPreferences().getString(
257 FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getId(),
258 (String)FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getDefaultValue());
259 breadcrumb.setFreeDiskSpaceWarningLevel(Integer.parseInt(fds));
262 this.mNavigationView =
263 (NavigationView)this.mRootView.findViewById(R.id.navigation_view);
264 this.mNavigationView.setRestrictions(restrictions);
265 this.mNavigationView.setOnFilePickedListener(this);
266 this.mNavigationView.setOnDirectoryChangedListener(this);
267 this.mNavigationView.setBreadcrumb(breadcrumb);
269 // Apply the current theme
273 this.mDialog = DialogHelper.createDialog(
274 this, R.drawable.ic_launcher,
275 pickingDirectory ? R.string.directory_picker_title : R.string.picker_title,
278 this.mDialog.setButton(
279 DialogInterface.BUTTON_NEGATIVE,
280 getString(R.string.cancel),
281 new DialogInterface.OnClickListener() {
283 public void onClick(DialogInterface dlg, int which) {
287 if (pickingDirectory) {
288 this.mDialog.setButton(
289 DialogInterface.BUTTON_POSITIVE,
290 getString(R.string.select),
291 new DialogInterface.OnClickListener() {
293 public void onClick(DialogInterface dlg, int which) {
294 PickerActivity.this.mFso = PickerActivity.this.mCurrentDirectory;
299 this.mDialog.setCancelable(true);
300 this.mDialog.setOnCancelListener(this);
301 this.mDialog.setOnDismissListener(this);
302 DialogHelper.delegateDialogShow(this, this.mDialog);
304 // Set content description of storage volume button
305 ButtonItem fs = (ButtonItem)this.mRootView.findViewById(R.id.ab_filesystem_info);
306 fs.setContentDescription(getString(R.string.actionbar_button_storage_cd));
308 final File initialDir = getInitialDirectoryFromIntent(getIntent());
309 final String rootDirectory;
311 if (initialDir != null) {
312 rootDirectory = initialDir.getAbsolutePath();
314 rootDirectory = FileHelper.ROOT_DIRECTORY;
317 this.mHandler = new Handler();
318 this.mHandler.post(new Runnable() {
321 // Navigate to. The navigation view will redirect to the appropriate directory
322 PickerActivity.this.mNavigationView.changeCurrentDir(rootDirectory);
329 * Method that measure the height needed to avoid resizing when
330 * change to a new directory. This method fixed the height of the window
333 void measureHeight() {
334 // Calculate the dialog size based on the window height
335 DisplayMetrics displaymetrics = new DisplayMetrics();
336 getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
337 final int height = displaymetrics.heightPixels;
339 Configuration config = getResources().getConfiguration();
340 int percent = config.orientation == Configuration.ORIENTATION_LANDSCAPE ? 55 : 70;
342 FrameLayout.LayoutParams params =
343 new FrameLayout.LayoutParams(
344 LayoutParams.WRAP_CONTENT, (height * percent) / 100);
345 this.mRootView.setLayoutParams(params);
349 * Method that initializes a console
351 private boolean initializeConsole() {
353 // Create a ChRooted console
354 ConsoleBuilder.createDefaultConsole(this, false, false);
355 // There is a console allocated. Use it.
357 } catch (Throwable _throw) {
358 // Capture the exception
359 ExceptionUtil.translateException(this, _throw, true, false);
368 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
369 switch (requestCode) {
370 case RESULT_CROP_IMAGE:
371 // Return what the callee activity returns
372 setResult(resultCode, data);
380 // The response is not understood
383 "Ignore response. requestCode: %s, resultCode: %s, data: %s", //$NON-NLS-1$
384 Integer.valueOf(requestCode),
385 Integer.valueOf(resultCode),
387 DialogHelper.showToast(this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
394 public void onDismiss(DialogInterface dialog) {
395 if (this.mFso != null) {
396 File src = new File(this.mFso.getFullPath());
397 if (getIntent().getExtras() != null) {
398 // Some AOSP applications use the gallery to edit and crop the selected image
399 // with the Gallery crop editor. In this case pass the picked file to the
400 // CropActivity with the requested parameters
401 // Expected result is on onActivityResult
402 Bundle extras = getIntent().getExtras();
403 String crop = extras.getString(EXTRA_CROP);
404 if (Boolean.parseBoolean(crop)) {
405 // We want to use the Gallery3d activity because we know about it, and his
406 // parameters. At least we have a compatible one.
407 Intent intent = new Intent(ACTION_CROP);
408 if (getIntent().getType() != null) {
409 intent.setType(getIntent().getType());
411 intent.setData(Uri.fromFile(src));
412 intent.putExtras(extras);
413 intent.setComponent(CROP_COMPONENT);
415 startActivityForResult(intent, RESULT_CROP_IMAGE);
417 } catch (ActivityNotFoundException e) {
418 Log.w(TAG, "Failed to find crop activity!");
420 intent.setComponent(null);
422 startActivityForResult(intent, RESULT_CROP_IMAGE);
424 } catch (ActivityNotFoundException e) {
425 Log.w(TAG, "Failed to find any crop activity!");
430 // Return the picked file, as expected (this activity should fill the intent data
431 // and return RESULT_OK result)
432 Intent result = new Intent();
433 result.setData(getResultUriForFileFromIntent(getContentResolver(), src, getIntent()));
434 setResult(Activity.RESULT_OK, result);
442 private static boolean isFilePickIntent(Intent intent) {
443 final String action = intent.getAction();
445 if (Intent.ACTION_GET_CONTENT.equals(action)) {
448 if (Intent.ACTION_PICK.equals(action)) {
449 final Uri data = intent.getData();
450 if (data != null && FILE_URI_SCHEME.equals(data.getScheme())) {
458 private static boolean isDirectoryPickIntent(Intent intent) {
459 if (Intent.ACTION_PICK.equals(intent.getAction()) && intent.getData() != null) {
460 String scheme = intent.getData().getScheme();
461 if (FOLDER_URI_SCHEME.equals(scheme) || DIRECTORY_URI_SCHEME.equals(scheme)) {
469 private static File getInitialDirectoryFromIntent(Intent intent) {
470 if (!Intent.ACTION_PICK.equals(intent.getAction())) {
474 final Uri data = intent.getData();
479 final String path = data.getPath();
484 final File file = new File(path);
485 if (!file.exists() || !file.isAbsolute()) {
489 if (file.isDirectory()) {
492 return file.getParentFile();
495 private static Uri getResultUriForFileFromIntent(ContentResolver cr, File src, Intent intent) {
496 // Try to find the preferred uri scheme
497 Uri result = MediaHelper.fileToContentUri(cr, src);
498 if (result == null) {
499 result = Uri.fromFile(src);
502 if (Intent.ACTION_PICK.equals(intent.getAction()) && intent.getData() != null) {
503 String scheme = intent.getData().getScheme();
504 if (scheme != null) {
505 result = result.buildUpon().scheme(scheme).build();
516 public void onCancel(DialogInterface dialog) {
524 public void onFilePicked(FileSystemObject item) {
526 this.mDialog.dismiss();
533 public void onDirectoryChanged(FileSystemObject item) {
534 this.mCurrentDirectory = item;
538 * Method invoked when an action item is clicked.
540 * @param view The button pushed
542 public void onActionBarItemClick(View view) {
543 switch (view.getId()) {
544 //######################
546 //######################
547 case R.id.ab_filesystem_info:
548 //Show a popup with the storage volumes to select
549 showStorageVolumesPopUp(view);
558 * Method that cancels the activity
560 private void cancel() {
561 setResult(Activity.RESULT_CANCELED);
566 * Method that shows a popup with the storage volumes
568 * @param anchor The view on which anchor the popup
570 private void showStorageVolumesPopUp(View anchor) {
571 // Create a list (but not checkable)
572 final StorageVolume[] volumes = StorageHelper.getStorageVolumes(PickerActivity.this);
573 List<CheckableItem> descriptions = new ArrayList<CheckableItem>();
574 if (volumes != null) {
575 int cc = volumes.length;
576 for (int i = 0; i < cc; i++) {
577 String desc = StorageHelper.getStorageVolumeDescription(this, volumes[i]);
578 CheckableItem item = new CheckableItem(desc, false, false);
579 descriptions.add(item);
582 CheckableListAdapter adapter =
583 new CheckableListAdapter(getApplicationContext(), descriptions);
585 //Create a show the popup menu
586 final ListPopupWindow popup = DialogHelper.createListPopupWindow(this, adapter, anchor);
587 popup.setOnItemClickListener(new OnItemClickListener() {
589 public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
591 if (volumes != null) {
593 mNavigationView.changeCurrentDir(volumes[position].getPath());
601 * Method that applies the current theme to the activity
605 Theme theme = ThemeManager.getCurrentTheme(this);
606 theme.setBaseTheme(this, true);
609 theme.setBackgroundDrawable(this, this.mRootView, "background_drawable"); //$NON-NLS-1$
610 this.mNavigationView.applyTheme();