Refactor the code.

Change-Id: Icc699cb284f3d6c58590a912d48cc0044e1047d6
diff --git a/new3d/src/com/android/gallery3d/data/BucketMediaSet.java b/new3d/src/com/android/gallery3d/data/BucketMediaSet.java
index f3cc6e1..7e9852c 100644
--- a/new3d/src/com/android/gallery3d/data/BucketMediaSet.java
+++ b/new3d/src/com/android/gallery3d/data/BucketMediaSet.java
@@ -4,73 +4,31 @@
 
 import android.content.ContentResolver;
 import android.database.Cursor;
-import android.os.Handler;
-import android.os.Message;
 
 import com.android.gallery3d.app.GalleryContext;
-import com.android.gallery3d.ui.SynchronizedHandler;
+import com.android.gallery3d.ui.Util;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 
-public class BucketMediaSet implements MediaSet {
+public class BucketMediaSet extends DatabaseMediaSet {
     private static final int MAX_NUM_COVER_ITEMS = 4;
 
-    private static final int MSG_LOAD_DATABASE = 1;
-    private static final int MSG_UPDATE_BUCKET = 2;
-
     public static final Comparator<BucketMediaSet> sNameComparator = new MyComparator();
 
     private final int mBucketId;
     private final String mBucketTitle;
+
     private final ArrayList<DatabaseMediaItem> mMediaItems =
             new ArrayList<DatabaseMediaItem>();
     private ArrayList<DatabaseMediaItem> mLoadBuffer =
             new ArrayList<DatabaseMediaItem>();
 
-    private final Handler mHandler;
-    private final Handler mMainHandler;
-
-    private MediaSetListener mListener;
-    private ImageService mImageService;
-    private ContentResolver mContentResolver;
-
-    protected void invalidate() {
-        mHandler.sendEmptyMessage(MSG_LOAD_DATABASE);
-    }
-
     public BucketMediaSet(GalleryContext context, int id, String title) {
-        mContentResolver = context.getContentResolver();
-        mImageService = context.getImageService();
+        super(context);
         mBucketId = id;
         mBucketTitle= title;
-
-        mHandler = new Handler(context.getDataManager().getDataLooper()) {
-            @Override
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MSG_LOAD_DATABASE:
-                        loadMediaItemsFromDatabase();
-                        break;
-                    default: throw new IllegalArgumentException();
-                }
-            }
-        };
-
-        mMainHandler = new SynchronizedHandler(
-                context.getUIMonitor(), context.getMainLooper()) {
-            @Override
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MSG_UPDATE_BUCKET:
-                        updateContent();
-                        break;
-                    default: throw new IllegalArgumentException();
-                }
-            }
-        };
-
     }
 
     public MediaItem[] getCoverMediaItems() {
@@ -106,20 +64,18 @@
         return mMediaItems.size();
     }
 
-    public void setContentListener(MediaSetListener listener) {
-        mListener = listener;
-    }
-
-    private void loadMediaItemsFromDatabase() {
+    @Override
+    protected void onLoadFromDatabase() {
         ArrayList<DatabaseMediaItem> items = new ArrayList<DatabaseMediaItem>();
         mLoadBuffer = items;
 
-        ContentResolver resolver = mContentResolver;
+        ContentResolver resolver = mContext.getContentResolver();
+        ImageService imageService = mContext.getImageService();
 
         Cursor cursor = ImageMediaItem.queryImageInBucket(resolver, mBucketId);
         try {
             while (cursor.moveToNext()) {
-                items.add(ImageMediaItem.load(mImageService, cursor));
+                items.add(ImageMediaItem.load(imageService, cursor));
             }
         } finally {
             cursor.close();
@@ -128,7 +84,7 @@
         cursor = VideoMediaItem.queryVideoInBucket(resolver, mBucketId);
         try {
             while (cursor.moveToNext()) {
-                items.add(VideoMediaItem.load(mImageService, cursor));
+                items.add(VideoMediaItem.load(imageService, cursor));
             }
         } finally {
             cursor.close();
@@ -144,12 +100,11 @@
                         : result > 0 ? 1 : -1;
             }
         });
-
-        mMainHandler.sendEmptyMessage(MSG_UPDATE_BUCKET);
     }
 
