From 018c616026bea96595031c0e61fd773a46f7a9b6 Mon Sep 17 00:00:00 2001 From: herriojr Date: Fri, 14 Aug 2015 15:51:40 -0700 Subject: Added in the MFU Screen and data layer. This implementation includes the MFU for keeping track of the top 5 most frequently accessed files. It is implemented in such a way that we can easily change algorithms if need be. Change-Id: Ib9b068deb5ac66f4cff99128396318133d4729c0 Ticket: CLOUD-48 --- AndroidManifest.xml | 4 + res/drawable/circle_large.xml | 25 ++ res/drawable/ic_frequent_placeholder.xml | 26 ++ res/layout/home_fragment.xml | 24 +- res/layout/mstaru_list.xml | 78 +++++ res/layout/mstaru_row.xml | 50 ++++ res/layout/navigation.xml | 3 +- res/values/colors.xml | 12 + res/values/ids.xml | 2 + res/values/strings.xml | 8 +- .../filemanager/FileManagerApplication.java | 8 + .../filemanager/activities/MainActivity.java | 67 ++++- .../adapters/FileSystemObjectAdapter.java | 8 +- .../filemanager/controllers/MStarUController.java | 129 +++++++++ .../mstaru/IMostStarUsedFilesManager.java | 82 ++++++ .../mstaru/MostFrequentlyUsedContract.java | 42 +++ .../mstaru/MostFrequentlyUsedManager.java | 287 ++++++++++++++++++ .../mstaru/MostFrequentlyUsedProvider.java | 320 +++++++++++++++++++++ .../filemanager/service/MoveFileService.java | 3 +- src/com/cyanogenmod/filemanager/ui/IconHolder.java | 134 ++++----- .../cyanogenmod/filemanager/ui/IconRenderer.java | 115 ++++++++ .../filemanager/ui/policy/DeleteActionPolicy.java | 4 + .../filemanager/ui/policy/IntentsActionPolicy.java | 7 + .../filemanager/ui/widgets/NavigationView.java | 14 + 24 files changed, 1358 insertions(+), 94 deletions(-) create mode 100644 res/drawable/circle_large.xml create mode 100644 res/drawable/ic_frequent_placeholder.xml create mode 100644 res/layout/mstaru_list.xml create mode 100644 res/layout/mstaru_row.xml create mode 100644 src/com/cyanogenmod/filemanager/controllers/MStarUController.java create mode 100644 src/com/cyanogenmod/filemanager/mstaru/IMostStarUsedFilesManager.java create mode 100644 src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedContract.java create mode 100644 src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedManager.java create mode 100644 src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java create mode 100644 src/com/cyanogenmod/filemanager/ui/IconRenderer.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 57a81b06..e0b04339 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -76,6 +76,10 @@ android:authorities="com.cyanogenmod.filemanager.providers.index" android:name=".providers.MimeTypeIndexProvider"/> + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_frequent_placeholder.xml b/res/drawable/ic_frequent_placeholder.xml new file mode 100644 index 00000000..521e624a --- /dev/null +++ b/res/drawable/ic_frequent_placeholder.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/home_fragment.xml b/res/layout/home_fragment.xml index 2dc24c03..c7d687fd 100644 --- a/res/layout/home_fragment.xml +++ b/res/layout/home_fragment.xml @@ -71,19 +71,23 @@ android:gravity="center" /> + + + + + - - - - + android:layout_width="match_parent" + android:layout_height="192dp" + android:layout_margin="8dp" + android:visibility="gone" + card_view:cardCornerRadius="4dp" /> diff --git a/res/layout/mstaru_list.xml b/res/layout/mstaru_list.xml new file mode 100644 index 00000000..d4b870f6 --- /dev/null +++ b/res/layout/mstaru_list.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/mstaru_row.xml b/res/layout/mstaru_row.xml new file mode 100644 index 00000000..fde4e3d2 --- /dev/null +++ b/res/layout/mstaru_row.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/navigation.xml b/res/layout/navigation.xml index 0f3a1162..bb2338de 100644 --- a/res/layout/navigation.xml +++ b/res/layout/navigation.xml @@ -19,7 +19,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/navigation_drawer_background"> diff --git a/res/values/colors.xml b/res/values/colors.xml index 592b5007..ef3ee9fb 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -32,6 +32,11 @@ #ff000000 + + #de000000 + + #8b000000 + #8bc34a @@ -56,8 +61,15 @@ #f2f2f2 + #ffffff + + #efefef + #8a000000 + #75000000 + #42000000 + #1C000000 #df000000 diff --git a/res/values/ids.xml b/res/values/ids.xml index d9a77162..fc500be6 100644 --- a/res/values/ids.xml +++ b/res/values/ids.xml @@ -23,4 +23,6 @@ + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 24d6b2f5..f44cba21 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -472,8 +472,11 @@ File]]> %1$s Analyzing\u2026]]> + + Opening\u2026 Opening\u2026 + Opening]]> %1$s]]> From]]> %2$s The extracting operation was completed successfully. The data was extracted to %1$s. @@ -924,7 +927,7 @@ Unable to complete action RETRY UPGRADE - + Unable to fetch file system info for this path @@ -950,4 +953,7 @@ Tap to retry Retry Upgrade + + Frequent + Access the files you use the most here. diff --git a/src/com/cyanogenmod/filemanager/FileManagerApplication.java b/src/com/cyanogenmod/filemanager/FileManagerApplication.java index e3c88d6c..245d4241 100644 --- a/src/com/cyanogenmod/filemanager/FileManagerApplication.java +++ b/src/com/cyanogenmod/filemanager/FileManagerApplication.java @@ -32,6 +32,7 @@ import com.cyanogenmod.filemanager.console.ConsoleBuilder; import com.cyanogenmod.filemanager.console.ConsoleHolder; import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; import com.cyanogenmod.filemanager.console.shell.PrivilegedConsole; +import com.cyanogenmod.filemanager.mstaru.IMostStarUsedFilesManager; import com.cyanogenmod.filemanager.preferences.AccessMode; import com.cyanogenmod.filemanager.preferences.FileManagerSettings; import com.cyanogenmod.filemanager.preferences.ObjectStringIdentifier; @@ -168,6 +169,8 @@ public final class FileManagerApplication extends Application { } }; + private IMostStarUsedFilesManager mMStarUManager; + /** * {@inheritDoc} @@ -194,6 +197,7 @@ public final class FileManagerApplication extends Application { // Schedule in case not scheduled (i.e. never booted with this app on device SecureCacheCleanupService.scheduleCleanup(getApplicationContext()); + mMStarUManager = IMostStarUsedFilesManager.Factory.newInstance(this); } /** @@ -620,4 +624,8 @@ public final class FileManagerApplication extends Application { "Failed to read optional shell commands.", e); //$NON-NLS-1$ } } + + public IMostStarUsedFilesManager getMStarUManager() { + return mMStarUManager; + } } diff --git a/src/com/cyanogenmod/filemanager/activities/MainActivity.java b/src/com/cyanogenmod/filemanager/activities/MainActivity.java index a6bba46e..9b3a4c84 100755 --- a/src/com/cyanogenmod/filemanager/activities/MainActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/MainActivity.java @@ -26,6 +26,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.Outline; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; import android.net.Uri; import android.nfc.NfcAdapter; import android.os.Bundle; @@ -59,16 +64,22 @@ import com.cyanogenmod.filemanager.FileManagerApplication; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences; import com.cyanogenmod.filemanager.adapters.QuickSearchAdapter; +import com.cyanogenmod.filemanager.controllers.MStarUController; import com.cyanogenmod.filemanager.controllers.NavigationDrawerController; import com.cyanogenmod.filemanager.model.Bookmark; import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.mstaru.IMostStarUsedFilesManager; 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.fragments.NavigationFragment; import com.cyanogenmod.filemanager.ui.fragments.NavigationFragment.OnGoHomeRequestListener; +import com.cyanogenmod.filemanager.ui.policy.InfoActionPolicy; +import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy; import com.cyanogenmod.filemanager.ui.widgets.NavigationView.OnBackRequestListener; import com.cyanogenmod.filemanager.util.FileHelper; +import com.cyanogenmod.filemanager.util.MimeTypeHelper; import com.cyanogenmod.filemanager.util.StorageHelper; import java.io.File; @@ -89,7 +100,7 @@ import java.util.List; * the app is killed, is restarted from his initial state. */ public class MainActivity extends ActionBarActivity - implements OnItemClickListener, OnBackRequestListener, OnGoHomeRequestListener { + implements OnItemClickListener, OnBackRequestListener, OnGoHomeRequestListener, MStarUController.OnClickListener, IMostStarUsedFilesManager.IFileObserver { private static final String TAG = MainActivity.class.getSimpleName(); @@ -244,6 +255,8 @@ public class MainActivity extends ActionBarActivity } }; + private MStarUController mMStarUController; + /** * {@inheritDoc} */ @@ -275,6 +288,14 @@ public class MainActivity extends ActionBarActivity newFilter.addDataScheme(ContentResolver.SCHEME_FILE); registerReceiver(mNotificationReceiver, newFilter); + mMStarUController = new MStarUController(this, findViewById(R.id.mstaru), this); + + mToolBar = (Toolbar) findViewById(R.id.material_toolbar); + setSupportActionBar(mToolBar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu); + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); NavigationView navigationDrawer = (NavigationView) findViewById(R.id.navigation_view); @@ -370,6 +391,25 @@ public class MainActivity extends ActionBarActivity searchView.setFocusable(false); } + @Override + public void onStart() { + super.onStart(); + + ((FileManagerApplication)getApplicationContext()).getMStarUManager().registerObserver(this); + } + + @Override + public void onStop() { + ((FileManagerApplication)getApplicationContext()).getMStarUManager().unregisterObserver(this); + + super.onStop(); + } + + @Override + public void onFilesChanged(List files) { + mMStarUController.replaceData(files); + } + /** * {@inheritDoc} */ @@ -532,17 +572,6 @@ public class MainActivity extends ActionBarActivity } } - @Override - protected void onStart() { - super.onStart(); - - mToolBar = (Toolbar) findViewById(R.id.material_toolbar); - setSupportActionBar(mToolBar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); - getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu); - } - private void initQuickSearch() { GridView gridview = (GridView) findViewById(R.id.quick_search_view); QuickSearchAdapter quickSearchAdapter = new QuickSearchAdapter(this, R.layout.quick_search_item); @@ -729,4 +758,18 @@ public class MainActivity extends ActionBarActivity public int getColorForPath(String path) { return mNavigationDrawerController.getColorForPath(path); } + + private void showFrequentFiles(List files) { + mMStarUController.replaceData(files); + } + + @Override + public void onItemClick(FileSystemObject fso) { + IntentsActionPolicy.openFileSystemObject(this, null, fso, false, null); + } + + @Override + public void onDetailsClick(FileSystemObject fso) { + InfoActionPolicy.showPropertiesDialog(this, fso, null); + } } diff --git a/src/com/cyanogenmod/filemanager/adapters/FileSystemObjectAdapter.java b/src/com/cyanogenmod/filemanager/adapters/FileSystemObjectAdapter.java index 6e91c14d..bb5f940d 100644 --- a/src/com/cyanogenmod/filemanager/adapters/FileSystemObjectAdapter.java +++ b/src/com/cyanogenmod/filemanager/adapters/FileSystemObjectAdapter.java @@ -546,12 +546,18 @@ public class FileSystemObjectAdapter private void setFileIcon(ImageView view, final int iconId, FileSystemObject fso) { // Use iconholder to check for thumbnail final ICallback callback = new ICallback() { + + @Override + public void onPreExecute(ImageView imageView) { + + } + @Override public void onLoaded(ImageView imageView, Drawable icon) { if (icon == null) { // Icon holder didn't have anything at the moment, set default. int colorId = MimeTypeHelper.getIconColorFromIconId(getContext(), iconId); - setIcon(mRes, imageView, mRes.getDrawable(iconId), + setIcon(mRes, imageView, mRes.getDrawable(iconId, null), mRes.getColor(R.color.navigation_view_icon_unselected), R.drawable.ic_icon_background, mRes.getColor(colorId)); diff --git a/src/com/cyanogenmod/filemanager/controllers/MStarUController.java b/src/com/cyanogenmod/filemanager/controllers/MStarUController.java new file mode 100644 index 00000000..74747a61 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/controllers/MStarUController.java @@ -0,0 +1,129 @@ +package com.cyanogenmod.filemanager.controllers; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.ui.IconRenderer; +import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.preferences.FileManagerSettings; +import com.cyanogenmod.filemanager.preferences.Preferences; +import com.cyanogenmod.filemanager.ui.IconHolder; +import com.cyanogenmod.filemanager.util.MimeTypeHelper; + +import java.util.List; + +/** + * This is meant to make this section more transferable + */ +public class MStarUController { + + public interface OnClickListener { + void onItemClick(FileSystemObject fso); + void onDetailsClick(FileSystemObject fso); + } + + private class ViewHolder { + /* package */ ImageView fileImage; + /* package */ TextView fileName; + /* package */ TextView parent; + /* package */ View details; + + FileSystemObject fso; + + public ViewHolder(View row) { + fileImage = (ImageView)row.findViewById(R.id.file_image); + fileName = (TextView)row.findViewById(R.id.file_name); + parent = (TextView)row.findViewById(R.id.file_parent); + details = row.findViewById(R.id.file_details); + } + } + + private View.OnClickListener mOnItemClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + MStarUController.ViewHolder h = + (MStarUController.ViewHolder)view.getTag(R.id.tag_viewholder); + + if (mOnClickListener != null) { + switch (view.getId()) { + case R.id.file_details: + mOnClickListener.onDetailsClick(h.fso); + break; + default: + mOnClickListener.onItemClick(h.fso); + break; + + } + } + } + }; + + private ViewGroup mStarUGroup; + + private ViewGroup mStarUEmpty; + + private MStarUController.OnClickListener mOnClickListener; + + private Context mContext; + + private IconHolder mImageLoader; + + public MStarUController(Context context, View root, MStarUController.OnClickListener l) { + mContext = context; + mStarUGroup = (ViewGroup)root.findViewById(R.id.mstaru_list); + mStarUEmpty = (ViewGroup)root.findViewById(R.id.mstaru_empty); + // This is kind of crap since it won't pick up changes if we leave the screen and come back + FileManagerSettings displayThumbsPref = FileManagerSettings.SETTINGS_DISPLAY_THUMBS; + final boolean displayThumbs = + Preferences.getSharedPreferences().getBoolean( + displayThumbsPref.getId(), + ((Boolean)displayThumbsPref.getDefaultValue()).booleanValue()); + mImageLoader = new IconHolder(context, displayThumbs); + mOnClickListener = l; + } + + public void replaceData(List files) { + if (files == null || files.isEmpty()) { + mStarUGroup.setVisibility(View.GONE); + mStarUEmpty.setVisibility(View.VISIBLE); + return; + } + mStarUGroup.setVisibility(View.VISIBLE); + mStarUEmpty.setVisibility(View.GONE); + + int size = Math.min(files.size(), mStarUGroup.getChildCount()); + int i = 0; + for (; i < size; i++) { + final View row = mStarUGroup.getChildAt(i); + final FileSystemObject fso = files.get(i); + + row.setVisibility(View.VISIBLE); + MStarUController.ViewHolder h = + (MStarUController.ViewHolder)row.getTag(R.id.tag_viewholder); + + if (h == null) { + h = new MStarUController.ViewHolder(row); + row.setTag(R.id.tag_viewholder, h); + row.setOnClickListener(mOnItemClickListener); + h.details.setOnClickListener(mOnItemClickListener); + h.details.setTag(R.id.tag_viewholder, h); + } + h.fso = fso; + + final int mimeTypeIconId = MimeTypeHelper.getIcon(mContext, fso); + IconHolder.ICallback callback = new IconRenderer(mContext, mimeTypeIconId); + mImageLoader.loadDrawable(h.fileImage, fso, mimeTypeIconId, callback); + + h.fileName.setText(fso.getName()); + h.parent.setText(fso.getParent()); + } + size = mStarUGroup.getChildCount(); + for (; i < size; i++) { + View row = mStarUGroup.getChildAt(i); + row.setVisibility(View.GONE); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/mstaru/IMostStarUsedFilesManager.java b/src/com/cyanogenmod/filemanager/mstaru/IMostStarUsedFilesManager.java new file mode 100644 index 00000000..1ad6ff5c --- /dev/null +++ b/src/com/cyanogenmod/filemanager/mstaru/IMostStarUsedFilesManager.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 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.mstaru; + +import android.content.Context; +import com.cyanogenmod.filemanager.model.FileSystemObject; + +import java.util.List; + +public interface IMostStarUsedFilesManager { + class Factory { + public static IMostStarUsedFilesManager newInstance(Context context) { + return new MostFrequentlyUsedManager(context); + } + + private Factory() {} + } + + interface IFileObserver { + void onFilesChanged(List files); + } + + /** + * Registers that a file has been accessed. + * + * @param fso The file system object + * + * @return Whether the operation succeeded or not + */ + boolean notifyAccessed(FileSystemObject fso); + + void notifyAccessedAsync(FileSystemObject fso); + + /** + * Registers that a file system object has been moved. + * + * @param from The original location of the object + * @param to The new location of the object + * + * @return Whether the operation succeeded or not + */ + boolean notifyMoved(FileSystemObject from, FileSystemObject to); + + void notifyMovedAsync(FileSystemObject from, FileSystemObject to); + + /** + * Registers that a file system object has been deleted. + * + * @param fso The file system object + * + * @return Whether the operation succeeded or not + */ + boolean notifyDeleted(FileSystemObject fso); + + void notifyDeletedAsync(FileSystemObject fso); + + /** + * Gets the list of important files we may want to show. If you need updates as things change + * consider using the register/unregister + * + * @return The list of files or null if there was a problem. + */ + List getFiles(); + + void registerObserver(IFileObserver observer); + + void unregisterObserver(IFileObserver observer); +} diff --git a/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedContract.java b/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedContract.java new file mode 100644 index 00000000..e3adbc35 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedContract.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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.mstaru; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * Created by herriojr on 8/10/15. + */ +/* package */ interface MostFrequentlyUsedContract { + String AUTHORITY = "com.cyanogenmod.filemanager.mfu"; + + class Item implements BaseColumns { + /* package */ static final String TABLE_NAME = "items"; + + public static final String FOLDER = TABLE_NAME; + + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/" + AUTHORITY + "." + FOLDER; + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + FOLDER); + + /* package */ static final String _LAST_DEGRADE_TIMESTAMP = "_last_degrade"; + public static final String KEY = "key"; + public static final String COUNT = "count"; + } +} diff --git a/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedManager.java b/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedManager.java new file mode 100644 index 00000000..63dd6695 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedManager.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2015 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.mstaru; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.NonNull; +import android.util.Log; +import com.cyanogenmod.filemanager.FileManagerApplication; +import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException; +import com.cyanogenmod.filemanager.console.CommandNotFoundException; +import com.cyanogenmod.filemanager.console.ConsoleAllocException; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.OperationTimeoutException; +import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.mstaru.MostFrequentlyUsedContract.Item; +import com.cyanogenmod.filemanager.util.CommandHelper; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * This is meant to only be used with the Application Context. If you break it, you bought it. + */ +public class MostFrequentlyUsedManager implements IMostStarUsedFilesManager { + private static final String TAG = MostFrequentlyUsedManager.class.getSimpleName(); + + private static class UIHandler extends Handler { + public static final int MSG_ITEMS = 0; + + private WeakReference mMgr; + + public UIHandler(MostFrequentlyUsedManager mgr) { + super(Looper.getMainLooper()); + mMgr = new WeakReference<>(mgr); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_ITEMS: + Log.d(TAG, "Notifying of changes"); + MostFrequentlyUsedManager mgr = mMgr.get(); + if (mgr != null) { + mgr.notifyChange((List) msg.obj); + } + break; + } + } + } + + private static final String[] PROJECTION = { + Item.KEY + }; + private static final int PROJECTION_KEY = 0; + + private ContentResolver mResolver; + + private ContentObserver mObserver; + private HandlerThread mThread; + + private Handler mUIHandler = new UIHandler(this); + + private Set mObservers; + + private List mFiles; + + /* package */ MostFrequentlyUsedManager(@NonNull Context context) { + context = context.getApplicationContext(); + mResolver = context.getContentResolver(); + mObservers = new HashSet<>(); + } + + private static boolean isMainThread() { + return Looper.getMainLooper() == Looper.myLooper(); + } + + private static FileSystemObject parseKey(@NonNull String key) { + try { + return CommandHelper.getFileInfo( + FileManagerApplication.getInstance(), key, false, null); + } catch (InsufficientPermissionsException + | InvalidCommandDefinitionException + | ConsoleAllocException + | ExecutionException + | OperationTimeoutException + | NoSuchFileOrDirectory + | CommandNotFoundException + | IOException e) { + return null; + } + } + + private static String keyFor(@NonNull FileSystemObject fso) { + Log.d(TAG, "Generating key for " + fso.getFullPath()); + return fso.getFullPath(); + } + + @Override + public boolean notifyAccessed(@NonNull FileSystemObject fso) throws IllegalStateException { + if (isMainThread()) { + throw new IllegalStateException("Must not be invoked from the main thread."); + } + + ContentValues values = new ContentValues(); + values.put(MostFrequentlyUsedContract.Item.KEY, keyFor(fso)); + + mResolver.insert(Item.CONTENT_URI, values); + return true; + } + + @Override + public void notifyAccessedAsync(@NonNull final FileSystemObject fso) { + new AsyncTask() { + @Override + public Void doInBackground(Void ... params) { + notifyAccessed(fso); + return null; + } + }.execute(); + } + + @Override + public boolean notifyMoved(@NonNull FileSystemObject from, + @NonNull FileSystemObject to) { + if (isMainThread()) { + throw new IllegalStateException("Must not be invoked from the main thread."); + } + + ContentValues values = new ContentValues(); + values.put(MostFrequentlyUsedContract.Item.KEY, keyFor(to)); + + mResolver.update(Item.CONTENT_URI, + values, + MostFrequentlyUsedContract.Item.KEY + "=?", + new String[]{ + keyFor(from), + }); + return true; + } + + @Override + public void notifyMovedAsync(@NonNull final FileSystemObject from, + @NonNull final FileSystemObject to) { + new AsyncTask() { + @Override + public Void doInBackground(Void ... params) { + notifyMoved(from, to); + return null; + } + }.execute(); + } + + @Override + public boolean notifyDeleted(@NonNull FileSystemObject fso) { + if (isMainThread()) { + throw new IllegalStateException("Must not be invoked from the main thread."); + } + + mResolver.delete(MostFrequentlyUsedContract.Item.CONTENT_URI, + MostFrequentlyUsedContract.Item.KEY + "=?", + new String[]{ + keyFor(fso) + }); + return true; + } + + @Override + public void notifyDeletedAsync(@NonNull final FileSystemObject fso) { + new AsyncTask() { + @Override + public Void doInBackground(Void ... params) { + notifyDeleted(fso); + return null; + } + }.execute(); + } + + @Override + public List getFiles() { + if (isMainThread()) { + throw new IllegalStateException("Must not be invoked from the main thread."); + } + + ArrayList files; + Cursor c = null; + try { + files = new ArrayList(); + c = mResolver.query(Item.CONTENT_URI, PROJECTION, null, null, null); + while (c.moveToNext()) { + FileSystemObject o = parseKey(c.getString(PROJECTION_KEY)); + if (o != null) { + files.add(o); + } + } + } finally { + if (c != null) { + c.close(); + } + } + + return files; + } + + @Override + public void registerObserver(@NonNull IFileObserver observer) { + if (!isMainThread()) { + throw new IllegalStateException("Must be invoked on the main thread."); + } + mObservers.add(observer); + + if (mObserver == null) { + mThread = new HandlerThread("FrequentObserver"); + mThread.start(); + + mObserver = new ContentObserver(new Handler(mThread.getLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + List files = getFiles(); + Log.d(TAG, "Got " + files.size() + " items"); + // post back to the main thread + mUIHandler.obtainMessage(UIHandler.MSG_ITEMS, files).sendToTarget(); + } + }; + mResolver.registerContentObserver(Item.CONTENT_URI, true, mObserver); + // kick it off, so we get the first item + mObserver.dispatchChange(false, Item.CONTENT_URI); + } else { + if (mFiles == null) { + notifyChange(mFiles); + } else { + // either we haven't received a response yet, or it was null, either way, we don't + // need to dispatch it again + } + } + } + + @Override + public void unregisterObserver(@NonNull IFileObserver observer) { + if (!isMainThread()) { + throw new IllegalStateException("Must be invoked on the main thread."); + } + + mObservers.remove(observer); + if (mObservers.isEmpty()) { + mResolver.unregisterContentObserver(mObserver); + mObserver = null; + mThread.getLooper().quit(); + } + } + + /* package */ void notifyChange(List files) { + mFiles = files; + for (IFileObserver o : mObservers) { + o.onFilesChanged(files); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java b/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java new file mode 100644 index 00000000..552a796e --- /dev/null +++ b/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2015 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.mstaru; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.database.sqlite.SQLiteStatement; +import android.net.Uri; + +import android.util.Log; +import com.cyanogenmod.filemanager.mstaru.MostFrequentlyUsedContract.Item; + +import java.util.HashMap; + +/** + * This provider provides access to help in keeping track and querying of the most frequently + * accessed items. + */ +public class MostFrequentlyUsedProvider extends ContentProvider { + private static final String TAG = MostFrequentlyUsedProvider.class.getSimpleName(); + + private static final boolean DEBUG = true; + + private static class MFUOpenHelper extends SQLiteOpenHelper { + + private static final String NAME = "mfu"; + private static final int VERSION = 1; + + /** + * {@inheritDoc} + */ + public MFUOpenHelper(Context context) { + super(context, NAME, null, VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + MostFrequentlyUsedContract.Item.TABLE_NAME + "(" + + MostFrequentlyUsedContract.Item._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Item.KEY + " TEXT," + + Item.COUNT + " INTEGER NOT NULL DEFAULT 1," + + Item._LAST_DEGRADE_TIMESTAMP + + " INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP," + + "UNIQUE(" + MostFrequentlyUsedContract.Item.KEY + ')' + + ");" + ); + + db.execSQL("CREATE INDEX " + Item.TABLE_NAME + "_count_idx" + + " ON " + Item.TABLE_NAME + "(" + Item.COUNT + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // No upgrades yet + } + } + + private static final String STMT_UPDATE_COUNT = "UPDATE " + Item.TABLE_NAME + + " SET " + Item.COUNT + "=" + Item.COUNT + "+1, " + + Item._LAST_DEGRADE_TIMESTAMP + "=CURRENT_TIMESTAMP" + + " WHERE " + Item.KEY + "=?;"; + + private static final String STMT_DEGRADE_COUNT = "UPDATE " + Item.TABLE_NAME + + " SET " + Item.COUNT +"=" + Item.COUNT + "-CAST(" + + "JULIANDAY(CURRENT_TIMESTAMP)" + + "-JULIANDAY(" + Item._LAST_DEGRADE_TIMESTAMP + ")" + + " AS INT)," + + Item._LAST_DEGRADE_TIMESTAMP + "=CURRENT_TIMESTAMP" + + " WHERE " + + "JULIANDAY() - JULIANDAY(" + Item._LAST_DEGRADE_TIMESTAMP + ") > 1;"; + + + private static final int MAX_RESULTS = 10; + + private static UriMatcher sUriMatcher; + + private static final int MATCH_ITEMS = 0; + + private static HashMap sFilesProjectionMap; + + static { + sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + sUriMatcher.addURI(MostFrequentlyUsedContract.AUTHORITY, "/" + Item.FOLDER, MATCH_ITEMS); + + sFilesProjectionMap = new HashMap(); + sFilesProjectionMap.put(Item._ID, Item._ID); + sFilesProjectionMap.put(MostFrequentlyUsedContract.Item.KEY, + MostFrequentlyUsedContract.Item.KEY); + sFilesProjectionMap.put(Item.COUNT, Item.COUNT); + } + + private MFUOpenHelper mOpenHelper; + + private ContentResolver mResolver; + + @Override + public boolean onCreate() { + mOpenHelper = new MFUOpenHelper(getContext()); + mResolver = getContext().getContentResolver(); + + return true; + } + + /** + * This degrades all columns by the number of days since the last degrade for each column. + * + * Eventually, everything will end up in the negatives, however, given our logic, we always keep + * at least {@link #MAX_RESULTS} items if that number exists. + * + * @param db + */ + private int degrade(SQLiteDatabase db) { + try { + SQLiteStatement stmt = db.compileStatement(STMT_DEGRADE_COUNT); + int cnt = stmt.executeUpdateDelete(); + + if (DEBUG) Log.d(TAG, "Degraded " + cnt + " items"); + + return cnt; + } catch(SQLiteException e) { + Log.e(TAG, "Failed to degrade ", e); + // Do nothing, we'll try again some other time + } + return 0; + } + + private int prune(SQLiteDatabase db) { + Cursor c; + try { + c = db.query(Item.TABLE_NAME, new String[]{Item._ID}, null, null, null, null, + Item.COUNT + " DESC", "" + MAX_RESULTS); + StringBuilder b = new StringBuilder(); + + String[] selectionArgs = new String[c.getCount()]; + b.append(Item.COUNT) + .append(" < 0"); + int i = 0; + + while (c.moveToNext()) { + b.append(" AND ") + .append(Item._ID) + .append("!=?"); + selectionArgs[i++] = "" + c.getLong(0); + } + int cnt = db.delete(Item.TABLE_NAME, b.toString(), selectionArgs); + if (DEBUG) Log.d(TAG, "Pruned " + cnt + " items"); + + return cnt; + } catch (SQLiteException e) { + // We can safely ignore this and just prune next time + } + return 0; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + if (sortOrder == null) { + sortOrder = Item.COUNT + " DESC"; + } + + int which = sUriMatcher.match(uri); + switch(which) { + case MATCH_ITEMS: + qb.setTables(Item.TABLE_NAME); + qb.setProjectionMap(sFilesProjectionMap); + break; + default: + throw new UnsupportedOperationException("Unknown uri " + uri); + } + + SQLiteDatabase db = null; + try { + db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + degrade(db); + prune(db); + db.setTransactionSuccessful(); + } catch(SQLiteException e) { + // Do nothing + } finally { + if (db != null) db.endTransaction(); + } + + db = mOpenHelper.getWritableDatabase(); + + Cursor c = qb.query( + db, + projection, + selection, + selectionArgs, + null, + null, + sortOrder, + "" + MAX_RESULTS); + return c; + } + + @Override + public String getType(Uri uri) { + switch(sUriMatcher.match(uri)) { + case MATCH_ITEMS: + return Item.CONTENT_TYPE; + default: + return null; + } + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + int which = sUriMatcher.match(uri); + String table; + switch(which) { + case MATCH_ITEMS: + table = Item.TABLE_NAME; + break; + default: + throw new UnsupportedOperationException("Unknown uri " + uri); + } + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long id = db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_IGNORE); + if (id == -1) { + SQLiteStatement stmt = db.compileStatement(STMT_UPDATE_COUNT); + stmt.bindString(1, values.getAsString(Item.KEY)); + int cnt = stmt.executeUpdateDelete(); + if (cnt > 0) { + Cursor c = null; + try { + String selection = Item.KEY + "=?"; + String[] selectionArgs = new String[]{ + values.getAsString(Item.KEY), + }; + c = db.query(MostFrequentlyUsedContract.Item.TABLE_NAME, + new String[]{Item._ID}, + selection, + selectionArgs, + null, null, null, null); + if (c.moveToFirst()) { + id = c.getLong(0); + } + } finally { + if (c != null) c.close(); + } + } + } + if (id > -1) { + mResolver.notifyChange(uri, null); + } + return Uri.withAppendedPath(uri, "" + id); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int which = sUriMatcher.match(uri); + String table; + switch(which) { + case MATCH_ITEMS: + table = MostFrequentlyUsedContract.Item.TABLE_NAME; + break; + default: + throw new UnsupportedOperationException("Unknown uri " + uri); + } + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int cnt = db.delete(table, selection, selectionArgs); + if (cnt > 0) { + mResolver.notifyChange(uri, null); + } + return cnt; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int which = sUriMatcher.match(uri); + String table; + switch(which) { + case MATCH_ITEMS: + table = Item.TABLE_NAME; + break; + default: + throw new UnsupportedOperationException("Unknown uri " + uri); + } + + values = new ContentValues(values); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int cnt = db.update(table, values, selection, selectionArgs); + values.put(Item.COUNT, Item.COUNT + " + 1"); + if (cnt > 0) { + mResolver.notifyChange(uri, null); + } + return cnt; + } +} diff --git a/src/com/cyanogenmod/filemanager/service/MoveFileService.java b/src/com/cyanogenmod/filemanager/service/MoveFileService.java index 21decfd1..e86150b4 100644 --- a/src/com/cyanogenmod/filemanager/service/MoveFileService.java +++ b/src/com/cyanogenmod/filemanager/service/MoveFileService.java @@ -168,9 +168,8 @@ public class MoveFileService extends IntentService { protected Boolean doInBackground(String... params) { mSrcPath = params[0]; mDstPath = params[1]; - String fileName = mSrcPath.substring(mSrcPath.lastIndexOf(File.separator) + 1); try { - CommandHelper.move(MoveFileService.this, mSrcPath, mDstPath, fileName, null, null); + CommandHelper.move(MoveFileService.this, mSrcPath, mDstPath, null); } catch (Exception e) { return Boolean.FALSE; } diff --git a/src/com/cyanogenmod/filemanager/ui/IconHolder.java b/src/com/cyanogenmod/filemanager/ui/IconHolder.java index 86443720..74b1fe55 100644 --- a/src/com/cyanogenmod/filemanager/ui/IconHolder.java +++ b/src/com/cyanogenmod/filemanager/ui/IconHolder.java @@ -38,6 +38,7 @@ 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; import com.cyanogenmod.filemanager.util.MimeTypeHelper.KnownMimeTypeResolver; import java.lang.ref.WeakReference; @@ -59,19 +60,21 @@ public class IconHolder { private final Map mIcons; // Themes based private final Map mAppIcons; // App based - private Map mAlbums; // Media albums - private final WeakHashMap mRequests; private final Context mContext; private final boolean mUseThumbs; - private boolean mNeedAlbumUpdate = true; private HandlerThread mWorkerThread; private Handler mWorkerHandler; public interface ICallback { - public void onLoaded(ImageView imageView, Drawable icon); + void onPreExecute(ImageView imageView); + void onLoaded(ImageView imageView, Drawable icon); + } + + public interface ITransform { + Drawable transform(Drawable d); } /** @@ -79,8 +82,10 @@ public class IconHolder { * TODO: Refactor this to have different loadables */ private static class Loadable { + private Context mContext; private WeakReference mCallback; + private ITransform mTransform; private static boolean sAlbumsDirty = true; private static Map sAlbums; @@ -88,16 +93,14 @@ public class IconHolder { WeakReference view; Drawable result; - public Loadable(Context context, ImageView view, FileSystemObject fso) { - this(context, view, fso, null); - } - - public Loadable(Context context, ImageView view, FileSystemObject fso, ICallback callback) { + public Loadable(Context context, ImageView view, FileSystemObject fso, ICallback callback, + ITransform transform) { this.mContext = context.getApplicationContext(); this.fso = fso; - this.view = new WeakReference(view); + this.view = new WeakReference<>(view); this.result = null; - this.mCallback = new WeakReference(callback); + this.mCallback = new WeakReference<>(callback); + this.mTransform = transform; } private static synchronized Map getAlbums(Context context) { @@ -113,7 +116,11 @@ public class IconHolder { } public boolean load() { - return (result = loadDrawable(fso)) != null; + result = loadDrawable(fso); + if (mTransform != null) { + result = mTransform.transform(result); + } + return result != null; } private Drawable loadDrawable(FileSystemObject fso) { @@ -271,8 +278,8 @@ public class IconHolder { super(); this.mContext = context; this.mUseThumbs = useThumbs; - this.mRequests = new WeakHashMap(); - this.mIcons = new HashMap(); + this.mRequests = new WeakHashMap<>(); + this.mIcons = new HashMap<>(); this.mAppIcons = new LinkedHashMap(MAX_CACHE, .75F, true) { private static final long serialVersionUID = 1L; @Override @@ -280,7 +287,6 @@ public class IconHolder { return size() > MAX_CACHE; } }; - this.mAlbums = new HashMap(); if (useThumbs) { final ContentResolver cr = mContext.getContentResolver(); for (Uri uri : MediaHelper.RELEVANT_URIS) { @@ -308,6 +314,10 @@ public class IconHolder { return dw; } + public void cancel(ImageView iconView) { + this.mRequests.remove(iconView); + } + /** * Clearing the selected Icon Cache * @param fso The Selected FileSystemObject reference @@ -327,37 +337,20 @@ public class IconHolder { * @param defaultIconId Resource ID to be used in case no specific drawable could be found */ public void loadDrawable(ImageView iconView, FileSystemObject fso, int defaultIconId) { - if (!mUseThumbs) { - iconView.setImageResource(defaultIconId); - return; - } - - // Is cached? - final String filePath = MediaHelper.normalizeMediaPath(fso.getFullPath()); - if (this.mAppIcons.containsKey(filePath)) { - iconView.setImageDrawable(this.mAppIcons.get(filePath)); - return; - } - - if (mWorkerThread == null) { - mWorkerThread = new HandlerThread("IconHolderLoader"); - mWorkerThread.start(); - mWorkerHandler = new WorkerHandler(mWorkerThread.getLooper()); - } - Loadable previousForView = mRequests.get(iconView); - if (previousForView != null) { - mWorkerHandler.removeMessages(MSG_LOAD, previousForView); - } - - Loadable loadable = new Loadable(mContext, iconView, fso); - mRequests.put(iconView, loadable); - iconView.setImageResource(defaultIconId); - - mWorkerHandler.obtainMessage(MSG_LOAD, loadable).sendToTarget(); + loadDrawable(iconView, fso, defaultIconId, null); } - public void cancel(ImageView iconView) { - this.mRequests.remove(iconView); + /** + * Method that returns a drawable reference of a FileSystemObject. + * + * @param iconView View to load the drawable into + * @param fso The FileSystemObject reference + * @param defaultIconId Resource ID to be used in case no specific drawable could be found + * @param callback ICallback to use when finished loading + */ + public void loadDrawable(ImageView iconView, FileSystemObject fso, int defaultIconId, + ICallback callback) { + loadDrawable(iconView, fso, defaultIconId, callback, null); } /** @@ -367,36 +360,43 @@ public class IconHolder { * @param fso The FileSystemObject reference * @param defaultIconId Resource ID to be used in case no specific drawable could be found * @param callback ICallback to use when finished loading + * @param transform The transfromer to apply to the drawable */ public void loadDrawable(ImageView iconView, FileSystemObject fso, int defaultIconId, - ICallback callback) { - if (!mUseThumbs) { - callback.onLoaded(iconView, null); - return; + ICallback callback, ITransform transform) { + Drawable icon = null; + if (callback != null) { + callback.onPreExecute(iconView); } - // Is cached? - final String filePath = MediaHelper.normalizeMediaPath(fso.getFullPath()); - if (this.mAppIcons.containsKey(filePath)) { - callback.onLoaded(iconView, mAppIcons.get(filePath)); - return; - } + if (!mUseThumbs) { + icon = mContext.getResources().getDrawable(defaultIconId, null); + } else { + // Is cached? + final String filePath = MediaHelper.normalizeMediaPath(fso.getFullPath()); + if (this.mAppIcons.containsKey(filePath)) { + icon = mAppIcons.get(filePath); + } else { - if (mWorkerThread == null) { - mWorkerThread = new HandlerThread("IconHolderLoader"); - mWorkerThread.start(); - mWorkerHandler = new WorkerHandler(mWorkerThread.getLooper()); - } - Loadable previousForView = mRequests.get(iconView); - if (previousForView != null) { - mWorkerHandler.removeMessages(MSG_LOAD, previousForView); - } + if (mWorkerThread == null) { + mWorkerThread = new HandlerThread("IconHolderLoader"); + mWorkerThread.start(); + mWorkerHandler = new WorkerHandler(mWorkerThread.getLooper()); + } + Loadable previousForView = mRequests.get(iconView); + if (previousForView != null) { + mWorkerHandler.removeMessages(MSG_LOAD, previousForView); + } - Loadable loadable = new Loadable(mContext, iconView, fso, callback); - mRequests.put(iconView, loadable); - callback.onLoaded(iconView, null); + Loadable loadable = new Loadable(mContext, iconView, fso, callback, transform); + mRequests.put(iconView, loadable); - mWorkerHandler.obtainMessage(MSG_LOAD, loadable).sendToTarget(); + mWorkerHandler.obtainMessage(MSG_LOAD, loadable).sendToTarget(); + } + } + if ( callback != null) { + callback.onLoaded(iconView, icon); + } } private class WorkerHandler extends Handler { diff --git a/src/com/cyanogenmod/filemanager/ui/IconRenderer.java b/src/com/cyanogenmod/filemanager/ui/IconRenderer.java new file mode 100644 index 00000000..cde1e462 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/ui/IconRenderer.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 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; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.Outline; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.widget.ImageView; +import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.util.MimeTypeHelper; + +public class IconRenderer implements IconHolder.ICallback { + private Context mContext; + + private int mIconId; + + private ViewOutlineProvider mIconViewOutlineProvider; + + public IconRenderer(Context context, int iconId) { + mContext = context.getApplicationContext(); + mIconId = iconId; + mIconViewOutlineProvider = new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + Resources res = mContext.getResources(); + int size = res.getDimensionPixelSize(R.dimen.circle_icon_wh); + float radius = res.getDimension(R.dimen.rectangle_icon_radius); + outline.setRoundRect(0, 0, size, size, radius); + } + }; + } + + private static void setIcon(Resources resources, ImageView view, Drawable iconDrawable, + int iconColor, int backgroundId, int backgroundColor) { + StateListDrawable stateListDrawable = new StateListDrawable(); +// addSelected(resources, stateListDrawable); + addUnselected(stateListDrawable, iconDrawable, iconColor); + + ColorStateList colorList = new ColorStateList( + new int[][]{new int[]{android.R.attr.state_selected}, + new int[]{}}, + new int[]{resources.getColor(R.color.navigation_view_icon_selected), + backgroundColor}); + + view.setBackgroundResource(backgroundId); + view.setBackgroundTintList(colorList); + view.setImageDrawable(stateListDrawable); + } + + private static void addUnselected(StateListDrawable drawable, Drawable iconDrawable, + int color) { + iconDrawable.setTint(color); + drawable.addState(new int[0], iconDrawable); + } + + private static void addUnselectedThumbnail(StateListDrawable drawable, Drawable iconDrawable) { + drawable.addState(new int[0], iconDrawable); + } + + private void setIconThumbnail(Resources resources, ImageView view, Drawable iconDrawable) { + StateListDrawable stateListDrawable = new StateListDrawable(); +// addSelected(resources, stateListDrawable); + addUnselectedThumbnail(stateListDrawable, iconDrawable); + + ColorStateList colorList = new ColorStateList( + new int[][]{new int[]{android.R.attr.state_selected}, + new int[]{}}, + new int[]{resources.getColor(R.color.navigation_view_icon_selected), + resources.getColor(R.color.navigation_view_icon_unselected)}); + + view.setBackgroundResource(R.drawable.ic_icon_background_rounded_rectagle); + view.setBackgroundTintList(colorList); + view.setImageDrawable(stateListDrawable); + } + + @Override + public void onPreExecute(ImageView imageView) { + imageView.setOutlineProvider(mIconViewOutlineProvider); + imageView.setClipToOutline(true); + } + + @Override + public void onLoaded(ImageView imageView, Drawable icon) { + Resources res = mContext.getResources(); + if (icon == null) { + // Icon holder didn't have anything at the moment, set default. + int colorId = MimeTypeHelper.getIconColorFromIconId(mContext, mIconId); + setIcon(res, imageView, res.getDrawable(mIconId), + res.getColor(R.color.navigation_view_icon_unselected), + R.drawable.ic_icon_background, + res.getColor(colorId)); + } else { + // Thumbnail present, set the background to rectangle to match better. + setIconThumbnail(res, imageView, icon); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java index b869d751..2ed0a383 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java @@ -24,6 +24,7 @@ import android.text.Spanned; import android.view.View; import android.widget.Toast; +import com.cyanogenmod.filemanager.FileManagerApplication; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.console.ExecutionException; import com.cyanogenmod.filemanager.console.RelaunchableException; @@ -379,6 +380,9 @@ public final class DeleteActionPolicy extends ActionsPolicy { String.format( "Failed to delete file: %s", fso.getFullPath())); //$NON-NLS-1$ } + + ((FileManagerApplication)ctx.getApplicationContext()) + .getMStarUManager().notifyDeletedAsync(fso); } }; ExceptionUtil.OnRelaunchCommandResult onRelaunchCommandResult = diff --git a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java index 1eb2196f..e4c01b06 100755 --- a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java @@ -31,6 +31,7 @@ import android.util.Log; import android.view.View; import android.widget.Toast; +import com.cyanogenmod.filemanager.FileManagerApplication; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.activities.EditorActivity; import com.cyanogenmod.filemanager.activities.ShortcutActivity; @@ -150,6 +151,9 @@ public final class IntentsActionPolicy extends ActionsPolicy { intent, choose, onDismissListener); + + ((FileManagerApplication)ctx.getApplicationContext()) + .getMStarUManager().notifyAccessedAsync(fso); } @Override @@ -175,6 +179,9 @@ public final class IntentsActionPolicy extends ActionsPolicy { // Resolve the intent resolveIntent(ctx, intent, choose, onDismissListener); + + ((FileManagerApplication)ctx.getApplicationContext()) + .getMStarUManager().notifyAccessedAsync(fso); } catch (Exception e) { ExceptionUtil.translateException(ctx, e); } diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java index 215a2e5d..e8588edf 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java @@ -52,6 +52,7 @@ import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.model.ParentDirectory; import com.cyanogenmod.filemanager.model.RootDirectory; import com.cyanogenmod.filemanager.model.Symlink; +import com.cyanogenmod.filemanager.mstaru.IMostStarUsedFilesManager; import com.cyanogenmod.filemanager.parcelables.NavigationViewInfoParcelable; import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable; import com.cyanogenmod.filemanager.preferences.AccessMode; @@ -497,6 +498,8 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe */ AdapterView mAdapterView; + private IMostStarUsedFilesManager mMStarUManager; + //The layout for icons mode private static final int RESOURCE_MODE_ICONS_LAYOUT = R.layout.navigation_view_icons; private static final int RESOURCE_MODE_ICONS_ITEM = R.layout.navigation_view_icons_item; @@ -1084,6 +1087,10 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe changeCurrentDir(newDir, true, false, false, null, null); } + public void changeCurrentDir(final Directory newDir) { + changeCurrentDir(newDir.getFullPath()); + } + /** * Method that changes the current directory of the view. * @@ -1094,6 +1101,10 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe changeCurrentDir(newDir, addToHistory, false, false, null, null); } + public void changeCurrentDir(final Directory newDir, boolean addToHistory) { + changeCurrentDir(newDir.getFullPath(), addToHistory); + } + /** * Method that changes the current directory of the view. * @@ -1104,6 +1115,9 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe changeCurrentDir(newDir, true, false, false, searchInfo, null); } + public void changeCurrentDir(final Directory newDir, SearchInfoParcelable searchInfo) { + changeCurrentDir(newDir.getFullPath(), searchInfo); + } /** * Method that changes the current directory of the view. * -- cgit v1.2.3