switch to alsa.omap3 module
[android_pandora.git] / apps / oi-filemanager / FileManager / src / org / openintents / filemanager / DirectoryScanner.java
1 package org.openintents.filemanager;
2
3 import java.io.File;
4 import java.lang.reflect.Method;
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.Comparator;
8 import java.util.List;
9
10 import org.openintents.filemanager.util.FileUtils;
11 import org.openintents.filemanager.util.ImageUtils;
12 import org.openintents.filemanager.util.MimeTypes;
13
14 import android.content.Context;
15 import android.content.Intent;
16 import android.content.pm.ApplicationInfo;
17 import android.content.pm.PackageInfo;
18 import android.content.pm.PackageManager;
19 import android.content.pm.PackageManager.NameNotFoundException;
20 import android.content.pm.ResolveInfo;
21 import android.content.res.Resources.NotFoundException;
22 import android.graphics.drawable.Drawable;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.os.SystemClock;
27 import android.support.v2.os.Build;
28 import android.util.Log;
29
30 public class DirectoryScanner extends Thread {
31
32         private static final String TAG = "OIFM_DirScanner";
33         
34         private File currentDirectory;
35         boolean cancel;
36
37         private String mSdCardPath;
38         private Context context;
39     private MimeTypes mMimeTypes;
40         private Handler handler;
41         private long operationStartTime;
42         private String mFilterFiletype;
43         private String mFilterMimetype;
44
45         private boolean mWriteableOnly;
46
47         private boolean mDirectoriesOnly;
48         
49         // Update progress bar every n files
50         static final private int PROGRESS_STEPS = 50;
51
52         // APK MIME type
53         private static final String MIME_APK = "application/vnd.android.package-archive";
54         
55         // Cupcake-specific methods
56     static Method formatter_formatFileSize;
57
58     static {
59         initializeCupcakeInterface();
60     }
61     
62
63
64         DirectoryScanner(File directory, Context context, Handler handler, MimeTypes mimeTypes, String filterFiletype, String filterMimetype, String sdCardPath, boolean writeableOnly, boolean directoriesOnly) {
65                 super("Directory Scanner");
66                 currentDirectory = directory;
67                 this.context = context;
68                 this.handler = handler;
69                 this.mMimeTypes = mimeTypes;
70                 this.mFilterFiletype = filterFiletype;
71                 this.mFilterMimetype = filterMimetype;
72                 this.mSdCardPath = sdCardPath;
73                 this.mWriteableOnly = writeableOnly;
74                 this.mDirectoriesOnly = directoriesOnly;
75         }
76         
77         private void clearData() {
78                 // Remove all references so we don't delay the garbage collection.
79                 context = null;
80                 mMimeTypes = null;
81                 handler = null;
82         }
83
84         public void run() {
85                 Log.v(TAG, "Scanning directory " + currentDirectory);
86                 
87                 File[] files = currentDirectory.listFiles();
88
89                 int fileCount = 0;
90                 int dirCount = 0;
91                 int sdCount = 0;
92                 int totalCount = 0;
93                 
94                 if (cancel) {
95                         Log.v(TAG, "Scan aborted");
96                         clearData();
97                         return;
98                 }
99                 
100                 if (files == null) {
101                         Log.v(TAG, "Returned null - inaccessible directory?");
102                         totalCount = 0;
103                 } else {
104                         totalCount = files.length;
105                 }
106                 
107                 operationStartTime = SystemClock.uptimeMillis();
108                 
109                 Log.v(TAG, "Counting files... (total count=" + totalCount + ")");
110
111                 int progress = 0;
112                 
113                 /** Dir separate for return after sorting*/
114                 List<IconifiedText> listDir = new ArrayList<IconifiedText>(totalCount);
115                 /** Dir separate for sorting */
116                 List<File> listDirFile = new ArrayList<File>(totalCount);
117
118                 /** Files separate for return after sorting*/
119                 List<IconifiedText> listFile = new ArrayList<IconifiedText>(totalCount);
120                 /** Files separate for sorting */
121                 List<File> listFileFile = new ArrayList<File>(totalCount);
122
123                 /** SD card separate for sorting - actually not sorted, so we don't need an ArrayList<File>*/
124                 List<IconifiedText> listSdCard = new ArrayList<IconifiedText>(3);
125                 
126                 boolean noMedia = false;
127
128                 // Cache some commonly used icons.
129                 Drawable sdIcon = context.getResources().getDrawable(R.drawable.ic_launcher_sdcard);
130                 Drawable folderIcon = context.getResources().getDrawable(R.drawable.ic_launcher_folder);
131                 Drawable genericFileIcon = context.getResources().getDrawable(R.drawable.icon_file);
132
133                 Drawable currentIcon = null; 
134                 
135                 boolean displayHiddenFiles = PreferenceActivity.getDisplayHiddenFiles(context);
136                 
137                 if (files != null) {
138                         for (File currentFile : files){ 
139                                 if (cancel) {
140                                         // Abort!
141                                         Log.v(TAG, "Scan aborted while checking files");
142                                         clearData();
143                                         return;
144                                 }
145
146                                 progress++;
147                                 updateProgress(progress, totalCount);
148
149                                 //If the user doesn't want to display hidden files and the file is hidden,
150                                 //skip displaying the file
151                                 if (!displayHiddenFiles && currentFile.isHidden()){
152                                         continue;
153                                 }
154                                                         
155                                 
156                                 if (currentFile.isDirectory()) { 
157                                         if (currentFile.getAbsolutePath().equals(mSdCardPath)) {
158                                                 currentIcon = sdIcon;
159
160                                                 listSdCard.add(new IconifiedText( 
161                                                                 currentFile.getName(), "", currentIcon)); 
162                                         } else {
163                                                 if (!mWriteableOnly || currentFile.canWrite()){
164                                                         listDirFile.add(currentFile);
165                                                 }
166                                         }
167                                 }else{ 
168                                         String fileName = currentFile.getName(); 
169                                         
170                                         // Is this the ".nomedia" file?
171                                         if (!noMedia) {
172                                                 if (fileName.equalsIgnoreCase(".nomedia")) {
173                                                         // It is!
174                                                         noMedia = true;
175                                                 }
176                                         }
177
178                                         String mimetype = mMimeTypes.getMimeType(fileName);
179
180                                         String filetype = FileUtils.getExtension(fileName);
181                                         boolean ext_allow = filetype.equalsIgnoreCase(mFilterFiletype) || mFilterFiletype == "";
182                                         boolean mime_allow = mFilterMimetype != null && 
183                                                         (mimetype.contentEquals(mFilterMimetype) || mFilterMimetype.contentEquals("*/*") ||
184                                                                         mFilterFiletype == null);
185                                         if (!mDirectoriesOnly && (ext_allow || mime_allow)) {
186                                                 listFileFile.add(currentFile);
187                                         }
188                                 } 
189                         }
190                 }
191                 
192                 Log.v(TAG, "Sorting results...");
193                 
194                 //Collections.sort(mListSdCard); 
195                 int sortBy = PreferenceActivity.getSortBy(context);
196                 boolean ascending = PreferenceActivity.getAscending(context);
197                 
198                 
199                 Collections.sort(listDirFile, Comparators.getForDirectory(sortBy, ascending)); 
200                 Collections.sort(listFileFile, Comparators.getForFile(sortBy, ascending)); 
201                 
202                 for(File f : listDirFile){
203                         listDir.add(new IconifiedText( 
204                                         f.getName(), FileUtils.formatDate(context, f.lastModified()), folderIcon));
205                 }
206                 
207                 for(File currentFile : listFileFile){
208                         String mimetype = mMimeTypes.getMimeType(currentFile.getName());
209                         currentIcon = getDrawableForMimetype(currentFile, mimetype);
210                         if (currentIcon == null) {
211                                 currentIcon = genericFileIcon;
212                         } else {
213                                 int width = genericFileIcon.getIntrinsicWidth();
214                                 int height = genericFileIcon.getIntrinsicHeight();
215                                 // Resizing image.
216                                 currentIcon = ImageUtils.resizeDrawable(currentIcon, width, height);
217
218                         }
219
220                         String size = "";
221
222                         try {
223                                 size = (String) formatter_formatFileSize.invoke(null, context, currentFile.length());
224                         } catch (Exception e) {
225                                 // The file size method is probably null (this is most
226                                 // likely not a Cupcake phone), or something else went wrong.
227                                 // Let's fall back to something primitive, like just the number
228                                 // of KB.
229                                 size = Long.toString(currentFile.length() / 1024);
230                                 size +=" KB";
231
232                                 // Technically "KB" should come from a string resource,
233                                 // but this is just a Cupcake 1.1 callback, and KB is universal
234                                 // enough.
235                         }
236                         
237                         listFile.add(new IconifiedText( 
238                                         currentFile.getName(), size + " , " + FileUtils.formatDate(
239                                                         context, currentFile.lastModified()), currentIcon));
240                 }
241
242                 if (!cancel) {
243                         Log.v(TAG, "Sending data back to main thread");
244                         
245                         DirectoryContents contents = new DirectoryContents();
246
247                         contents.listDir = listDir;
248                         contents.listFile = listFile;
249                         contents.listSdCard = listSdCard;
250                         contents.noMedia = noMedia;
251
252                         Message msg = handler.obtainMessage(FileManagerActivity.MESSAGE_SHOW_DIRECTORY_CONTENTS);
253                         msg.obj = contents;
254                         msg.sendToTarget();
255                 }
256
257                 clearData();
258         }
259         
260         private void updateProgress(int progress, int maxProgress) {
261                 // Only update the progress bar every n steps...
262                 if ((progress % PROGRESS_STEPS) == 0) {
263                         // Also don't update for the first second.
264                         long curTime = SystemClock.uptimeMillis();
265                         
266                         if (curTime - operationStartTime < 1000L) {
267                                 return;
268                         }
269                         
270                         // Okay, send an update.
271                         Message msg = handler.obtainMessage(FileManagerActivity.MESSAGE_SET_PROGRESS);
272                         msg.arg1 = progress;
273                         msg.arg2 = maxProgress;
274                         msg.sendToTarget();
275                 }
276         }
277
278         /**
279      * Return the Drawable that is associated with a specific mime type
280      * for the VIEW action.
281      * 
282      * @param mimetype
283      * @return
284      */
285     Drawable getDrawableForMimetype(File file, String mimetype) {
286      if (mimetype == null) {
287          return null;
288      }
289      
290          PackageManager pm = context.getPackageManager();
291          
292          // Returns the icon packaged in files with the .apk MIME type.
293          if(mimetype.equals(MIME_APK)){
294                  String path = file.getPath();
295                  PackageInfo pInfo = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
296                  if (pInfo!=null) {
297                          ApplicationInfo aInfo = pInfo.applicationInfo;
298                          
299                          // Bug in SDK versions >= 8. See here: http://code.google.com/p/android/issues/detail?id=9151
300                          if(Build.VERSION.SDK_INT >= 8){
301                                  aInfo.sourceDir = path;
302                                  aInfo.publicSourceDir = path;
303                          }
304                          
305                          return aInfo.loadIcon(pm);
306                  }
307          }
308          
309          int iconResource = mMimeTypes.getIcon(mimetype);
310          Drawable ret = null;
311          if(iconResource > 0){
312                  try {
313                          ret = pm.getResourcesForApplication(context.getPackageName()).getDrawable(iconResource);
314                  }catch(NotFoundException e){}
315                  catch(NameNotFoundException e){}
316          }
317          
318          if(ret != null){
319                  return ret;
320          }
321          
322          Uri data = FileUtils.getUri(file);
323         
324          Intent intent = new Intent(Intent.ACTION_VIEW);
325          //intent.setType(mimetype);
326          
327          // Let's probe the intent exactly in the same way as the VIEW action
328          // is performed in FileManagerActivity.openFile(..)
329      intent.setDataAndType(data, mimetype);
330      
331          final List<ResolveInfo> lri = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
332          
333          if (lri != null && lri.size() > 0) {
334                  //Log.i(TAG, "lri.size()" + lri.size());
335                  
336                  // return first element
337                  int index = 0;
338                  
339                  // Actually first element should be "best match",
340                  // but it seems that more recently installed applications
341                  // could be even better match.
342                  index = lri.size()-1;
343                  
344                  final ResolveInfo ri = lri.get(index);
345                  return ri.loadIcon(pm);
346          }
347          
348          return null;
349     }
350
351     private static void initializeCupcakeInterface() {
352         try {
353             formatter_formatFileSize = Class.forName("android.text.format.Formatter").getMethod("formatFileSize", Context.class, long.class);
354         } catch (Exception ex) {
355          // This is not cupcake.
356          return;
357         }
358     }
359 }
360
361 /**
362  * The container class for all comparators.
363  */
364 class Comparators{
365         public static final int NAME = 1;
366         public static final int SIZE = 2;
367         public static final int LAST_MODIFIED = 3;
368         
369         
370         public static Comparator<File> getForFile(int comparator, boolean ascending){
371                 switch(comparator){
372                 case NAME: return new NameComparator(ascending);
373                 case SIZE: return new SizeComparator(ascending);
374                 case LAST_MODIFIED: return new LastModifiedComparator(ascending);
375                 default: return null;
376                 }
377         }
378         public static Comparator<File> getForDirectory(int comparator, boolean ascending){
379                 switch(comparator){
380                 case NAME: return new NameComparator(ascending);
381                 case SIZE: return new NameComparator(ascending); //Not a bug! Getting directory's size is verry slow
382                 case LAST_MODIFIED: return new LastModifiedComparator(ascending);
383                 default: return null;
384                 }
385         }
386 }
387
388
389 abstract class FileComparator implements Comparator<File>{
390         protected boolean ascending = true;
391         
392         public FileComparator(boolean asc){
393                 ascending = asc;
394         }
395         
396         public FileComparator(){
397                 this(true);
398         }
399         
400         public int compare(File f1, File f2){
401                 return comp((ascending ? f1 : f2), (ascending ? f2 : f1));
402         }
403         
404         protected abstract int comp(File f1, File f2);
405 }
406
407 class NameComparator extends FileComparator{
408         public NameComparator(boolean asc){
409                 super(asc);
410         }
411         
412         protected int comp(File f1, File f2) {
413             return f1.getName().toLowerCase().compareTo(f2.getName().toLowerCase());
414         }
415 }
416
417 class SizeComparator extends FileComparator{
418         public SizeComparator(boolean asc){
419                 super(asc);
420         }
421         
422         protected int comp(File f1, File f2) {
423             return ((Long)f1.length()).compareTo(f2.length());
424         }
425         
426         /*//Very inefficient
427         private long getFileSize(File f){
428         if(f.isFile())
429                 return f.length();
430         int ret = 0;
431         for(File file : f.listFiles())
432                 ret += getFileSize(file);
433         
434         return ret;
435     }
436     */
437 }
438
439 class LastModifiedComparator extends FileComparator{
440         public LastModifiedComparator(boolean asc){
441                 super(asc);
442         }
443         
444         protected int comp(File f1, File f2) {
445             return ((Long)f1.lastModified()).compareTo(f2.lastModified());
446         }
447 }