public Drawable getCachedPreview(int contentRowID) {
   if (LOG)
     Log.v(TAG, "getCachedPreview(" + contentRowID + ") mPreviewStatus=" + mAllPreviewStatus);
   initTask();
   registerObserver();
   if (mAllPreviewStatus == TYPE_NEED_LOAD) {
     loadPreviewStatus();
   }
   if (mAllPreviewStatus == TYPE_LOADED_NO_PREVIEW) {
     return mDefaultDrawable;
   }
   //
   MyDrawable cachedDrawable = null;
   synchronized (sCachedPreview) {
     cachedDrawable = sCachedPreview.get(contentRowID);
   }
   // when sdcard exists, load or reload the preview
   if (mMounted && (cachedDrawable == null || cachedDrawable.type == TYPE_NEED_LOAD)) {
     // add priority
     prioritySeed++;
     synchronized (mTaskQueue) {
       // check is processing or not
       // current request is not in queue.
       boolean isProcessing = false;
       if (currentRequest != null) {
         synchronized (currentRequest) {
           if (currentRequest.rowId == contentRowID) {
             isProcessing = true;
           }
         }
       }
       if (LOG) Log.v(TAG, "getCachedPreview() isProcessing=" + isProcessing);
       if (!isProcessing) {
         // check is in request queue or not.
         TaskParams oldRequest = null;
         for (TaskParams one : mTaskQueue) {
           if (one.rowId == contentRowID) {
             oldRequest = one;
             break;
           }
         }
         if (LOG) Log.i(TAG, "getCachedPreview() oldRequest=" + oldRequest);
         if (oldRequest == null) { // not in cache and not in request
           MyDrawable temp = new MyDrawable(null, TYPE_NEED_LOAD);
           synchronized (sCachedPreview) {
             cachedDrawable = sCachedPreview.get(contentRowID);
             if (cachedDrawable == null) {
               sCachedPreview.put(contentRowID, temp);
             }
             cachedDrawable = temp;
           }
           TaskParams task = new TaskParams(contentRowID, mListener, -prioritySeed);
           mTaskQueue.add(task);
           mTaskHandler.sendEmptyMessage(TASK_REQUEST_NEW);
         } else { // not in cache, but in request
           oldRequest.priority = -prioritySeed; // just update priority
           if (mTaskQueue.remove(oldRequest)) {
             mTaskQueue.add(oldRequest); // re-order the queue
           }
         }
       } else {
         // do nothing
       }
     }
     if (LOG) Log.v(TAG, "getCachedPreview() async load the drawable for " + contentRowID);
   }
   Drawable result = null;
   if (cachedDrawable == null || cachedDrawable.type != TYPE_LOADED_HAS_PREVIEW) {
     result = mDefaultDrawable;
   } else {
     result = cachedDrawable.drawable;
   }
   if (LOG)
     Log.v(
         TAG,
         "getCachedPreview() mPreviewStatus="
             + mAllPreviewStatus
             + ", cachedDrawable="
             + cachedDrawable
             + ", return "
             + result);
   return result;
 }
  /*package*/ static class CachedPreview extends Handler {
    private final HashMap<Integer, MyDrawable> sCachedPreview = new HashMap<Integer, MyDrawable>();
    private Context mContext;
    private ContentResolver mCr;
    private BitmapDrawable mDefaultDrawable;
    private boolean mIsSGMode;
    private Uri mUri;
    private int mUsage;
    private boolean mMounted;
    private int mAllPreviewStatus = TYPE_NEED_LOAD;
    private Boolean mRegisted = false;
    private MyContentObserver mContentObserver;
    private MyBroadcastReceiver mSdCardReceiver;
    private int mIconWidth;
    private int mIconHeight;
    private DrawableStateListener mListener;

    // asyc request media for fast the calling thread
    private Handler mTaskHandler;
    private static Looper sLooper;
    // priority queue for async request
    private static final PriorityQueue<TaskParams> mTaskQueue =
        new PriorityQueue<TaskParams>(10, TaskParams.getComparator());
    private static final int TASK_REQUEST_DONE = 1;
    private static final int TASK_REQUEST_NEW = 2;

    public CachedPreview(
        Context context,
        BitmapDrawable defaultDrawable,
        boolean isSGMode,
        int usage,
        DrawableStateListener listener) {
      mContext = context;
      mCr = mContext.getContentResolver();
      mDefaultDrawable = defaultDrawable;
      Bitmap icon = mDefaultDrawable.getBitmap();
      mIconWidth = icon.getWidth();
      mIconHeight = icon.getHeight();
      mUsage = usage;
      mIsSGMode = isSGMode;
      if (mIsSGMode) {
        mUri = MBBMSStore.SG.PreviewData.CONTENT_URI;
      } else {
        mUri = MBBMSStore.ESG.Media.CONTENT_URI;
      }
      mListener = listener;
      if (LOG) Log.v(TAG, "CachedPreview(" + isSGMode + ", " + usage + ")");
    }

    private void registerObserver() {
      // Here I register the ContentObserver and BroadcasetReceiver.
      // These can check the database's update and sdcard's state.
      // So, when there's no sdcard or database has no preview infos,
      // getCachedPreview() will be faster.
      synchronized (mRegisted) {
        if (!mRegisted) {
          mContentObserver = new MyContentObserver(null);
          mSdCardReceiver = new MyBroadcastReceiver();

          mCr.registerContentObserver(mUri, true, mContentObserver);

          IntentFilter filter1 = new IntentFilter(Intent.ACTION_MEDIA_EJECT);
          filter1.addDataScheme("file");
          mContext.registerReceiver(mSdCardReceiver, filter1);

          IntentFilter filter2 = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
          filter2.addDataScheme("file");
          mContext.registerReceiver(mSdCardReceiver, filter2);
          mRegisted = true;

          mMounted = isExternalStorageReady();
          if (LOG) Log.v(TAG, "registerObserver() regist observers! mMounted=" + mMounted);
        }
      }
      if (LOG) Log.v(TAG, "registerObserver() mRegisted=" + mRegisted + ", mMounted=" + mMounted);
    }

    @Override
    public void handleMessage(Message msg) {
      if (LOG) Log.v(TAG, "mCallingHandler.handleMessage(" + msg + ")");
      if (msg.what == TASK_REQUEST_DONE && (msg.obj instanceof TaskParams)) {
        TaskParams result = (TaskParams) msg.obj;
        if (result.listener != null) {
          result.listener.onChanged(result.rowId, result.drawable);
        }
      }
    }

    private boolean initTask;
    private long prioritySeed;
    private TaskParams currentRequest;

    private void initTask() {
      if (LOG) Log.v(TAG, "initTask() initTask=" + initTask + ", prioritySeed=" + prioritySeed);
      if (initTask) return;
      prioritySeed = 0;
      synchronized (CachedPreview.class) {
        if (sLooper == null) {
          HandlerThread t =
              new HandlerThread(
                  "cached-preview-thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);
          t.start();
          sLooper = t.getLooper();
        }
      }
      mTaskHandler =
          new Handler(sLooper) {
            @Override
            public void handleMessage(Message msg) {
              if (LOG) Log.v(TAG, "mTaskHandler.handleMessage(" + msg + ")");
              if (msg.what == TASK_REQUEST_NEW) {
                synchronized (mTaskQueue) {
                  currentRequest = mTaskQueue.poll();
                }
                if (currentRequest == null) {
                  Log.w(TAG, "wrong request, has request but no task params.");
                  return;
                }
                // recheck the drawable is exists or not.
                int contentRowID = currentRequest.rowId;
                MyDrawable cachedDrawable = null;
                synchronized (sCachedPreview) {
                  cachedDrawable = sCachedPreview.get(contentRowID);
                }
                if (cachedDrawable == null) {
                  Log.w(TAG, "cached drawable was delete. may for clear.");
                  return;
                }
                // when sdcard exists, load or reload the preview
                if (mMounted && cachedDrawable.type == TYPE_NEED_LOAD) {
                  Bitmap tempBitmap = getPreview(contentRowID);
                  if (tempBitmap != null) {
                    tempBitmap =
                        Bitmap.createScaledBitmap(tempBitmap, mIconWidth, mIconHeight, true);
                    cachedDrawable.set(new FastBitmapDrawable(tempBitmap), TYPE_LOADED_HAS_PREVIEW);
                  } else {
                    cachedDrawable.set(null, TYPE_LOADED_NO_PREVIEW);
                  }
                }
                currentRequest.drawable = cachedDrawable;
                Message done = CachedPreview.this.obtainMessage(TASK_REQUEST_DONE);
                done.obj = currentRequest;
                done.sendToTarget();
                if (LOG) Log.v(TAG, "mTaskHandler.handleMessage() send done. " + currentRequest);
              }
            }
          };
      initTask = true;
    }

    private void clearTask() {
      if (LOG) Log.v(TAG, "clearTask() initTask=" + initTask);
      if (initTask) {
        prioritySeed = 0;
        removeMessages(TASK_REQUEST_DONE);
        synchronized (mTaskQueue) {
          mTaskQueue.clear();
        }
        mTaskHandler.removeMessages(TASK_REQUEST_NEW);
        mTaskHandler = null;
      }
      initTask = false;
    }

    public Drawable getCachedPreview(int contentRowID) {
      if (LOG)
        Log.v(TAG, "getCachedPreview(" + contentRowID + ") mPreviewStatus=" + mAllPreviewStatus);
      initTask();
      registerObserver();
      if (mAllPreviewStatus == TYPE_NEED_LOAD) {
        loadPreviewStatus();
      }
      if (mAllPreviewStatus == TYPE_LOADED_NO_PREVIEW) {
        return mDefaultDrawable;
      }
      //
      MyDrawable cachedDrawable = null;
      synchronized (sCachedPreview) {
        cachedDrawable = sCachedPreview.get(contentRowID);
      }
      // when sdcard exists, load or reload the preview
      if (mMounted && (cachedDrawable == null || cachedDrawable.type == TYPE_NEED_LOAD)) {
        // add priority
        prioritySeed++;
        synchronized (mTaskQueue) {
          // check is processing or not
          // current request is not in queue.
          boolean isProcessing = false;
          if (currentRequest != null) {
            synchronized (currentRequest) {
              if (currentRequest.rowId == contentRowID) {
                isProcessing = true;
              }
            }
          }
          if (LOG) Log.v(TAG, "getCachedPreview() isProcessing=" + isProcessing);
          if (!isProcessing) {
            // check is in request queue or not.
            TaskParams oldRequest = null;
            for (TaskParams one : mTaskQueue) {
              if (one.rowId == contentRowID) {
                oldRequest = one;
                break;
              }
            }
            if (LOG) Log.i(TAG, "getCachedPreview() oldRequest=" + oldRequest);
            if (oldRequest == null) { // not in cache and not in request
              MyDrawable temp = new MyDrawable(null, TYPE_NEED_LOAD);
              synchronized (sCachedPreview) {
                cachedDrawable = sCachedPreview.get(contentRowID);
                if (cachedDrawable == null) {
                  sCachedPreview.put(contentRowID, temp);
                }
                cachedDrawable = temp;
              }
              TaskParams task = new TaskParams(contentRowID, mListener, -prioritySeed);
              mTaskQueue.add(task);
              mTaskHandler.sendEmptyMessage(TASK_REQUEST_NEW);
            } else { // not in cache, but in request
              oldRequest.priority = -prioritySeed; // just update priority
              if (mTaskQueue.remove(oldRequest)) {
                mTaskQueue.add(oldRequest); // re-order the queue
              }
            }
          } else {
            // do nothing
          }
        }
        if (LOG) Log.v(TAG, "getCachedPreview() async load the drawable for " + contentRowID);
      }
      Drawable result = null;
      if (cachedDrawable == null || cachedDrawable.type != TYPE_LOADED_HAS_PREVIEW) {
        result = mDefaultDrawable;
      } else {
        result = cachedDrawable.drawable;
      }
      if (LOG)
        Log.v(
            TAG,
            "getCachedPreview() mPreviewStatus="
                + mAllPreviewStatus
                + ", cachedDrawable="
                + cachedDrawable
                + ", return "
                + result);
      return result;
    }

    private Bitmap getPreview(int contentRowID) {
      Bitmap bitmap = null;
      if (mIsSGMode) {
        bitmap = MBBMSStore.SG.ContentPreviewData.getPreviewBitmap(mCr, contentRowID, mUsage, null);
      } else {
        bitmap = MBBMSStore.ESG.ContentMedia.getMediaBitmap(mCr, contentRowID, mUsage, null);
      }
      if (LOG) Log.v(TAG, "getPreview() bitmap=" + bitmap);
      return bitmap;
    }

    public void clearCachedPreview() {
      synchronized (mRegisted) {
        if (mRegisted) {
          mCr.unregisterContentObserver(mContentObserver);
          mContentObserver = null;
          mContext.unregisterReceiver(mSdCardReceiver);
          mSdCardReceiver = null;
          mAllPreviewStatus = TYPE_NEED_LOAD;
          mRegisted = false;
        }
      }
      synchronized (sCachedPreview) {
        sCachedPreview.clear();
      }
      clearTask();
    }

    private void loadPreviewStatus() {
      Cursor cursor = null;
      try {
        cursor = mCr.query(mUri, new String[] {"count(*)"}, null, null, null);
        if (cursor != null && cursor.moveToFirst()) {
          int count = cursor.getInt(0);
          if (count > 0) {
            mAllPreviewStatus = TYPE_LOADED_HAS_PREVIEW;
          } else {
            mAllPreviewStatus = TYPE_LOADED_NO_PREVIEW;
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
        // ignore it.
      } finally {
        if (cursor != null) {
          cursor.close();
        }
      }
      if (LOG) Log.v(TAG, "loadPreviewStatus() mPreviewStatus=" + mAllPreviewStatus);
    }

    private class MyContentObserver extends ContentObserver {

      public MyContentObserver(Handler handler) {
        super(handler);
      }

      @Override
      public void onChange(boolean selfChange) {
        if (LOG) Log.v(TAG, "mContentObserver.onChange(" + selfChange + ")");
        if (mAllPreviewStatus != TYPE_NEED_LOAD) {
          // we do not delete drawable until we refresh the whole SG or ESG info
          // which is be operated in MainScreen Activity.
          // So here do not need to consider delete case.
          Set<Integer> keys = sCachedPreview.keySet();
          for (Integer key : keys) {
            MyDrawable drawable = sCachedPreview.get(key);
            if (drawable.type == TYPE_LOADED_NO_PREVIEW) {
              drawable.type = TYPE_NEED_LOAD;
            }
          }
          mAllPreviewStatus = TYPE_NEED_LOAD;
        }
      }
    }

    private class MyBroadcastReceiver extends BroadcastReceiver {

      @Override
      public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        String path = intent.getData().getPath();
        // only observe sdcard with default path: "/mnt/sdcard"
        String externalPath = Environment.getExternalStorageDirectory().getPath();
        if (Intent.ACTION_MEDIA_EJECT.equals(action) && externalPath.equalsIgnoreCase(path)) {
          mMounted = false;
        } else if (Intent.ACTION_MEDIA_MOUNTED.equals(action)
            && externalPath.equalsIgnoreCase(path)) {
          mMounted = true;
        }
        if (LOG) Log.v(TAG, "onReceive(" + intent + ") mMounted=" + mMounted);
      }
    };

    /*package*/ static final int TYPE_NEED_LOAD = 0;
    /*package*/ static final int TYPE_LOADED_NO_PREVIEW = 1;
    /*package*/ static final int TYPE_LOADED_HAS_PREVIEW = 2;
    /*package*/ class MyDrawable {
      int type;
      Drawable drawable;

      public MyDrawable(Drawable idrawable, int itype) {
        type = itype;
        drawable = idrawable;
      }

      public void set(Drawable idrawable, int itype) {
        type = itype;
        drawable = idrawable;
      }

      @Override
      public String toString() {
        return new StringBuilder()
            .append("MyDrawable(type=")
            .append(type)
            .append(", drawable=")
            .append(drawable)
            .append(")")
            .toString();
      }
    }

    public interface DrawableStateListener {
      public void onChanged(int rowId, MyDrawable drawable);
    }

    public static class TaskParams {
      int rowId;
      MyDrawable drawable;
      DrawableStateListener listener;
      long priority;

      public TaskParams(int rowId, DrawableStateListener listener, long priority) {
        this.rowId = rowId;
        this.listener = listener;
        this.priority = priority;
      }

      @Override
      public String toString() {
        return new StringBuilder()
            .append("TaskInput(rowId=")
            .append(rowId)
            .append(", listener=")
            .append(listener)
            .append(", drawable=")
            .append(drawable)
            .append(")")
            .toString();
      }

      static Comparator<TaskParams> getComparator() {
        return new Comparator<TaskParams>() {

          public int compare(TaskParams r1, TaskParams r2) {
            if (r1.priority != r2.priority) {
              return (r1.priority < r2.priority) ? -1 : 1;
            }
            return 0;
          }
        };
      }
    }
  }