-    private void updateContent() {
-        if (mLoadBuffer == null) throw new IllegalArgumentException();
+    @Override
+    protected void onUpdateContent() {
+        Util.Assert(mLoadBuffer != null);
 
         mMediaItems.clear();
         mMediaItems.addAll(mLoadBuffer);
diff --git a/new3d/src/com/android/gallery3d/data/DatabaseMediaSet.java b/new3d/src/com/android/gallery3d/data/DatabaseMediaSet.java
index 1e4eb5f..77e21e4 100644
--- a/new3d/src/com/android/gallery3d/data/DatabaseMediaSet.java
+++ b/new3d/src/com/android/gallery3d/data/DatabaseMediaSet.java
@@ -9,16 +9,22 @@
 import com.android.gallery3d.ui.SynchronizedHandler;
 import com.android.gallery3d.ui.Util;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 public abstract class DatabaseMediaSet implements MediaSet {
 
     private static final int MSG_LOAD_DATABASE = 0;
     private static final int MSG_UPDATE_CONTENT = 1;
 
-    protected Handler mMainHandler;
-    protected Handler mDbHandler;
+    private static final int BIT_INVALIDATING = 1;
+    private static final int BIT_PENDING = 2;
+
+    protected final Handler mMainHandler;
+    protected final Handler mDbHandler;
     protected final GalleryContext mContext;
 
     protected MediaSetListener mListener;
+    private AtomicInteger mState = new AtomicInteger();
 
     protected DatabaseMediaSet(GalleryContext context) {
         mContext = context;
@@ -30,6 +36,24 @@
                 Util.Assert(message.what == MSG_UPDATE_CONTENT);
                 onUpdateContent();
                 if (mListener != null) mListener.onContentChanged();
+
+                while (true) {
+                    int s = mState.get();
+
+                    // Either (1) resets the the pending bit and sets the
+                    //            invalidating bit, or
+                    //        (2) resets the state to 0 if the pending bit was
+                    //            originally cleared.
+                    int t = (s & BIT_PENDING) == 0 ? 0 : BIT_INVALIDATING;
+                    if (mState.compareAndSet(s, t)) {
+                        if (t == BIT_INVALIDATING) {
+                            // Case 1: clear the pending bit by loading data
+                            //         from database.
+                            mDbHandler.sendEmptyMessage(MSG_LOAD_DATABASE);
+                        }
+                        break;
+                    }
+                }
             }
         };
 
@@ -44,7 +68,21 @@
     }
 
     public void invalidate() {
-        mDbHandler.sendEmptyMessage(MSG_LOAD_DATABASE);
+        while (true) {
+            int s = mState.get();
+
+            // State is moved either to (1) invalidating, or (2) invalidating and pending.
+            int t = (s & BIT_INVALIDATING) == 0
+                    ? BIT_INVALIDATING
+                    : BIT_INVALIDATING | BIT_PENDING;
+            if (mState.compareAndSet(s, t)) {
+                if (t == BIT_INVALIDATING) {
+                    // Case 1: loading data from database.
+                    mDbHandler.sendEmptyMessage(MSG_LOAD_DATABASE);
+                }
+                break;
+            }
+        }
     }
 
     public void setContentListener(MediaSetListener listener) {
diff --git a/new3d/src/com/android/gallery3d/data/LocalMediaSet.java b/new3d/src/com/android/gallery3d/data/LocalMediaSet.java
deleted file mode 100644
index 4e398ab..0000000
--- a/new3d/src/com/android/gallery3d/data/LocalMediaSet.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package com.android.gallery3d.data;
-
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
-//
-// LocalMediaSet is a MediaSet which is obtained from MediaProvider.
-// Each LocalMediaSet is identified by a bucket id.
-// It is populated by addMediaItem(MediaItem) and addSubMediaSet(LocalMediaSet).
-//
-// getSubMediaSetById(int setId) returns a sub-MediaSet given a bucket id.
-//
-public class LocalMediaSet implements MediaSet {
-    public static final int ROOT_SET_ID = -1;
-    private static final String TAG = "LocalMediaSet";
-
-    private static final int MAX_NUM_COVERED_ITEMS = 4;
-
-    private final ArrayList<LocalMediaSet> mSubMediaSets =
-            new ArrayList<LocalMediaSet>();
-    private final Map<Integer, Integer> mIdsToIndice =
-            new HashMap<Integer, Integer>();
-
-    private final ArrayList<MediaItem> mMediaItems = new ArrayList<MediaItem>();
-
-    private final int mBucketId;
-    private final String mTitle;
-
-    public LocalMediaSet(int bucketId, String title) {
-        mBucketId = bucketId;
-        mTitle = title;
-    }
-
-    public int getBucketId() {
-        return mBucketId;
-    }
-
-    public MediaItem[] getCoverMediaItems() {
-        MediaItem[] coverItems = new MediaItem[MAX_NUM_COVERED_ITEMS];
-        int filled = fillCoverMediaItems(coverItems, 0);
-        if (filled < MAX_NUM_COVERED_ITEMS) {
-            MediaItem[] result = new MediaItem[filled];
-            System.arraycopy(coverItems, 0, result, 0, filled);
-            return result;
-        } else {
-            return coverItems;
-        }
-    }
-
-    private int fillCoverMediaItems(MediaItem[] items, int offset) {
-        // Fill from my MediaItems.
-        int size = Math.min(getMediaItemCount(), items.length - offset);
-        for (int i = 0; i < size; ++i) {
-            items[offset + i] = getMediaItem(i);
-        }
-        if (offset + size == items.length) return size;
-
-        // Fill from sub-MediaSets.
-        int n = getSubMediaSetCount();
-        for (int i = 0; i < n; ++i) {
-            size += mSubMediaSets.get(i).fillCoverMediaItems(items, offset + size);
-            if (offset + size == items.length) break;
-        }
-
-        return size;
-    }
-
-    public MediaItem getMediaItem(int index) {
-        return mMediaItems.get(index);
-    }
-
-    void addMediaItem(MediaItem item) {
-        mMediaItems.add(item);
-    }
-
-    public MediaSet getSubMediaSetById(int setId) {
-        Integer index = mIdsToIndice.get(setId);
-        return index == null ? null
-                : mSubMediaSets.get(index);
-    }
-
-    public int getSubMediaSetCount() {
-        return mSubMediaSets.size();
-    }
-
-    public MediaSet getSubMediaSet(int index) {
-        return mSubMediaSets.get(index);
-    }
-
-    public String getTitle() {
-        return mTitle;
-    }
-
-    public int getMediaItemCount() {
-        return mMediaItems.size();
-    }
-
-    public int getTotalMediaItemCount() {
-        int totalItemCount = mMediaItems.size();
-        for (LocalMediaSet set : mSubMediaSets) {
-            totalItemCount += set.getTotalMediaItemCount();
-        }
-        return totalItemCount;
-    }
-
-    public void addSubMediaSet(LocalMediaSet set) {
-        if (mSubMediaSets.add(set)) {
-            mIdsToIndice.put(set.mBucketId, mSubMediaSets.size() - 1);
-        }
-    }
-
-    /*
-     * Only for checking LocalMediaSet's content.
-     */
-    public void printOut() {
-        Log.i(TAG, "Media Set, numItems: " + mTitle + ", " + getTotalMediaItemCount());
-
-        for (MediaItem item : mMediaItems) {
-            Log.i(TAG, "Media title: " + item.getTitle());
-        }
-
-        for (LocalMediaSet set: mSubMediaSets) {
-            set.printOut();
-        }
-    }
-
-    public void setContentListener(MediaSetListener listener) {
-    }
-}
diff --git a/new3d/src/com/android/gallery3d/data/RootMediaSet.java b/new3d/src/com/android/gallery3d/data/RootMediaSet.java
index 7d5e6ee..c73e5a8 100644
--- a/new3d/src/com/android/gallery3d/data/RootMediaSet.java
+++ b/new3d/src/com/android/gallery3d/data/RootMediaSet.java
@@ -5,8 +5,6 @@
 import android.content.ContentResolver;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
 import android.provider.MediaStore.Images.ImageColumns;
@@ -14,19 +12,15 @@
 import android.util.Log;
 
 import com.android.gallery3d.app.GalleryContext;
-import com.android.gallery3d.ui.SynchronizedHandler;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
-public class RootMediaSet implements MediaSet{
+public class RootMediaSet extends DatabaseMediaSet {
     private static final String TITLE = "RootSet";
 
-    private static final int MSG_LOAD_DATA = 0;
-    private static final int MSG_UPDATE_CONTENT = 0;
-
     // Must preserve order between these indices and the order of the terms in
     // BUCKET_PROJECTION_IMAGES, BUCKET_PROJECTION_VIDEOS.
     // Not using SortedHashMap for efficiency reasons.
@@ -48,44 +42,9 @@
 
     private HashMap<Integer, String> mLoadBuffer;
 
-    private final Handler mDataHandler;
-    private final Handler mMainHandler;
-
-    private final GalleryContext mContext;
-    private MediaSetListener mListener;
-
     public RootMediaSet(GalleryContext context) {
-
-        mContext = context;
-
-        mDataHandler = new Handler(context.getDataManager().getDataLooper()) {
-            @Override
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MSG_LOAD_DATA:
-                        loadBucketsFromDatabase();
-                        break;
-                    default:
-                        throw new IllegalArgumentException();
-                }
-            }
-        };
-
-        mMainHandler = new SynchronizedHandler(
-                context.getUIMonitor(), context.getMainLooper()) {
-            @Override
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MSG_UPDATE_CONTENT:
-                        updateContent();
-                        break;
-                    default:
-                        throw new IllegalArgumentException();
-                }
-            }
-        };
-
-        mDataHandler.sendEmptyMessage(MSG_LOAD_DATA);
+        super(context);
+        invalidate();
     }
 
     public MediaItem[] getCoverMediaItems() {
@@ -122,11 +81,9 @@
         return total;
     }
 
