package com.android.camera.ui; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.android.camera2.R; import com.android.camera.Storage; import com.android.camera.StoragePathPreference; import com.android.camera.StorageUtil; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import android.graphics.Color; public class StoragePathPopup extends AbstractSettingPopup { private static final String TAG = "StoragePathPopup"; // default construct public StoragePathPopup(Context ctx, AttributeSet attrs) { super(ctx, attrs); } @Override public void setOrientation(int orientation, boolean animation) { synchronized(mLock) { super.setOrientation(orientation, animation); } } // mode picker private int mMode = StoragePathPreference.VAL_STORAGE_UNKNOW_MODE; // notify UI activity path is changed listener private StoragePathPreference.StoragePathChangedListener mListener; public void initializeSetup( StoragePathPreference.StoragePathChangedListener listener, int mode) { if (mMode != mode) mMode = mode; if (mListener != listener) mListener = listener; } public boolean collapseStorageControl() { if (View.VISIBLE == getVisibility()) { setVisibility(View.GONE); return true; } return false; } public void displayStoragePopup() { processStoragePopup(View.VISIBLE); bringToFront(); } public void dismissStoragePopup(boolean isCancle) { processStoragePopup(View.GONE); if (mListener != null && mTask != null) { // anyway we must call intercept thread for next time mTask.intercept(); mListener.storageChanged(mTask.getPath(), isCancle); } } private void processStoragePopup(int state) { Log.d(TAG, "processStoragePopup(), state = " + state); setVisibility(state); if (mTask != null) { switch (state) { case View.VISIBLE: // initialize data mTask.execute(DataAsyncTask.KEY_INITIALIZE_DATA, null); break; case View.GONE: case View.INVISIBLE: break; default: break; } } } private void proxyProgressPanel(int res) { Log.d(TAG, "proxyProgressPanel()"); boolean ensure = (mProgressPanel != null && mTask != null && mTask.scanning() && DataAsyncTask.KEY_UNKNOW_EVENT != res); if (ensure) { if (!mHandler.hasMessages(ViewHandler.MSG_NOTICE_STORAGE_SCAN)) { TextView tv = (TextView) mProgressPanel.findViewById(R.id.tv_progress_notice); tv.setText(res); mHandler.sendEmptyMessageDelayed( ViewHandler.MSG_NOTICE_STORAGE_SCAN, ViewHandler.VAL_NOTICE_STORAGE_DELAY); } } if (mProgressPanel != null) { mProgressPanel.setVisibility(ensure ? View.VISIBLE : View.GONE); } } private void proxyUpdateViews(int key) { if (mControlPanelListener != null) { mControlPanelListener.updateViews(key); } } private void proxySendMessage(int what) { if (mHandler != null) { Message msg = mHandler.obtainMessage(what); proxySendMessage(msg); } } private synchronized void proxySendMessage(Message msg) { if (mHandler != null) mHandler.sendMessage(msg); } // data list private ListView mListView; // adapter object private DataAdapter mAdapter; // item click listener private ItemClickListener mItemClickControl; // task object private DataAsyncTask mTask; // handler private ViewHandler mHandler; // progress bar panel private View mProgressPanel; // control click listener private ControlClickListener mControlPanelListener; // lock private final Object mLock = new Object(); @Override protected void onFinishInflate() { Log.d(TAG, "onFinishInflate"); super.onFinishInflate(); mListView = (ListView) findViewById(R.id.storage_path_list); mTask = new DataAsyncTask(); // create task mAdapter = new DataAdapter(); // create adapter mHandler = new ViewHandler(); // create handler if (mListView != null) { // initialize empty view mListView.setEmptyView(findViewById(R.id.storage_path_list_empty)); // initialize adapter mListView.setAdapter(mAdapter); // initialize item click listener mItemClickControl = new ItemClickListener(); mListView.setOnItemClickListener(mItemClickControl); // initialize progress bar panel mProgressPanel = findViewById(R.id.fl_progress_panel); proxyProgressPanel(DataAsyncTask.KEY_UNKNOW_EVENT); // initialize control panel listener mControlPanelListener = new ControlClickListener(); proxyUpdateViews(ViewHandler.MSG_UI_STATE_INFLATE); } } // fill data private class DataAsyncTask implements Runnable { // default event public static final int KEY_UNKNOW_EVENT = -1; // initialize data public static final int KEY_INITIALIZE_DATA = 0; // list view item click data public static final int KEY_ITEM_CLICK_DATA = 1; // intercept thread public static final int KEY_INTERCEPT_THREAD = 2; // key private int mKeyEvent = -1; // files filter private FilenameFilter filter; // current path private String cPath; // default construct /*package*/ DataAsyncTask() { filter = new FilenameFilter() { @Override public boolean accept(File dir, String filename) { File f = new File(dir, filename); Log.d(TAG, "filter path = " + f.getAbsolutePath()); return (f.exists() && f.isDirectory() && !f.isHidden() && f.canRead() && f.canWrite()); } }; } /*package*/ boolean scanning() { return (mKeyEvent != KEY_UNKNOW_EVENT); } /*package*/ void intercept() { mKeyEvent = KEY_INTERCEPT_THREAD; } @SuppressWarnings("unused") private boolean intercepted() { return (KEY_INTERCEPT_THREAD == mKeyEvent); } /*package*/ synchronized void execute(int key, String path) { if (mKeyEvent != key) { mKeyEvent = key; cPath = path; Thread t = new Thread(this); t.start(); } } /*package*/ String getPath() { return cPath; } @Override public void run() { List<DataItem> result = null; // initialize data if (KEY_INITIALIZE_DATA == mKeyEvent) { // update control panel proxySendMessage(ViewHandler.MSG_UI_STATE_INITIALIZE); result = initialize(); } // fill data by item click if (KEY_ITEM_CLICK_DATA == mKeyEvent) { // update control panel proxySendMessage(ViewHandler.MSG_UI_STATE_SCANNING); result = find(); proxySendMessage(ViewHandler.MSG_UI_STATE_SCANNED); } if (mAdapter != null) { mAdapter.notifyChanged(result); } // reset to default event mKeyEvent = KEY_UNKNOW_EVENT; } /*package*/ boolean isRootStorage(String path) { boolean result = false; if (path != null) { StorageUtil util = StorageUtil.newInstance(); Map<String, String> roots = util.supportedRootDirectory(); Set<Entry<String, String>> entries = roots.entrySet(); if (entries != null) { String key = null, val = null; for (Entry<String, String> entry : entries) { key = entry.getKey(); val = entry.getValue(); if (result = ((key != null && key.equals(path)) || (val != null && val.equals(path)))) { break; } } } } return result; } private List<DataItem> find() { List<DataItem> result = new ArrayList<DataItem>(DataAdapter.INIT_SIZE); String path = null; // get current path if ((path = cPath) != null) { // fixed bug 199757 start StorageUtil util = StorageUtil.newInstance(); Map<String, String> directory = util.supportedRootDirectory(); String convert = directory.get(path); // fixed bug 199757 end if (convert != null) path = convert; Log.d(TAG, "fill data from path = " + path); // sometime "path" is "internal" or "external", so we must validate "exists" && "directory" && "not hidden" File f = new File(path); if (f.exists() && f.isDirectory() && !f.isHidden()) { Map<String, String> root = util.supportedRootDirectory(); String internal = root.get(Storage.KEY_DEFAULT_INTERNAL); String external = root.get(Storage.KEY_DEFAULT_EXTERNAL); File[] folders = f.listFiles(filter); // fixed bug 199752 start for (int i = 0, len = (folders != null ? folders.length : 0); i < len; i++) { File d = folders[i]; // fixed bug 199752 end String c = d.getAbsolutePath(); boolean hc = true; // has children, default true boolean hp = false; // has parent, default false String sp = d.getParentFile().getAbsolutePath(); hp = !(sp != null && (sp.equals(internal) || sp.equals(external))); result.add(new DataItem(c, hp, hc)); } } } // if "result" is empty, so reset size if (result.isEmpty()) result = new ArrayList<DataItem>(Storage.VAL_DEFAULT_ROOT_DIRECTORY_SIZE); return result; } private List<DataItem> initialize() { List<DataItem> result = new ArrayList<DataItem>(Storage.VAL_DEFAULT_ROOT_DIRECTORY_SIZE); // fixed bug 199757 start StorageUtil util = StorageUtil.newInstance(); Map<String, String> directory = util.supportedRootDirectory(); String internal = directory.get(Storage.KEY_DEFAULT_INTERNAL); String external = directory.get(Storage.KEY_DEFAULT_EXTERNAL); // fixed bug 199757 end if (internal != null) result.add(new DataItem(Storage.KEY_DEFAULT_INTERNAL, false, true)); if (external != null) result.add(new DataItem(Storage.KEY_DEFAULT_EXTERNAL, false, true)); return result; } } private class ItemClickListener implements OnItemClickListener { // default construct private ItemClickListener() { } @Override public void onItemClick(AdapterView<?> group, View v, int pos, long id) { Log.d(TAG, "list view onItemClick()"); DataItem item = null; if (mAdapter != null && mTask != null && (item = mAdapter.getItem(pos)) != null) { Log.d(TAG, String.format("onItemClick() pos = %d, count = %d", new Object[] { pos, mAdapter.getCount() })); String nPath = item.cPath; String oPath = mTask.getPath(); boolean scanning = mTask.scanning(); Log.d(TAG, String.format("need reload folders? scanning = %b, old.path = %s, new.path = %s", new Object[] { scanning, oPath, nPath })); // if current is scanning, so notice user is scanning if (!nPath.equals(oPath) && !scanning) { mTask.execute(DataAsyncTask.KEY_ITEM_CLICK_DATA, nPath); proxyProgressPanel(R.string.notice_storage_scanning); } } } } private class ControlClickListener implements View.OnClickListener { private View mDone; private View mBack; private View mCancel; private View mTextView;//add by topwise qiujq for bug152 // default construct /*package*/ ControlClickListener() { // initialize image buttons mDone = findViewById(R.id.btn_save_path_done); mBack = findViewById(R.id.btn_save_path_back); mCancel = findViewById(R.id.btn_save_path_cancel); mTextView = findViewById(R.id.tv_save_path_title);//add by topwise qiujq for bug152 mDone.setOnClickListener(this); mBack.setOnClickListener(this); mCancel.setOnClickListener(this); mTextView.setOnClickListener(this);//add by topwise qiujq for bug152 } @Override public void onClick(View v) { int id = v.getId(); String path = null; if (mTask != null) path = mTask.getPath(); Log.d(TAG, "onClick() path = " + path); if (R.id.btn_save_path_cancel == id) { dismissStoragePopup(true); } else if (path != null && mTask != null) { switch (id) { case R.id.btn_save_path_done: StoragePathPreference mProxy = StoragePathPreference.getInstance(getContext()); mProxy.writeStorage(mMode, path); dismissStoragePopup(false); break; case R.id.btn_save_path_back: case R.id.tv_save_path_title://add by topwise qiujq for bug152 File f = new File(path); String p = f.getParent(); String c = f.getAbsolutePath(); boolean root = mTask.isRootStorage(path); Log.d(TAG, String.format("onClick(back), path = %s, parent = %s, root = %b", new Object[] { c, p, root })); mTask.execute( (root ? DataAsyncTask.KEY_INITIALIZE_DATA : DataAsyncTask.KEY_ITEM_CLICK_DATA), (root ? null : p)); // null is initialize data if (!root) { proxyProgressPanel(R.string.notice_storage_scanning); } break; } }else if ((R.id.btn_save_path_back == id) || (R.id.tv_save_path_title == id)){//add by topwise qiujq for bug152 dismissStoragePopup(false); } } /*package*/ synchronized void updateViews(int key) { Log.d(TAG, "proxyUpdateViews() key = " + key); switch (key) { case ViewHandler.MSG_UI_STATE_SCANNED: case ViewHandler.MSG_UI_STATE_INFLATE: boolean enable = (ViewHandler.MSG_UI_STATE_INFLATE != key ? ViewHandler.VAL_BOOLEAN_TRUE : ViewHandler.VAL_BOOLEAN_FALSE); mDone.setEnabled(enable); // mBack.setEnabled(enable); mCancel.setEnabled(enable); break; case ViewHandler.MSG_UI_STATE_INITIALIZE: mDone.setEnabled(ViewHandler.VAL_BOOLEAN_FALSE); // mBack.setEnabled(ViewHandler.VAL_BOOLEAN_FALSE); mCancel.setEnabled(ViewHandler.VAL_BOOLEAN_TRUE); break; case ViewHandler.MSG_UI_STATE_SCANNING: mDone.setEnabled(ViewHandler.VAL_BOOLEAN_TRUE); // mBack.setEnabled(ViewHandler.VAL_BOOLEAN_FALSE); mCancel.setEnabled(ViewHandler.VAL_BOOLEAN_TRUE); break; } } } private class ViewHandler extends Handler { /*package*/ static final int MSG_NOTIFY_DATA_CHANGED = 0; /*package*/ static final int MSG_NOTICE_STORAGE_SCAN = 1; /*package*/ static final int MSG_UI_STATE_INFLATE = 101; /*package*/ static final int MSG_UI_STATE_INITIALIZE = 102; /*package*/ static final int MSG_UI_STATE_SCANNING = 103; /*package*/ static final int MSG_UI_STATE_SCANNED = 104; /*package*/ static final boolean VAL_BOOLEAN_TRUE = true; /*package*/ static final boolean VAL_BOOLEAN_FALSE = false; /*package*/ static final long VAL_NOTICE_STORAGE_DELAY = 10 * 1000; /*package*/ ViewHandler() { super(); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); int what = msg.what; switch (what) { case MSG_NOTIFY_DATA_CHANGED: if (mAdapter != null && View.VISIBLE == getVisibility()) { @SuppressWarnings("unchecked") List<DataItem> data = (List<DataItem>) msg.obj; if (mAdapter.fill(data)) { mAdapter.notifyDataSetChanged(); } proxyProgressPanel(DataAsyncTask.KEY_UNKNOW_EVENT); } break; case MSG_NOTICE_STORAGE_SCAN: proxyProgressPanel(R.string.notice_storage_still_scanning); break; case MSG_UI_STATE_INFLATE: case MSG_UI_STATE_SCANNED: case MSG_UI_STATE_SCANNING: case MSG_UI_STATE_INITIALIZE: proxyUpdateViews(what); break; } } } // data adapter private class DataAdapter extends BaseAdapter { private static final int INIT_SIZE = 10; // this is data source private List<DataItem> mData; // default construct /*package*/ DataAdapter() { mData = Collections.synchronizedList(new ArrayList<DataItem>(INIT_SIZE)); } @Override public int getCount() { return mData.size(); } @Override public DataItem getItem(int pos) { DataItem item = null; if (pos < getCount()) item = mData.get(pos); return item; } @Override public long getItemId(int pos) { return pos; } @Override public View getView(int pos, View cvt, ViewGroup group) { synchronized(mLock) { TextView vText = null; DataItem data = null; // initialize "cvt" object if (cvt == null) { LayoutInflater inflater = LayoutInflater.from(StoragePathPopup.this.getContext()); cvt = inflater.inflate(R.layout.save_path_item, group, false); } if (cvt != null && (data = getItem(pos)) != null) { vText = (TextView) cvt.findViewById(R.id.item_text); vText.setText(data.cPath); vText.setTextColor(Color.BLACK); } return cvt; } } public boolean fill(List<DataItem> list) { boolean result = (list != null && mData != null); if (result) { Log.d(TAG, String.format("fill() old.size = %d, new.size = %d", new Object[] { mData.size(), list.size() })); mData.clear(); Collections.sort(list);//SPRD mData.addAll(list); } return result; } private void notifyChanged(List<DataItem> list) { synchronized(mLock) { if (mHandler != null) { mHandler.sendMessage( mHandler.obtainMessage(ViewHandler.MSG_NOTIFY_DATA_CHANGED, list)); } } } } // data item private class DataItem implements Comparable<DataItem>{//SPRD /*package*/ final String cPath; // current path /*package*/ final boolean hasParent; /*package*/ final boolean hasChildren; /*package*/ DataItem(String path, boolean p, boolean c) { cPath = path; hasParent = p; hasChildren = c; } @Override public int compareTo(DataItem mDataItem) {//SPRD // TODO Auto-generated method stub return this.cPath.compareTo(mDataItem.cPath); } } @Override public void reloadPreference() { Log.d(TAG, "reloadPreference"); } }