811a5a4a |
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 | } |