-    public void setContentListener(MediaSetListener listener) {
-        mListener = listener;
-    }
+    @Override
+    protected void onLoadFromDatabase() {
 
-    private void loadBucketsFromDatabase() {
         ContentResolver resolver = mContext.getContentResolver();
         HashMap<Integer, String> map = new HashMap<Integer, String>();
         mLoadBuffer = map;
@@ -161,11 +118,10 @@
         } finally {
             cursor.close();
         }
-
-        mMainHandler.sendEmptyMessage(MSG_UPDATE_CONTENT);
     }
 
-    private void updateContent() {
+    @Override
+    protected void onUpdateContent() {
         HashMap<Integer, String> map = mLoadBuffer;
         if (map == null) throw new IllegalStateException();
 
diff --git a/new3d/tests/src/com/android/gallery3d/data/LocalMediaSetTest.java b/new3d/tests/src/com/android/gallery3d/data/LocalMediaSetTest.java
deleted file mode 100644
index 7dca658..0000000
--- a/new3d/tests/src/com/android/gallery3d/data/LocalMediaSetTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source 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.android.gallery3d.data;
-
-import android.graphics.Bitmap;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.TestCase;
-
-@SmallTest
-public class LocalMediaSetTest extends TestCase {
-    private static final String TAG = "LocalMediaSetTest";
-
-    public void testEmptySet() {
-        LocalMediaSet s = new LocalMediaSet(42, "Empty Set");
-        assertEquals(0, s.getMediaItemCount());
-        assertEquals(0, s.getSubMediaSetCount());
-        assertEquals(0, s.getTotalMediaItemCount());
-        assertEquals("Empty Set", s.getTitle());
-        assertEquals(0, s.getCoverMediaItems().length);
-        assertNull(s.getSubMediaSetById(42));
-    }
-
-    private static class MyMediaItem implements MediaItem {
-        public String getMediaUri() { return ""; }
-        public String getTitle() { return ""; }
-        public Bitmap getImage(int type) { return null; }
-        public void setListener(MediaItemListener listener) {}
-        public int requestImage(int type) {return 0;}
-        public void cancelImageRequest(int type) {}
-    }
-
-    public void testOneItem() {
-        LocalMediaSet s = new LocalMediaSet(1, "One Item Set");
-        MediaItem item = new MyMediaItem();
-        s.addMediaItem(item);
-        assertEquals(1, s.getMediaItemCount());
-        assertEquals(0, s.getSubMediaSetCount());
-        assertEquals(1, s.getTotalMediaItemCount());
-        assertSame(item, s.getCoverMediaItems()[0]);
-        assertSame(item, s.getMediaItem(0));
-    }
-
-    public void testTwoItems() {
-        LocalMediaSet s = new LocalMediaSet(2, "Two Items Set");
-        MediaItem item1 = new MyMediaItem();
-        MediaItem item2 = new MyMediaItem();
-        s.addMediaItem(item1);
-        s.addMediaItem(item2);
-        assertEquals(2, s.getMediaItemCount());
-        assertEquals(0, s.getSubMediaSetCount());
-        assertEquals(2, s.getTotalMediaItemCount());
-        assertTrue(s.getCoverMediaItems()[0] == item1
-                || s.getCoverMediaItems()[0] == item2);
-    }
-
-    public void testEmptySubMediaSet() {
-        LocalMediaSet s = new LocalMediaSet(3, "One Empty Sub-MediaSet");
-        LocalMediaSet t = new LocalMediaSet(42, "Empty Set");
-        s.addSubMediaSet(t);
-        assertEquals(0, s.getMediaItemCount());
-        assertEquals(1, s.getSubMediaSetCount());
-        assertEquals(0, s.getTotalMediaItemCount());
-        assertEquals("One Empty Sub-MediaSet", s.getTitle());
-        assertEquals(0, s.getCoverMediaItems().length);
-        assertSame(t, s.getSubMediaSet(0));
-        assertSame(t, s.getSubMediaSetById(42));
-        assertNull(s.getSubMediaSetById(0));
-        assertEquals("Empty Set", t.getTitle());
-    }
-
-    public void testSubSubMediaSet() {
-        LocalMediaSet s = new LocalMediaSet(0, "Set 0");
-        LocalMediaSet s1 = new LocalMediaSet(1, "Set 1");
-        LocalMediaSet s2 = new LocalMediaSet(2, "Set 2");
-        MediaItem item = new MyMediaItem();
-        s.addSubMediaSet(s1);
-        assertEquals(0, s.getMediaItemCount());
-        assertEquals(1, s.getSubMediaSetCount());
-        assertEquals(0, s.getTotalMediaItemCount());
-        assertEquals(0, s.getCoverMediaItems().length);
-        assertSame(s1, s.getSubMediaSet(0));
-        assertNull(s.getSubMediaSetById(0));
-        assertSame(s1, s.getSubMediaSetById(1));
-        assertNull(s.getSubMediaSetById(2));
-        s1.addSubMediaSet(s2);
-        assertEquals(0, s.getMediaItemCount());
-        assertEquals(1, s.getSubMediaSetCount());
-        assertEquals(0, s.getTotalMediaItemCount());
-        assertEquals(0, s.getCoverMediaItems().length);
-        assertSame(s1, s.getSubMediaSet(0));
-        assertNull(s.getSubMediaSetById(0));
-        assertSame(s1, s.getSubMediaSetById(1));
-        assertNull(s.getSubMediaSetById(2));
-        assertSame(s2, s1.getSubMediaSet(0));
-        assertSame(s2, s1.getSubMediaSetById(2));
-        s2.addMediaItem(item);
-        assertEquals(0, s.getMediaItemCount());
-        assertEquals(1, s.getSubMediaSetCount());
-        assertEquals(1, s.getTotalMediaItemCount());
-        assertEquals(1, s.getCoverMediaItems().length);
-        assertSame(s1, s.getSubMediaSet(0));
-        assertNull(s.getSubMediaSetById(0));
-        assertSame(s1, s.getSubMediaSetById(1));
-        assertNull(s.getSubMediaSetById(2));
-    }
-
-    //
-    // [0] - [1]
-    //     -  2
-    //     -  3
-    //     - [4] -  5
-    //           -  6
-    //           -  7
-    //           - [8] - [9] - 10
-    //                 - [11]
-    //
-    public void testMediaSetTree() {
-        LocalMediaSet s0 = new LocalMediaSet(LocalMediaSet.ROOT_SET_ID, "Set 0");
-        LocalMediaSet s1 = new LocalMediaSet(1, "Set 1");
-        LocalMediaSet s4 = new LocalMediaSet(4, "Set 4");
-        LocalMediaSet s8 = new LocalMediaSet(8, "Set 8");
-        LocalMediaSet s9 = new LocalMediaSet(9, "Set 9");
-        LocalMediaSet s11 = new LocalMediaSet(11, "Set 11");
-        MediaItem t2 = new MyMediaItem();
-        MediaItem t3 = new MyMediaItem();
-        MediaItem t5 = new MyMediaItem();
-        MediaItem t6 = new MyMediaItem();
-        MediaItem t7 = new MyMediaItem();
-        MediaItem t10 = new MyMediaItem();
-
-        s0.addSubMediaSet(s1);
-        s0.addMediaItem(t2);
-        s0.addMediaItem(t3);
-        s0.addSubMediaSet(s4);
-        s4.addMediaItem(t5);
-        s4.addMediaItem(t6);
-        s4.addMediaItem(t7);
-        s4.addSubMediaSet(s8);
-        s8.addSubMediaSet(s9);
-        s9.addMediaItem(t10);
-        s8.addSubMediaSet(s11);
-
-        LocalMediaSet s = s0;
-
-        assertEquals(2, s.getMediaItemCount());
-        assertEquals(2, s.getSubMediaSetCount());
-        assertEquals(6, s.getTotalMediaItemCount());
-        assertTrue(s.getCoverMediaItems().length > 0);
-        assertSame(s1, s.getSubMediaSet(0));
-        assertSame(s4, s.getSubMediaSet(1));
-        assertSame(s1, s.getSubMediaSetById(1));
-        assertSame(s4, s.getSubMediaSetById(4));
-        assertNull(s.getSubMediaSetById(8));
-        assertSame(s8, s4.getSubMediaSetById(8));
-        assertNull(s.getSubMediaSetById(LocalMediaSet.ROOT_SET_ID));
-
-        MediaItem[] m = s.getCoverMediaItems();
-        for (int i = 0; i < m.length; i++) {
-            assertTrue(m[i] == t2 || m[i] == t3 || m[i] == t5
-                    || m[i] == t6 || m[i] == t7 || m[i] == t10);
-            for (int j = 0; j < i; j++) {
-                assertNotSame(m[j], m[i]);
-            }
-        }
-    }
-}