OSDN Git Service

fdf50993b6ad9d54fdfb5ddcc7ad2df96b9ce535
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / activities / PickerActivity.java
1 /*
2  * Copyright (C) 2012 The CyanogenMod 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
17 package com.cyanogenmod.filemanager.activities;
18
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;
45
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;
67
68 import java.io.File;
69 import java.util.ArrayList;
70 import java.util.HashMap;
71 import java.util.List;
72 import java.util.Map;
73
74 /**
75  * The activity for allow to use a {@link NavigationView} like, to pick a file from other
76  * application.
77  */
78 public class PickerActivity extends Activity
79         implements OnCancelListener, OnDismissListener, OnFilePickedListener, OnDirectoryChangedListener {
80
81     private static final String TAG = "PickerActivity"; //$NON-NLS-1$
82
83     private static boolean DEBUG = false;
84
85     private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
86         @Override
87         public void onReceive(Context context, Intent intent) {
88             if (intent != null) {
89                 if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) {
90                     applyTheme();
91                 }
92             }
93         }
94     };
95
96     // The result code
97     private static final int RESULT_CROP_IMAGE = 1;
98
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 =
102                                     new ComponentName(
103                                             "com.android.gallery3d", //$NON-NLS-1$
104                                             "com.android.gallery3d.filtershow.crop.CropActivity"); //$NON-NLS-1$
105
106     // Gallery crop editor action
107     private static final String ACTION_CROP = "com.android.camera.action.CROP"; //$NON-NLS-1$
108
109     // Extra data for Gallery CROP action
110     private static final String EXTRA_CROP = "crop"; //$NON-NLS-1$
111
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$
116
117     FileSystemObject mFso;  // The picked item
118     FileSystemObject mCurrentDirectory;
119     private AlertDialog mDialog;
120     private Handler mHandler;
121     /**
122      * @hide
123      */
124     NavigationView mNavigationView;
125     private View mRootView;
126
127     /**
128      * {@inheritDoc}
129      */
130     @Override
131     protected void onCreate(Bundle state) {
132         if (DEBUG) {
133             Log.d(TAG, "PickerActivity.onCreate"); //$NON-NLS-1$
134         }
135
136         // Register the broadcast receiver
137         IntentFilter filter = new IntentFilter();
138         filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED);
139         registerReceiver(this.mNotificationReceiver, filter);
140
141         // Initialize the activity
142         init();
143
144         //Save state
145         super.onCreate(state);
146     }
147
148     /**
149      * {@inheritDoc}
150      */
151     @Override
152     protected void onDestroy() {
153         if (DEBUG) {
154             Log.d(TAG, "PickerActivity.onDestroy"); //$NON-NLS-1$
155         }
156
157         // Unregister the receiver
158         try {
159             unregisterReceiver(this.mNotificationReceiver);
160         } catch (Throwable ex) {
161             /**NON BLOCK**/
162         }
163
164         //All destroy. Continue
165         super.onDestroy();
166     }
167
168     /**
169      * {@inheritDoc}
170      */
171     @Override
172     public void onConfigurationChanged(Configuration newConfig) {
173         super.onConfigurationChanged(newConfig);
174         measureHeight();
175     }
176
177     /**
178      * Method that displays a dialog with a {@link NavigationView} to select the
179      * proposed file
180      */
181     private void init() {
182         final boolean pickingDirectory;
183         final Intent intent = getIntent();
184
185         if (isFilePickIntent(intent)) {
186             // ok
187             Log.d(TAG, "PickerActivity: got file pick intent: " + String.valueOf(intent)); //$NON-NLS-1$
188             pickingDirectory = false;
189         } else if (isDirectoryPickIntent(getIntent())) {
190             // ok
191             Log.d(TAG, "PickerActivity: got folder pick intent: " + String.valueOf(intent)); //$NON-NLS-1$
192             pickingDirectory = true;
193         } else {
194             Log.d(TAG, "PickerActivity got unrecognized intent: " + String.valueOf(intent)); //$NON-NLS-1$
195             setResult(Activity.RESULT_CANCELED);
196             finish();
197             return;
198         }
199
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)) {
206                 Log.i(TAG,
207                         String.format(
208                                 "Mime type %s unknown, falling back to wildcard.", //$NON-NLS-1$
209                                 mimeType));
210                 mimeType = MimeTypeHelper.ALL_MIME_TYPES;
211             }
212             restrictions.put(DisplayRestrictions.MIME_TYPE_RESTRICTION, mimeType);
213         }
214         // Other restrictions
215         Bundle extras = getIntent().getExtras();
216         Log.d(TAG, "PickerActivity. extras: " + String.valueOf(extras)); //$NON-NLS-1$
217         if (extras != null) {
218             //-- File size
219             if (extras.containsKey(android.provider.MediaStore.Audio.Media.EXTRA_MAX_BYTES)) {
220                 long size =
221                         extras.getLong(android.provider.MediaStore.Audio.Media.EXTRA_MAX_BYTES);
222                 restrictions.put(DisplayRestrictions.SIZE_RESTRICTION, Long.valueOf(size));
223             }
224             //-- Local filesystems only
225             if (extras.containsKey(Intent.EXTRA_LOCAL_ONLY)) {
226                 boolean localOnly = extras.getBoolean(Intent.EXTRA_LOCAL_ONLY);
227                 restrictions.put(
228                         DisplayRestrictions.LOCAL_FILESYSTEM_ONLY_RESTRICTION,
229                         Boolean.valueOf(localOnly));
230             }
231         }
232         if (pickingDirectory) {
233             restrictions.put(DisplayRestrictions.DIRECTORY_ONLY_RESTRICTION, Boolean.TRUE);
234         }
235
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);
240             cancel();
241             return;
242         }
243
244         // Create the root file
245         this.mRootView = getLayoutInflater().inflate(R.layout.picker, null, false);
246         this.mRootView.post(new Runnable() {
247             @Override
248             public void run() {
249                 measureHeight();
250             }
251         });
252
253         // Breadcrumb
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));
260
261         // Navigation view
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);
268
269         // Apply the current theme
270         applyTheme();
271
272         // Create the dialog
273         this.mDialog = DialogHelper.createDialog(
274             this, R.drawable.ic_launcher,
275             pickingDirectory ? R.string.directory_picker_title : R.string.picker_title,
276             this.mRootView);
277
278         this.mDialog.setButton(
279                 DialogInterface.BUTTON_NEGATIVE,
280                 getString(R.string.cancel),
281                 new DialogInterface.OnClickListener() {
282             @Override
283             public void onClick(DialogInterface dlg, int which) {
284                 dlg.cancel();
285             }
286         });
287         if (pickingDirectory) {
288             this.mDialog.setButton(
289                     DialogInterface.BUTTON_POSITIVE,
290                     getString(R.string.select),
291                     new DialogInterface.OnClickListener() {
292                 @Override
293                 public void onClick(DialogInterface dlg, int which) {
294                     PickerActivity.this.mFso = PickerActivity.this.mCurrentDirectory;
295                     dlg.dismiss();
296                 }
297             });
298         }
299         this.mDialog.setCancelable(true);
300         this.mDialog.setOnCancelListener(this);
301         this.mDialog.setOnDismissListener(this);
302         DialogHelper.delegateDialogShow(this, this.mDialog);
303
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));
307
308         final File initialDir = getInitialDirectoryFromIntent(getIntent());
309         final String rootDirectory;
310
311         if (initialDir != null) {
312             rootDirectory = initialDir.getAbsolutePath();
313         } else {
314             rootDirectory = FileHelper.ROOT_DIRECTORY;
315         }
316
317         this.mHandler = new Handler();
318         this.mHandler.post(new Runnable() {
319             @Override
320             public void run() {
321                 // Navigate to. The navigation view will redirect to the appropriate directory
322                 PickerActivity.this.mNavigationView.changeCurrentDir(rootDirectory);
323             }
324         });
325
326     }
327
328     /**
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
331      * @hide
332      */
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;
338
339         Configuration config = getResources().getConfiguration();
340         int percent = config.orientation == Configuration.ORIENTATION_LANDSCAPE ? 55 : 70;
341
342         FrameLayout.LayoutParams params =
343                 new FrameLayout.LayoutParams(
344                         LayoutParams.WRAP_CONTENT, (height * percent) / 100);
345         this.mRootView.setLayoutParams(params);
346     }
347
348     /**
349      * Method that initializes a console
350      */
351     private boolean initializeConsole() {
352         try {
353             // Create a ChRooted console
354             ConsoleBuilder.createDefaultConsole(this, false, false);
355             // There is a console allocated. Use it.
356             return true;
357         } catch (Throwable _throw) {
358             // Capture the exception
359             ExceptionUtil.translateException(this, _throw, true, false);
360         }
361         return false;
362     }
363
364     /**
365      * {@inheritDoc}
366      */
367     @Override
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);
373                 finish();
374                 return;
375
376             default:
377                 break;
378         }
379
380         // The response is not understood
381         Log.w(TAG,
382                 String.format(
383                         "Ignore response. requestCode: %s, resultCode: %s, data: %s", //$NON-NLS-1$
384                         Integer.valueOf(requestCode),
385                         Integer.valueOf(resultCode),
386                         data));
387         DialogHelper.showToast(this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
388     }
389
390     /**
391      * {@inheritDoc}
392      */
393     @Override
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());
410                     }
411                     intent.setData(Uri.fromFile(src));
412                     intent.putExtras(extras);
413                     intent.setComponent(CROP_COMPONENT);
414                     try {
415                         startActivityForResult(intent, RESULT_CROP_IMAGE);
416                         return;
417                     } catch (ActivityNotFoundException e) {
418                         Log.w(TAG, "Failed to find crop activity!");
419                     }
420                     intent.setComponent(null);
421                     try {
422                         startActivityForResult(intent, RESULT_CROP_IMAGE);
423                         return;
424                     } catch (ActivityNotFoundException e) {
425                         Log.w(TAG, "Failed to find any crop activity!");
426                     }
427                 }
428             }
429
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);
435             finish();
436
437         } else {
438             cancel();
439         }
440     }
441
442     private static boolean isFilePickIntent(Intent intent) {
443         final String action = intent.getAction();
444
445         if (Intent.ACTION_GET_CONTENT.equals(action)) {
446             return true;
447         }
448         if (Intent.ACTION_PICK.equals(action)) {
449             final Uri data = intent.getData();
450             if (data != null && FILE_URI_SCHEME.equals(data.getScheme())) {
451                 return true;
452             }
453         }
454
455         return false;
456     }
457
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)) {
462                 return true;
463             }
464         }
465
466         return false;
467     }
468
469     private static File getInitialDirectoryFromIntent(Intent intent) {
470         if (!Intent.ACTION_PICK.equals(intent.getAction())) {
471             return null;
472         }
473
474         final Uri data = intent.getData();
475         if (data == null) {
476             return null;
477         }
478
479         final String path = data.getPath();
480         if (path == null) {
481             return null;
482         }
483
484         final File file = new File(path);
485         if (!file.exists() || !file.isAbsolute()) {
486             return null;
487         }
488
489         if (file.isDirectory()) {
490             return file;
491         }
492         return file.getParentFile();
493     }
494
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);
500         }
501
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();
506             }
507         }
508
509         return result;
510     }
511
512     /**
513      * {@inheritDoc}
514      */
515     @Override
516     public void onCancel(DialogInterface dialog) {
517         cancel();
518     }
519
520     /**
521      * {@inheritDoc}
522      */
523     @Override
524     public void onFilePicked(FileSystemObject item) {
525         this.mFso = item;
526         this.mDialog.dismiss();
527     }
528
529     /**
530      * {@inheritDoc}
531      */
532     @Override
533     public void onDirectoryChanged(FileSystemObject item) {
534         this.mCurrentDirectory = item;
535     }
536
537     /**
538      * Method invoked when an action item is clicked.
539      *
540      * @param view The button pushed
541      */
542     public void onActionBarItemClick(View view) {
543         switch (view.getId()) {
544             //######################
545             //Breadcrumb Actions
546             //######################
547             case R.id.ab_filesystem_info:
548                 //Show a popup with the storage volumes to select
549                 showStorageVolumesPopUp(view);
550                 break;
551
552             default:
553                 break;
554         }
555     }
556
557     /**
558      * Method that cancels the activity
559      */
560     private void cancel() {
561         setResult(Activity.RESULT_CANCELED);
562         finish();
563     }
564
565     /**
566      * Method that shows a popup with the storage volumes
567      *
568      * @param anchor The view on which anchor the popup
569      */
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);
580             }
581         }
582         CheckableListAdapter adapter =
583                 new CheckableListAdapter(getApplicationContext(), descriptions);
584
585         //Create a show the popup menu
586         final ListPopupWindow popup = DialogHelper.createListPopupWindow(this, adapter, anchor);
587         popup.setOnItemClickListener(new OnItemClickListener() {
588             @Override
589             public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
590                 popup.dismiss();
591                 if (volumes != null) {
592                     PickerActivity.this.
593                         mNavigationView.changeCurrentDir(volumes[position].getPath());
594                 }
595             }
596         });
597         popup.show();
598     }
599
600     /**
601      * Method that applies the current theme to the activity
602      * @hide
603      */
604     void applyTheme() {
605         Theme theme = ThemeManager.getCurrentTheme(this);
606         theme.setBaseTheme(this, true);
607
608         // View
609         theme.setBackgroundDrawable(this, this.mRootView, "background_drawable"); //$NON-NLS-1$
610         this.mNavigationView.applyTheme();
611     }
612 }