diff options
author | Jo De Boeck <deboeck.jo@gmail.com> | 2013-09-12 21:47:44 +0200 |
---|---|---|
committer | Danny Baumann <dannybaumann@web.de> | 2013-09-20 14:55:51 +0200 |
commit | 2f9277f279882b0ed4d30b84bf7327610b2a84e6 (patch) | |
tree | e88b8ef5519c1029d23dae25ddd64eab5d827c09 /src/com/cyanogenmod/filemanager | |
parent | 2f2d3e6a6f4fd74d9ef0c11420243d638124d13a (diff) | |
download | android_packages_apps_CMFileManager-2f9277f279882b0ed4d30b84bf7327610b2a84e6.tar.gz android_packages_apps_CMFileManager-2f9277f279882b0ed4d30b84bf7327610b2a84e6.tar.bz2 android_packages_apps_CMFileManager-2f9277f279882b0ed4d30b84bf7327610b2a84e6.zip |
Implement showing of apk icon and image and video thumbs in listing
Patchset 8: Move some stuff to MimeTypeHelper
Add cache
Add image and thumbail support
Add setting for this (defaul false for improve performance)
Patchset 9: Fixed icon derp display
Same size for all drawables
Patchset 10: Async drawable load
Shrink cache
Patchset 11: Use layout's width and height to scale the images
Patchset 12: Music album thumbs
Patchset 13: Added suggestions
Fixed FC
Patchset 14: Use content observer for icons
Keep context in icon holder
Fix scroll position being reset on thumbnail load
Patchset 15: Additional fixes and optimizations
Patchset 16: Do not saturate the ui with constants redrawing
Do not send back events while changing to a new directory
Fix FC on HistoryActivity (creation of a ContentObserver inside of a non-ui thread)
Added normalized media paths
Patchset 17: Use a HandlerThread to handle the preview loads
Patchset 18: Optimizations
Patchset 19: Fix deadlock
Patchset 20: Remove debug leftover
Patchset 21: Fix whitespace
Patchset 22: Misc. cleanup
Change-Id: I0a4ea801ff0cfbf31dda125b8925e7d35f4c3c99
Diffstat (limited to 'src/com/cyanogenmod/filemanager')
14 files changed, 876 insertions, 223 deletions
diff --git a/src/com/cyanogenmod/filemanager/activities/HistoryActivity.java b/src/com/cyanogenmod/filemanager/activities/HistoryActivity.java index 817028f6..964d880f 100644 --- a/src/com/cyanogenmod/filemanager/activities/HistoryActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/HistoryActivity.java @@ -220,33 +220,30 @@ public class HistoryActivity extends Activity implements OnItemClickListener { this.mListView = (ListView)findViewById(R.id.history_listview); // Load the history in background - AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { + AsyncTask<Void, Void, List<History>> task = new AsyncTask<Void, Void, List<History>>() { Exception mCause; List<History> mHistory; @Override - protected Boolean doInBackground(Void... params) { + protected List<History> doInBackground(Void... params) { try { this.mHistory = (List<History>)getIntent().getSerializableExtra(EXTRA_HISTORY_LIST); if (this.mHistory.isEmpty()) { View msg = findViewById(R.id.history_empty_msg); msg.setVisibility(View.VISIBLE); - return Boolean.TRUE; + return new ArrayList<History>(); } HistoryActivity.this.mIsEmpty = this.mHistory.isEmpty(); //Show inverted history final List<History> adapterList = new ArrayList<History>(this.mHistory); Collections.reverse(adapterList); - HistoryActivity.this.mAdapter = - new HistoryAdapter(HistoryActivity.this, adapterList); - - return Boolean.TRUE; + return adapterList; } catch (Exception e) { this.mCause = e; - return Boolean.FALSE; + return null; } } @@ -256,9 +253,12 @@ public class HistoryActivity extends Activity implements OnItemClickListener { } @Override - protected void onPostExecute(Boolean result) { + protected void onPostExecute(List<History> result) { waiting.setVisibility(View.GONE); - if (result.booleanValue()) { + if (result != null) { + HistoryActivity.this.mAdapter = + new HistoryAdapter(HistoryActivity.this, result); + if (HistoryActivity.this.mListView != null && HistoryActivity.this.mAdapter != null) { diff --git a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java index a837c4b8..3d97a04a 100644 --- a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java @@ -207,6 +207,14 @@ public class NavigationActivity extends Activity return; } + // Display thumbs + if (key.compareTo(FileManagerSettings. + SETTINGS_DISPLAY_THUMBS.getId()) == 0) { + // Clean the icon cache applying the current theme + applyTheme(); + return; + } + // Use flinger if (key.compareTo(FileManagerSettings. SETTINGS_USE_FLINGER.getId()) == 0) { @@ -1308,7 +1316,7 @@ public class NavigationActivity extends Activity * @param history The history reference * @return boolean A problem occurs while navigate */ - public boolean navigateToHistory(History history) { + public synchronized boolean navigateToHistory(History history) { try { //Gets the history History realHistory = this.mHistory.get(history.getPosition()); @@ -1322,7 +1330,9 @@ public class NavigationActivity extends Activity NavigationView view = getNavigationView(viewId); // Selected items must not be restored from on history navigation info.setSelectedFiles(view.getSelectedFiles()); - view.onRestoreState(info); + if (!view.onRestoreState(info)) { + return true; + } } else if (realHistory.getItem() instanceof SearchInfoParcelable) { //Search (open search with the search results) @@ -1598,6 +1608,11 @@ public class NavigationActivity extends Activity * @hide */ void exit() { + // Recycle the navigation views + int cc = this.mNavigationViews.length; + for (int i = 0; i < cc; i++) { + this.mNavigationViews[i].recycle(); + } try { FileManagerApplication.destroyBackgroundConsole(); } catch (Throwable ex) { diff --git a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java index 75d528e4..a50a1a08 100644 --- a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java @@ -611,6 +611,9 @@ public class SearchActivity extends Activity } //Set the listview + if (this.mSearchListView.getAdapter() != null) { + ((SearchResultAdapter)this.mSearchListView.getAdapter()).dispose(); + } this.mResultList = new ArrayList<FileSystemObject>(); SearchResultAdapter adapter = new SearchResultAdapter(this, @@ -1047,7 +1050,7 @@ public class SearchActivity extends Activity @Override public void onSuccess() { if (navigateTo(fFso, intent)) { - finish(); + exit(); } } @Override @@ -1072,8 +1075,18 @@ public class SearchActivity extends Activity // End this activity if (finish) { - finish(); + exit(); + } + } + + /** + * Method invoked when the activity needs to exit + */ + private void exit() { + if (this.mSearchListView.getAdapter() != null) { + ((SearchResultAdapter)this.mSearchListView.getAdapter()).dispose(); } + finish(); } /** diff --git a/src/com/cyanogenmod/filemanager/activities/preferences/GeneralPreferenceFragment.java b/src/com/cyanogenmod/filemanager/activities/preferences/GeneralPreferenceFragment.java index 8d1385d9..16e3eabf 100644 --- a/src/com/cyanogenmod/filemanager/activities/preferences/GeneralPreferenceFragment.java +++ b/src/com/cyanogenmod/filemanager/activities/preferences/GeneralPreferenceFragment.java @@ -47,6 +47,7 @@ public class GeneralPreferenceFragment extends TitlePreferenceFragment { private ListPreference mFiletimeFormatMode; private ListPreference mFreeDiskSpaceWarningLevel; private CheckBoxPreference mComputeFolderStatistics; + private CheckBoxPreference mDisplayThumbs; // private CheckBoxPreference mUseFlinger; private ListPreference mAccessMode; private CheckBoxPreference mDebugTraces; @@ -182,6 +183,12 @@ public class GeneralPreferenceFragment extends TitlePreferenceFragment { FileManagerSettings.SETTINGS_COMPUTE_FOLDER_STATISTICS.getId()); this.mComputeFolderStatistics.setOnPreferenceChangeListener(this.mOnChangeListener); + // Display thumbs + this.mDisplayThumbs = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getId()); + this.mDisplayThumbs.setOnPreferenceChangeListener(this.mOnChangeListener); + // Use flinger // this.mUseFlinger = // (CheckBoxPreference)findPreference( diff --git a/src/com/cyanogenmod/filemanager/adapters/BookmarksAdapter.java b/src/com/cyanogenmod/filemanager/adapters/BookmarksAdapter.java index cbe63a49..bbb5e3c8 100644 --- a/src/com/cyanogenmod/filemanager/adapters/BookmarksAdapter.java +++ b/src/com/cyanogenmod/filemanager/adapters/BookmarksAdapter.java @@ -104,7 +104,7 @@ public class BookmarksAdapter extends ArrayAdapter<Bookmark> { public BookmarksAdapter( Context context, List<Bookmark> bookmarks, OnClickListener onActionClickListener) { super(context, RESOURCE_ITEM_NAME, bookmarks); - this.mIconHolder = new IconHolder(); + this.mIconHolder = new IconHolder(context, false); this.mOnActionClickListener = onActionClickListener; //Do cache of the data for better performance @@ -126,7 +126,10 @@ public class BookmarksAdapter extends ArrayAdapter<Bookmark> { public void dispose() { clear(); this.mData = null; - this.mIconHolder = null; + if (mIconHolder != null) { + mIconHolder.cleanup(); + mIconHolder = null; + } } /** @@ -144,21 +147,19 @@ public class BookmarksAdapter extends ArrayAdapter<Bookmark> { //Build the data holder this.mData[i] = new BookmarksAdapter.DataHolder(); this.mData[i].mDwIcon = - this.mIconHolder.getDrawable(getContext(), BookmarksHelper.getIcon(bookmark)); + this.mIconHolder.getDrawable(BookmarksHelper.getIcon(bookmark)); this.mData[i].mName = bookmark.mName; this.mData[i].mPath = bookmark.mPath; this.mData[i].mDwAction = null; this.mData[i].mActionCd = null; if (bookmark.mType.compareTo(BOOKMARK_TYPE.HOME) == 0) { this.mData[i].mDwAction = - this.mIconHolder.getDrawable( - getContext(), "ic_config_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_config_drawable"); //$NON-NLS-1$ this.mData[i].mActionCd = getContext().getString(R.string.bookmarks_button_config_cd); } else if (bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) { this.mData[i].mDwAction = - this.mIconHolder.getDrawable(getContext(), - "ic_close_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_close_drawable"); //$NON-NLS-1$ this.mData[i].mActionCd = getContext().getString(R.string.bookmarks_button_remove_bookmark_cd); } @@ -220,7 +221,10 @@ public class BookmarksAdapter extends ArrayAdapter<Bookmark> { * Method that should be invoked when the theme of the app was changed */ public void notifyThemeChanged() { + if (mIconHolder != null) { + mIconHolder.cleanup(); + } // Empty icon holder - this.mIconHolder = new IconHolder(); + this.mIconHolder = new IconHolder(getContext(), false); } } diff --git a/src/com/cyanogenmod/filemanager/adapters/FileSystemObjectAdapter.java b/src/com/cyanogenmod/filemanager/adapters/FileSystemObjectAdapter.java index eeb70bf0..333ac13e 100644 --- a/src/com/cyanogenmod/filemanager/adapters/FileSystemObjectAdapter.java +++ b/src/com/cyanogenmod/filemanager/adapters/FileSystemObjectAdapter.java @@ -31,6 +31,8 @@ import android.widget.TextView; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.model.ParentDirectory; +import com.cyanogenmod.filemanager.preferences.FileManagerSettings; +import com.cyanogenmod.filemanager.preferences.Preferences; import com.cyanogenmod.filemanager.ui.IconHolder; import com.cyanogenmod.filemanager.ui.ThemeManager; import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; @@ -94,6 +96,7 @@ public class FileSystemObjectAdapter String mSize; } + private static final int MESSAGE_REDRAW = 1; private DataHolder[] mData; private IconHolder mIconHolder; @@ -103,6 +106,8 @@ public class FileSystemObjectAdapter private OnSelectionChangedListener mOnSelectionChangedListener; + private boolean mDisposed; + //The resource of the item check private static final int RESOURCE_ITEM_CHECK = R.id.navigation_view_item_check; //The resource of the item icon @@ -127,14 +132,13 @@ public class FileSystemObjectAdapter Context context, List<FileSystemObject> files, int itemViewResourceId, boolean pickable) { super(context, RESOURCE_ITEM_NAME, files); - this.mIconHolder = new IconHolder(); + this.mDisposed = false; this.mItemViewResourceId = itemViewResourceId; this.mSelectedItems = new ArrayList<FileSystemObject>(); this.mPickable = pickable; + notifyThemeChanged(); // Reload icons - //Do cache of the data for better performance - loadDefaultIcons(); - processData(files); + processData(); } /** @@ -151,8 +155,8 @@ public class FileSystemObjectAdapter * Method that loads the default icons (known icons and more common icons). */ private void loadDefaultIcons() { - this.mIconHolder.getDrawable(getContext(), "ic_fso_folder_drawable"); //$NON-NLS-1$ - this.mIconHolder.getDrawable(getContext(), "ic_fso_default_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_default_drawable"); //$NON-NLS-1$ } /** @@ -160,7 +164,10 @@ public class FileSystemObjectAdapter */ @Override public void notifyDataSetChanged() { - processData(null); + if (this.mDisposed) { + return; + } + processData(); super.notifyDataSetChanged(); } @@ -168,9 +175,13 @@ public class FileSystemObjectAdapter * Method that dispose the elements of the adapter. */ public void dispose() { + this.mDisposed = true; clear(); this.mData = null; - this.mIconHolder = null; + if (mIconHolder != null) { + mIconHolder.cleanup(); + mIconHolder = null; + } this.mSelectedItems.clear(); } @@ -194,17 +205,17 @@ public class FileSystemObjectAdapter /** * Method that process the data before use {@link #getView} method. - * - * @param files The list of files (to better performance) or null. */ - private void processData(List<FileSystemObject> files) { + private void processData() { Theme theme = ThemeManager.getCurrentTheme(getContext()); Resources res = getContext().getResources(); - this.mData = new DataHolder[getCount()]; - int cc = (files == null) ? getCount() : files.size(); + int cc = getCount(); + + this.mData = new DataHolder[cc]; + for (int i = 0; i < cc; i++) { //File system object info - FileSystemObject fso = (files == null) ? getItem(i) : files.get(i); + FileSystemObject fso = getItem(i); //Parse the last modification time and permissions StringBuilder sbSummary = new StringBuilder(); @@ -231,12 +242,10 @@ public class FileSystemObjectAdapter getContext(), "checkbox_deselected_drawable"); //$NON-NLS-1$ } this.mData[i].mDwIcon = this.mIconHolder.getDrawable( - getContext(), MimeTypeHelper.getIcon(getContext(), fso)); this.mData[i].mName = fso.getName(); this.mData[i].mSummary = sbSummary.toString(); this.mData[i].mSize = FileHelper.getHumanReadableSize(fso); - } } @@ -289,7 +298,13 @@ public class FileSystemObjectAdapter } //Set the data - viewHolder.mIvIcon.setImageDrawable(dataHolder.mDwIcon); + + if (convertView != null) { + // Cancel load for previous usage + mIconHolder.cancelLoad(viewHolder.mIvIcon); + } + mIconHolder.loadDrawable(viewHolder.mIvIcon, getItem(position), dataHolder.mDwIcon); + viewHolder.mTvName.setText(dataHolder.mName); if (viewHolder.mTvSummary != null) { viewHolder.mTvSummary.setText(dataHolder.mSummary); @@ -532,7 +547,14 @@ public class FileSystemObjectAdapter */ public void notifyThemeChanged() { // Empty icon holder - this.mIconHolder = new IconHolder(); + if (this.mIconHolder != null) { + this.mIconHolder.cleanup(); + } + final boolean displayThumbs = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getId(), + ((Boolean)FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getDefaultValue()).booleanValue()); + this.mIconHolder = new IconHolder(getContext(), displayThumbs); + loadDefaultIcons(); } } diff --git a/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java b/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java index 4582410f..f8ebbf02 100644 --- a/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java +++ b/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java @@ -98,7 +98,7 @@ public class HistoryAdapter extends ArrayAdapter<History> { */ public HistoryAdapter(Context context, List<History> history) { super(context, RESOURCE_ITEM_NAME, history); - this.mIconHolder = new IconHolder(); + notifyThemeChanged(); // Reload icons //Do cache of the data for better performance processData(history); @@ -119,7 +119,10 @@ public class HistoryAdapter extends ArrayAdapter<History> { public void dispose() { clear(); this.mData = null; - this.mIconHolder = null; + if (mIconHolder != null) { + mIconHolder.cleanup(); + mIconHolder = null; + } } /** @@ -138,12 +141,10 @@ public class HistoryAdapter extends ArrayAdapter<History> { this.mData[i] = new HistoryAdapter.DataHolder(); if (history.getItem() instanceof NavigationViewInfoParcelable) { this.mData[i].mDwIcon = - this.mIconHolder.getDrawable( - getContext(), "ic_fso_folder_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$ } else if (history.getItem() instanceof SearchInfoParcelable) { this.mData[i].mDwIcon = - this.mIconHolder.getDrawable( - getContext(), "ic_history_search_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_history_search_drawable"); //$NON-NLS-1$ } this.mData[i].mName = history.getItem().getTitle(); if (this.mData[i].mName == null || this.mData[i].mName.trim().length() == 0) { @@ -207,8 +208,11 @@ public class HistoryAdapter extends ArrayAdapter<History> { * Method that should be invoked when the theme of the app was changed */ public void notifyThemeChanged() { - // Empty icon holder - this.mIconHolder = new IconHolder(); + if (mIconHolder != null) { + mIconHolder.cleanup(); + } + // Empty icon holder (only have folders and search icons) + this.mIconHolder = new IconHolder(getContext(), false); } } diff --git a/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java b/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java index 0561a647..0caf438b 100644 --- a/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java +++ b/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java @@ -80,6 +80,7 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { Float mRelevance; } + private static final int MESSAGE_REDRAW = 1; private DataHolder[] mData; private IconHolder mIconHolder; @@ -90,6 +91,8 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { private final List<String> mQueries; + private boolean mDisposed; + //The resource of the item icon private static final int RESOURCE_ITEM_ICON = R.id.search_item_icon; //The resource of the item name @@ -111,7 +114,11 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { public SearchResultAdapter( Context context, List<SearchResult> files, int itemViewResourceId, Query queries) { super(context, RESOURCE_ITEM_NAME, files); - this.mIconHolder = new IconHolder(); + this.mDisposed = false; + final boolean displayThumbs = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getId(), + ((Boolean)FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getDefaultValue()).booleanValue()); + this.mIconHolder = new IconHolder(context, displayThumbs); this.mItemViewResourceId = itemViewResourceId; this.mQueries = queries.getQueries(); @@ -127,15 +134,15 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { //Do cache of the data for better performance loadDefaultIcons(); - processData(files); + processData(); } /** * Method that loads the default icons (known icons and more common icons). */ private void loadDefaultIcons() { - this.mIconHolder.getDrawable(getContext(), "ic_fso_folder_drawable"); //$NON-NLS-1$ - this.mIconHolder.getDrawable(getContext(), "ic_fso_default_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_default_drawable"); //$NON-NLS-1$ } /** @@ -143,7 +150,10 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { */ @Override public void notifyDataSetChanged() { - processData(null); + if (this.mDisposed) { + return; + } + processData(); super.notifyDataSetChanged(); } @@ -151,6 +161,10 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { * Method that dispose the elements of the adapter. */ public void dispose() { + if (this.mIconHolder != null) { + this.mIconHolder.cleanup(); + } + this.mDisposed = true; clear(); this.mData = null; this.mIconHolder = null; @@ -158,25 +172,23 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { /** * Method that process the data before use {@link #getView} method. - * - * @param files The list of files (to better performance) or null. */ - private void processData(List<SearchResult> files) { + private void processData() { Theme theme = ThemeManager.getCurrentTheme(getContext()); int highlightedColor = theme.getColor(getContext(), "search_highlight_color"); //$NON-NLS-1$ this.mData = new DataHolder[getCount()]; - int cc = (files == null) ? getCount() : files.size(); + int cc = getCount(); for (int i = 0; i < cc; i++) { //File system object info - SearchResult result = (files == null) ? getItem(i) : files.get(i); + SearchResult result = getItem(i); //Build the data holder + final FileSystemObject fso = result.getFso(); this.mData[i] = new SearchResultAdapter.DataHolder(); - this.mData[i].mDwIcon = - this.mIconHolder.getDrawable( - getContext(), MimeTypeHelper.getIcon(getContext(), result.getFso())); + this.mData[i].mDwIcon = this.mIconHolder.getDrawable( + MimeTypeHelper.getIcon(getContext(), fso)); if (this.mHighlightTerms) { this.mData[i].mName = SearchHelper.getHighlightedName(result, this.mQueries, highlightedColor); @@ -264,7 +276,13 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { ViewHolder viewHolder = (ViewHolder)v.getTag(); //Set the data - viewHolder.mIvIcon.setImageDrawable(dataHolder.mDwIcon); + + if (convertView != null) { + mIconHolder.cancelLoad(viewHolder.mIvIcon); + } + mIconHolder.loadDrawable(viewHolder.mIvIcon, + getItem(position).getFso(), dataHolder.mDwIcon); + viewHolder.mTvName.setText(dataHolder.mName, TextView.BufferType.SPANNABLE); viewHolder.mTvParentDir.setText(dataHolder.mParentDir); if (dataHolder.mRelevance != null) { diff --git a/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java b/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java index 5a220119..4d2bb2fa 100644 --- a/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java +++ b/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java @@ -98,6 +98,12 @@ public enum FileManagerSettings { SETTINGS_COMPUTE_FOLDER_STATISTICS( "cm_filemanager_compute_folder_statistics", Boolean.FALSE), //$NON-NLS-1$ /** + * When to display thumbs of pictures, videos, ... + * @hide + */ + SETTINGS_DISPLAY_THUMBS( + "cm_filemanager_show_thumbs", Boolean.FALSE), //$NON-NLS-1$ + /** * Whether use flinger to remove items * @hide */ diff --git a/src/com/cyanogenmod/filemanager/ui/IconHolder.java b/src/com/cyanogenmod/filemanager/ui/IconHolder.java index df60a81f..2f658647 100644 --- a/src/com/cyanogenmod/filemanager/ui/IconHolder.java +++ b/src/com/cyanogenmod/filemanager/ui/IconHolder.java @@ -16,12 +16,31 @@ package com.cyanogenmod.filemanager.ui; +import android.content.ContentResolver; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.media.ThumbnailUtils; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.widget.ImageView; +import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import com.cyanogenmod.filemanager.util.FileHelper; +import com.cyanogenmod.filemanager.util.MediaHelper; +import com.cyanogenmod.filemanager.util.MimeTypeHelper.KnownMimeTypeResolver; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -29,35 +48,303 @@ import java.util.Map; */ public class IconHolder { - private final Map<String, Drawable> mIcons; + private static final int MAX_CACHE = 500; + + private static final int MSG_LOAD = 1; + private static final int MSG_LOADED = 2; + private static final int MSG_DESTROY = 3; + + private final Map<String, Drawable> mIcons; // Themes based + private final Map<String, Drawable> mAppIcons; // App based + + private Map<String, Long> mAlbums; // Media albums + + private Map<ImageView, FileSystemObject> mRequests; + + private final Context mContext; + private final boolean mUseThumbs; + private boolean mNeedAlbumUpdate = true; + + private HandlerThread mWorkerThread; + private Handler mWorkerHandler; + + private static class LoadResult { + FileSystemObject fso; + Drawable result; + } + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_LOADED: + processResult((LoadResult) msg.obj); + sendEmptyMessageDelayed(MSG_DESTROY, 3000); + break; + case MSG_DESTROY: + shutdownWorker(); + break; + } + } + + private void processResult(LoadResult result) { + // Cache the new drawable + final String filePath = MediaHelper.normalizeMediaPath(result.fso.getFullPath()); + mAppIcons.put(filePath, result.result); + + // find the request for it + for (Map.Entry<ImageView, FileSystemObject> entry : mRequests.entrySet()) { + final ImageView imageView = entry.getKey(); + final FileSystemObject fso = entry.getValue(); + if (fso == result.fso) { + imageView.setImageDrawable(result.result); + mRequests.remove(imageView); + break; + } + } + } + }; + + private ContentObserver mMediaObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + synchronized (this) { + mNeedAlbumUpdate = true; + } + } + }; /** * Constructor of <code>IconHolder</code>. + * + * @param useThumbs If thumbs of images, videos, apps, ... should be returned + * instead of the default icon. */ - public IconHolder() { + public IconHolder(Context context, boolean useThumbs) { super(); + this.mContext = context; + this.mUseThumbs = useThumbs; + this.mRequests = new HashMap<ImageView, FileSystemObject>(); this.mIcons = new HashMap<String, Drawable>(); + this.mAppIcons = new LinkedHashMap<String, Drawable>(MAX_CACHE, .75F, true) { + private static final long serialVersionUID = 1L; + @Override + protected boolean removeEldestEntry(Entry<String, Drawable> eldest) { + return size() > MAX_CACHE; + } + }; + this.mAlbums = new HashMap<String, Long>(); + if (useThumbs) { + final ContentResolver cr = mContext.getContentResolver(); + for (Uri uri : MediaHelper.RELEVANT_URIS) { + cr.registerContentObserver(uri, true, mMediaObserver); + } + } } /** - * Method that loads, cache and returns a drawable reference - * of a icon. + * Method that returns a drawable reference of a icon. * - * @param context The current context * @param resid The resource identifier * @return Drawable The drawable icon reference */ - public Drawable getDrawable(Context context, final String resid) { + public Drawable getDrawable(final String resid) { //Check if the icon exists in the cache if (this.mIcons.containsKey(resid)) { return this.mIcons.get(resid); } //Load the drawable, cache and returns reference - Theme theme = ThemeManager.getCurrentTheme(context); - Drawable dw = theme.getDrawable(context, resid); + Theme theme = ThemeManager.getCurrentTheme(mContext); + Drawable dw = theme.getDrawable(mContext, resid); this.mIcons.put(resid, dw); return dw; } + /** + * Method that returns a drawable reference of a FileSystemObject. + * + * @param iconView View to load the drawable into + * @param fso The FileSystemObject reference + * @param defaultIcon Drawable to be used in case no specific one could be found + * @return Drawable The drawable reference + */ + public void loadDrawable(ImageView iconView, FileSystemObject fso, Drawable defaultIcon) { + if (!mUseThumbs) { + iconView.setImageDrawable(defaultIcon); + return; + } + + // Is cached? + final String filePath = MediaHelper.normalizeMediaPath(fso.getFullPath()); + if (this.mAppIcons.containsKey(filePath)) { + iconView.setImageDrawable(this.mAppIcons.get(filePath)); + return; + } + + mRequests.put(iconView, fso); + iconView.setImageDrawable(defaultIcon); + + mHandler.removeMessages(MSG_DESTROY); + if (mWorkerThread == null) { + mWorkerThread = new HandlerThread("IconHolderLoader"); + mWorkerThread.start(); + mWorkerHandler = new WorkerHandler(mWorkerThread.getLooper()); + } + Message msg = mWorkerHandler.obtainMessage(MSG_LOAD, fso); + msg.sendToTarget(); + } + + /** + * Cancel loading of a drawable for a certain ImageView. + */ + public void cancelLoad(ImageView view) { + FileSystemObject fso = mRequests.get(view); + if (fso != null && mWorkerHandler != null) { + mWorkerHandler.removeMessages(MSG_LOAD, fso); + } + mRequests.remove(view); + } + + private class WorkerHandler extends Handler { + public WorkerHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_LOAD: + FileSystemObject fso = (FileSystemObject) msg.obj; + Drawable d = loadDrawable(fso); + if (d != null) { + LoadResult result = new LoadResult(); + result.fso = fso; + result.result = d; + mHandler.obtainMessage(MSG_LOADED, result).sendToTarget(); + } + break; + } + } + + private Drawable loadDrawable(FileSystemObject fso) { + final String filePath = MediaHelper.normalizeMediaPath(fso.getFullPath()); + + if (KnownMimeTypeResolver.isAndroidApp(mContext, fso)) { + return getAppDrawable(fso); + } else if (KnownMimeTypeResolver.isImage(mContext, fso)) { + return getImageDrawable(filePath); + } else if (KnownMimeTypeResolver.isVideo(mContext, fso)) { + return getVideoDrawable(filePath); + } else if (FileHelper.isDirectory(fso)) { + synchronized (mMediaObserver) { + if (mNeedAlbumUpdate) { + mNeedAlbumUpdate = false; + mAlbums = MediaHelper.getAllAlbums(mContext.getContentResolver()); + } + } + if (mAlbums.containsKey(filePath)) { + return getAlbumDrawable(mAlbums.get(filePath)); + } + } + + return null; + } + + /** + * Method that returns the main icon of the app + * + * @param fso The FileSystemObject + * @return Drawable The drawable or null if cannot be extracted + */ + private Drawable getAppDrawable(FileSystemObject fso) { + final String filepath = fso.getFullPath(); + PackageManager pm = mContext.getPackageManager(); + PackageInfo packageInfo = pm.getPackageArchiveInfo(filepath, + PackageManager.GET_ACTIVITIES); + if (packageInfo != null) { + // Read http://code.google.com/p/android/issues/detail?id=9151, CM fixed this + // issue. We retain it for compatibility with older versions and roms without + // this fix. Required to access apk which are not installed. + final ApplicationInfo appInfo = packageInfo.applicationInfo; + appInfo.sourceDir = filepath; + appInfo.publicSourceDir = filepath; + return pm.getDrawable(appInfo.packageName, appInfo.icon, appInfo); + } + return null; + } + + /** + * Method that returns a thumbnail of the picture + * + * @param file The path to the file + * @return Drawable The drawable or null if cannot be extracted + */ + private Drawable getImageDrawable(String file) { + Bitmap thumb = ThumbnailUtils.createImageThumbnail( + MediaHelper.normalizeMediaPath(file), + ThumbnailUtils.TARGET_SIZE_MICRO_THUMBNAIL); + if (thumb == null) { + return null; + } + return new BitmapDrawable(mContext.getResources(), thumb); + } + + /** + * Method that returns a thumbnail of the video + * + * @param file The path to the file + * @return Drawable The drawable or null if cannot be extracted + */ + private Drawable getVideoDrawable(String file) { + Bitmap thumb = ThumbnailUtils.createVideoThumbnail( + MediaHelper.normalizeMediaPath(file), + ThumbnailUtils.TARGET_SIZE_MICRO_THUMBNAIL); + if (thumb == null) { + return null; + } + return new BitmapDrawable(mContext.getResources(), thumb); + } + + /** + * Method that returns a thumbnail of the album folder + * + * @param albumId The album identifier + * @return Drawable The drawable or null if cannot be extracted + */ + private Drawable getAlbumDrawable(long albumId) { + String path = MediaHelper.getAlbumThumbnailPath(mContext.getContentResolver(), albumId); + if (path == null) { + return null; + } + Bitmap thumb = ThumbnailUtils.createImageThumbnail(path, + ThumbnailUtils.TARGET_SIZE_MICRO_THUMBNAIL); + if (thumb == null) { + return null; + } + return new BitmapDrawable(mContext.getResources(), thumb); + } + } + + /** + * Shut down worker thread + */ + private void shutdownWorker() { + if (mWorkerThread != null) { + mWorkerThread.getLooper().quit(); + mWorkerHandler = null; + mWorkerThread = null; + } + } + + /** + * Free any resources used by this instance + */ + public void cleanup() { + this.mRequests.clear(); + this.mIcons.clear(); + this.mAppIcons.clear(); + mContext.getContentResolver().unregisterContentObserver(mMediaObserver); + shutdownWorker(); + } } diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/FixedSizeImageView.java b/src/com/cyanogenmod/filemanager/ui/widgets/FixedSizeImageView.java new file mode 100644 index 00000000..f2af92c6 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/ui/widgets/FixedSizeImageView.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.ui.widgets; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * A subclass of ImageView that assumes to be fixed size + * (not wrap_content / match_parent). Doing so it can + * optimize the drawable change code paths. + */ +public class FixedSizeImageView extends ImageView { + private boolean mSuppressLayoutRequest; + + public FixedSizeImageView(Context context) { + super(context); + } + + public FixedSizeImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FixedSizeImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setImageResource(int resId) { + mSuppressLayoutRequest = true; + super.setImageResource(resId); + mSuppressLayoutRequest = false; + } + + public void setImageURI(Uri uri) { + mSuppressLayoutRequest = true; + super.setImageURI(uri); + mSuppressLayoutRequest = false; + } + + public void setImageDrawable(Drawable drawable) { + mSuppressLayoutRequest = true; + super.setImageDrawable(drawable); + mSuppressLayoutRequest = false; + } + + public void requestLayout() { + if (!mSuppressLayoutRequest) { + super.requestLayout(); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java index 11570b43..58602a82 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java @@ -212,6 +212,7 @@ public class NavigationView extends RelativeLayout implements List<FileSystemObject> mFiles; private FileSystemObjectAdapter mAdapter; + private boolean mChangingDir; private final Object mSync = new Object(); private OnHistoryListener mOnHistoryListener; @@ -310,8 +311,15 @@ public class NavigationView extends RelativeLayout implements * Invoked when the instance need to be restored. * * @param info The serialized info + * @return boolean If can restore */ - public void onRestoreState(NavigationViewInfoParcelable info) { + public boolean onRestoreState(NavigationViewInfoParcelable info) { + synchronized (mSync) { + if (mChangingDir) { + return false; + } + } + //Restore the data this.mId = info.getId(); this.mCurrentDir = info.getCurrentDir(); @@ -321,6 +329,7 @@ public class NavigationView extends RelativeLayout implements //Update the views refresh(); + return true; } /** @@ -585,11 +594,20 @@ public class NavigationView extends RelativeLayout implements } /** + * Method that recycles this object + */ + public void recycle() { + if (this.mAdapter != null) { + this.mAdapter.dispose(); + } + } + + /** * Method that change the view mode. * * @param newMode The new mode */ - @SuppressWarnings({ "unchecked", "null" }) + @SuppressWarnings("unchecked") public void changeViewMode(final NavigationLayoutMode newMode) { synchronized (this.mSync) { //Check that it is really necessary change the mode @@ -777,181 +795,201 @@ public class NavigationView extends RelativeLayout implements // is created) final String fNewDir = checkChRootedNavigation(newDir); + // Wait to finalization synchronized (this.mSync) { - //Check that it is really necessary change the directory - if (!reload && this.mCurrentDir != null && this.mCurrentDir.compareTo(fNewDir) == 0) { - return; + if (mChangingDir) { + try { + mSync.wait(); + } catch (InterruptedException iex) { + // Ignore + } } + mChangingDir = true; + } - final boolean hasChanged = - !(this.mCurrentDir != null && this.mCurrentDir.compareTo(fNewDir) == 0); - final boolean isNewHistory = (this.mCurrentDir != null); - - //Execute the listing in a background process - AsyncTask<String, Integer, List<FileSystemObject>> task = - new AsyncTask<String, Integer, List<FileSystemObject>>() { - /** - * {@inheritDoc} - */ - @Override - protected List<FileSystemObject> doInBackground(String... params) { - try { - //Reset the custom title view and returns to breadcrumb - if (NavigationView.this.mTitle != null) { - NavigationView.this.mTitle.post(new Runnable() { - @Override - public void run() { - try { - NavigationView.this.mTitle.restoreView(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - - //Start of loading data - if (NavigationView.this.mBreadcrumb != null) { - try { - NavigationView.this.mBreadcrumb.startLoading(); - } catch (Throwable ex) { - /**NON BLOCK**/ - } - } + //Check that it is really necessary change the directory + if (!reload && this.mCurrentDir != null && this.mCurrentDir.compareTo(fNewDir) == 0) { + return; + } - //Get the files, resolve links and apply configuration - //(sort, hidden, ...) - List<FileSystemObject> files = NavigationView.this.mFiles; - if (!useCurrent) { - files = CommandHelper.listFiles(getContext(), fNewDir, null); - } - return files; - } catch (final ConsoleAllocException e) { - //Show exception and exists - NavigationView.this.post(new Runnable() { + final boolean hasChanged = + !(this.mCurrentDir != null && this.mCurrentDir.compareTo(fNewDir) == 0); + final boolean isNewHistory = (this.mCurrentDir != null); + + //Execute the listing in a background process + AsyncTask<String, Integer, List<FileSystemObject>> task = + new AsyncTask<String, Integer, List<FileSystemObject>>() { + /** + * {@inheritDoc} + */ + @Override + protected List<FileSystemObject> doInBackground(String... params) { + try { + //Reset the custom title view and returns to breadcrumb + if (NavigationView.this.mTitle != null) { + NavigationView.this.mTitle.post(new Runnable() { @Override public void run() { - Context ctx = getContext(); - Log.e(TAG, ctx.getString( - R.string.msgs_cant_create_console), e); - DialogHelper.showToast(ctx, - R.string.msgs_cant_create_console, - Toast.LENGTH_LONG); - ((Activity)ctx).finish(); + try { + NavigationView.this.mTitle.restoreView(); + } catch (Exception e) { + e.printStackTrace(); + } } }); - return null; - - } catch (Exception ex) { - //End of loading data - if (NavigationView.this.mBreadcrumb != null) { - try { - NavigationView.this.mBreadcrumb.endLoading(); - } catch (Throwable ex2) { - /**NON BLOCK**/ - } + } + + + //Start of loading data + if (NavigationView.this.mBreadcrumb != null) { + try { + NavigationView.this.mBreadcrumb.startLoading(); + } catch (Throwable ex) { + /**NON BLOCK**/ } + } - //Capture exception (attach task, and use listener to do the anim) - ExceptionUtil.attachAsyncTask( - ex, - new AsyncTask<Object, Integer, Boolean>() { - private List<FileSystemObject> mTaskFiles = null; - @Override - @SuppressWarnings({ - "unchecked", "unqualified-field-access" - }) - protected Boolean doInBackground(Object... taskParams) { - mTaskFiles = (List<FileSystemObject>)taskParams[0]; - return Boolean.TRUE; - } + //Get the files, resolve links and apply configuration + //(sort, hidden, ...) + List<FileSystemObject> files = NavigationView.this.mFiles; + if (!useCurrent) { + files = CommandHelper.listFiles(getContext(), fNewDir, null); + } + return files; - @Override - @SuppressWarnings("unqualified-field-access") - protected void onPostExecute(Boolean result) { - if (!result.booleanValue()) { - return; - } - onPostExecuteTask( - mTaskFiles, addToHistory, - isNewHistory, hasChanged, - searchInfo, fNewDir, scrollTo); - } - }); - final OnRelaunchCommandResult exListener = - new OnRelaunchCommandResult() { + } catch (final ConsoleAllocException e) { + //Show exception and exists + NavigationView.this.post(new Runnable() { + @Override + public void run() { + Context ctx = getContext(); + Log.e(TAG, ctx.getString( + R.string.msgs_cant_create_console), e); + DialogHelper.showToast(ctx, + R.string.msgs_cant_create_console, + Toast.LENGTH_LONG); + ((Activity)ctx).finish(); + } + }); + return null; + + } catch (Exception ex) { + //End of loading data + if (NavigationView.this.mBreadcrumb != null) { + try { + NavigationView.this.mBreadcrumb.endLoading(); + } catch (Throwable ex2) { + /**NON BLOCK**/ + } + } + + //Capture exception (attach task, and use listener to do the anim) + ExceptionUtil.attachAsyncTask( + ex, + new AsyncTask<Object, Integer, Boolean>() { + private List<FileSystemObject> mTaskFiles = null; @Override - public void onSuccess() { - // Do animation - fadeEfect(false); + @SuppressWarnings({ + "unchecked", "unqualified-field-access" + }) + protected Boolean doInBackground(Object... taskParams) { + mTaskFiles = (List<FileSystemObject>)taskParams[0]; + return Boolean.TRUE; } + @Override - public void onFailed(Throwable cause) { - // Do animation - fadeEfect(false); + @SuppressWarnings("unqualified-field-access") + protected void onPostExecute(Boolean result) { + if (!result.booleanValue()) { + return; + } + onPostExecuteTask( + mTaskFiles, addToHistory, + isNewHistory, hasChanged, + searchInfo, fNewDir, scrollTo); } - @Override - public void onCancelled() { - // Do animation - fadeEfect(false); + }); + final OnRelaunchCommandResult exListener = + new OnRelaunchCommandResult() { + @Override + public void onSuccess() { + done(); + } + @Override + public void onFailed(Throwable cause) { + done(); + } + @Override + public void onCancelled() { + done(); + } + private void done() { + // Do animation + fadeEfect(false); + synchronized (mSync) { + mChangingDir = false; + mSync.notify(); } - }; - ExceptionUtil.translateException( - getContext(), ex, false, true, exListener); - } - return null; - } - - /** - * {@inheritDoc} - */ - @Override - protected void onPreExecute() { - // Do animation - fadeEfect(true); + } + }; + ExceptionUtil.translateException( + getContext(), ex, false, true, exListener); } + return null; + } + /** + * {@inheritDoc} + */ + @Override + protected void onPreExecute() { + // Do animation + fadeEfect(true); + } + /** + * {@inheritDoc} + */ + @Override + protected void onPostExecute(List<FileSystemObject> files) { + // This means an exception. This method will be recalled then + if (files != null) { + onPostExecuteTask( + files, addToHistory, isNewHistory, + hasChanged, searchInfo, fNewDir, scrollTo); - /** - * {@inheritDoc} - */ - @Override - protected void onPostExecute(List<FileSystemObject> files) { - if (files != null) { - onPostExecuteTask( - files, addToHistory, isNewHistory, - hasChanged, searchInfo, fNewDir, scrollTo); + // Do animation + fadeEfect(false); - // Do animation - fadeEfect(false); + synchronized (mSync) { + mChangingDir = false; + mSync.notify(); } } + } - /** - * Method that performs a fade animation. - * - * @param out Fade out (true); Fade in (false) - */ - void fadeEfect(final boolean out) { - Activity activity = (Activity)getContext(); - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Animation fadeAnim = out ? - new AlphaAnimation(1, 0) : - new AlphaAnimation(0, 1); - fadeAnim.setDuration(50L); - fadeAnim.setFillAfter(true); - fadeAnim.setInterpolator(new AccelerateInterpolator()); - NavigationView.this.startAnimation(fadeAnim); - } - }); - } - }; - task.execute(fNewDir); - } + /** + * Method that performs a fade animation. + * + * @param out Fade out (true); Fade in (false) + */ + void fadeEfect(final boolean out) { + Activity activity = (Activity)getContext(); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + Animation fadeAnim = out ? + new AlphaAnimation(1, 0) : + new AlphaAnimation(0, 1); + fadeAnim.setDuration(50L); + fadeAnim.setFillAfter(true); + fadeAnim.setInterpolator(new AccelerateInterpolator()); + NavigationView.this.startAnimation(fadeAnim); + } + }); + } + }; + task.execute(fNewDir); } diff --git a/src/com/cyanogenmod/filemanager/util/MediaHelper.java b/src/com/cyanogenmod/filemanager/util/MediaHelper.java new file mode 100644 index 00000000..db818db3 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/util/MediaHelper.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.util; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.os.UserHandle; +import android.provider.BaseColumns; +import android.provider.MediaStore; +import android.text.TextUtils; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * A helper class with useful methods to extract media data. + */ +public final class MediaHelper { + + /** + * URIs that are relevant for determining album art; + * useful for content observer registration + */ + public static final Uri[] RELEVANT_URIS = new Uri[] { + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI + }; + + /** + * Method that returns an array with all the unique albums paths and ids. + * + * @param cr The ContentResolver + * @return Map<String, Long> The albums map + */ + public static Map<String, Long> getAllAlbums(ContentResolver cr) { + Map<String, Long> albums = new HashMap<String, Long>(); + final String[] projection = + { + "distinct " + MediaStore.Audio.Media.ALBUM_ID, + "substr(" + MediaStore.Audio.Media.DATA + ", 0, length(" + + MediaStore.Audio.Media.DATA + ") - length(" + + MediaStore.Audio.Media.DISPLAY_NAME + "))" + }; + final String where = MediaStore.Audio.Media.IS_MUSIC + " = ?"; + Cursor c = cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + projection, where, new String[]{"1"}, null); + if (c != null) { + try { + while (c.moveToNext()) { + long albumId = c.getLong(0); + String albumPath = c.getString(1); + albums.put(albumPath, albumId); + } + } finally { + c.close(); + } + } + return albums; + } + + /** + * Method that returns the album thumbnail path by its identifier. + * + * @param cr The ContentResolver + * @param albumId The album identifier to search + * @return String The album thumbnail path + */ + public static String getAlbumThumbnailPath(ContentResolver cr, long albumId) { + final String[] projection = {MediaStore.Audio.Albums.ALBUM_ART}; + final String where = BaseColumns._ID + " = ?"; + Cursor c = cr.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, + projection, where, new String[]{String.valueOf(albumId)}, null); + try { + if (c != null && c.moveToNext()) { + return c.getString(0); + } + } finally { + if (c != null) { + c.close(); + } + } + return null; + } + + private static final String EMULATED_STORAGE_SOURCE = System.getenv("EMULATED_STORAGE_SOURCE"); + private static final String EMULATED_STORAGE_TARGET = System.getenv("EMULATED_STORAGE_TARGET"); + private static final String EXTERNAL_STORAGE = System.getenv("EXTERNAL_STORAGE"); + + /** + * Method that converts a not standard media mount path to a standard media path + * + * @param path The path to normalize + * @return String The normalized media path + */ + public static String normalizeMediaPath(String path) { + // Retrieve all the paths and check that we have this environment vars + if (TextUtils.isEmpty(EMULATED_STORAGE_SOURCE) || + TextUtils.isEmpty(EMULATED_STORAGE_TARGET) || + TextUtils.isEmpty(EXTERNAL_STORAGE)) { + return path; + } + + // We need to convert EMULATED_STORAGE_SOURCE -> EMULATED_STORAGE_TARGET + if (path.startsWith(EMULATED_STORAGE_SOURCE)) { + path = path.replace(EMULATED_STORAGE_SOURCE, EMULATED_STORAGE_TARGET); + } + // We need to convert EXTERNAL_STORAGE -> EMULATED_STORAGE_TARGET / userId + if (path.startsWith(EXTERNAL_STORAGE)) { + final String userId = String.valueOf(UserHandle.myUserId()); + final String target = new File(EMULATED_STORAGE_TARGET, userId).getAbsolutePath(); + path = path.replace(EXTERNAL_STORAGE, target); + } + return path; + } +} diff --git a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java b/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java index da37d3f5..5ddb3da6 100644 --- a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java +++ b/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java @@ -553,4 +553,44 @@ public final class MimeTypeHelper { return mimeTypeExpression.replaceAll("\\*", ".\\*"); //$NON-NLS-1$ //$NON-NLS-2$ } + + /** + * Class for resolve known mime types + */ + public static final class KnownMimeTypeResolver { + private static final String MIME_TYPE_APK = "application/vnd.android.package-archive"; + + /** + * Method that returns if the FileSystemObject is an Android app. + * + * @param context The current context + * @param fso The FileSystemObject to check + * @return boolean If the FileSystemObject is an Android app. + */ + public static boolean isAndroidApp(Context context, FileSystemObject fso) { + return MIME_TYPE_APK.equals(MimeTypeHelper.getMimeType(context, fso)); + } + + /** + * Method that returns if the FileSystemObject is an image file. + * + * @param context The current context + * @param fso The FileSystemObject to check + * @return boolean If the FileSystemObject is an image file. + */ + public static boolean isImage(Context context, FileSystemObject fso) { + return MimeTypeHelper.getCategory(context, fso).compareTo(MimeTypeCategory.IMAGE) == 0; + } + + /** + * Method that returns if the FileSystemObject is an video file. + * + * @param context The current context + * @param fso The FileSystemObject to check + * @return boolean If the FileSystemObject is an video file. + */ + public static boolean isVideo(Context context, FileSystemObject fso) { + return MimeTypeHelper.getCategory(context, fso).compareTo(MimeTypeCategory.VIDEO) == 0; + } + } } |