diff options
Diffstat (limited to 'src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedManager.java')
-rw-r--r-- | src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedManager.java | 287 |
1 files changed, 287 insertions, 0 deletions
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<MostFrequentlyUsedManager> 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<FileSystemObject>) 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<IFileObserver> mObservers; + + private List<FileSystemObject> 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<Void, Void, Void>() { + @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<Void, Void, Void>() { + @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<Void, Void, Void>() { + @Override + public Void doInBackground(Void ... params) { + notifyDeleted(fso); + return null; + } + }.execute(); + } + + @Override + public List<FileSystemObject> getFiles() { + if (isMainThread()) { + throw new IllegalStateException("Must not be invoked from the main thread."); + } + + ArrayList<FileSystemObject> files; + Cursor c = null; + try { + files = new ArrayList<FileSystemObject>(); + 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<FileSystemObject> 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<FileSystemObject> files) { + mFiles = files; + for (IFileObserver o : mObservers) { + o.onFilesChanged(files); + } + } +} |