ManageCachePage.java revision 90fe70f11b0961e1e5ae353cdf563236a55f502d
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.app;
18
19import android.app.Activity;
20import android.content.res.Configuration;
21import android.os.Bundle;
22import android.os.Handler;
23import android.os.Message;
24import android.text.format.Formatter;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.View.OnClickListener;
28import android.widget.FrameLayout;
29import android.widget.ProgressBar;
30import android.widget.TextView;
31import android.widget.Toast;
32
33import com.android.gallery3d.R;
34import com.android.gallery3d.common.Utils;
35import com.android.gallery3d.data.MediaObject;
36import com.android.gallery3d.data.MediaSet;
37import com.android.gallery3d.data.Path;
38import com.android.gallery3d.ui.CacheStorageUsageInfo;
39import com.android.gallery3d.ui.GLCanvas;
40import com.android.gallery3d.ui.GLRoot;
41import com.android.gallery3d.ui.GLView;
42import com.android.gallery3d.ui.ManageCacheDrawer;
43import com.android.gallery3d.ui.MenuExecutor;
44import com.android.gallery3d.ui.SelectionManager;
45import com.android.gallery3d.ui.SlotView;
46import com.android.gallery3d.ui.SynchronizedHandler;
47import com.android.gallery3d.util.Future;
48import com.android.gallery3d.util.GalleryUtils;
49import com.android.gallery3d.util.ThreadPool.Job;
50import com.android.gallery3d.util.ThreadPool.JobContext;
51
52import java.util.ArrayList;
53
54public class ManageCachePage extends ActivityState implements
55        SelectionManager.SelectionListener, MenuExecutor.ProgressListener,
56        EyePosition.EyePositionListener, OnClickListener {
57    public static final String KEY_MEDIA_PATH = "media-path";
58
59    private static final String TAG = "ManageCachePage";
60
61    private static final int DATA_CACHE_SIZE = 256;
62    private static final int MSG_REFRESH_STORAGE_INFO = 1;
63    private static final int MSG_REQUEST_LAYOUT = 2;
64    private static final int PROGRESS_BAR_MAX = 10000;
65
66    private SlotView mSlotView;
67    private MediaSet mMediaSet;
68
69    protected SelectionManager mSelectionManager;
70    protected ManageCacheDrawer mSelectionDrawer;
71    private AlbumSetDataLoader mAlbumSetDataAdapter;
72
73    private EyePosition mEyePosition;
74
75    // The eyes' position of the user, the origin is at the center of the
76    // device and the unit is in pixels.
77    private float mX;
78    private float mY;
79    private float mZ;
80
81    private int mAlbumCountToMakeAvailableOffline;
82    private View mFooterContent;
83    private CacheStorageUsageInfo mCacheStorageInfo;
84    private Future<Void> mUpdateStorageInfo;
85    private Handler mHandler;
86    private boolean mLayoutReady = false;
87
88    private GLView mRootPane = new GLView() {
89        private float mMatrix[] = new float[16];
90
91        @Override
92        protected void renderBackground(GLCanvas view) {
93            view.clearBuffer();
94        }
95
96        @Override
97        protected void onLayout(
98                boolean changed, int left, int top, int right, int bottom) {
99            // Hack: our layout depends on other components on the screen.
100            // We assume the other components will complete before we get a change
101            // to run a message in main thread.
102            if (!mLayoutReady) {
103                mHandler.sendEmptyMessage(MSG_REQUEST_LAYOUT);
104                return;
105            }
106            mLayoutReady = false;
107
108            mEyePosition.resetPosition();
109            Activity activity = (Activity) mActivity;
110            int slotViewTop = mActivity.getGalleryActionBar().getHeight();
111            int slotViewBottom = bottom - top;
112
113            View footer = activity.findViewById(R.id.footer);
114            if (footer != null) {
115                int location[] = {0, 0};
116                footer.getLocationOnScreen(location);
117                slotViewBottom = location[1];
118            }
119
120            mSlotView.layout(0, slotViewTop, right - left, slotViewBottom);
121        }
122
123        @Override
124        protected void render(GLCanvas canvas) {
125            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
126            GalleryUtils.setViewPointMatrix(mMatrix,
127                        getWidth() / 2 + mX, getHeight() / 2 + mY, mZ);
128            canvas.multiplyMatrix(mMatrix, 0);
129            super.render(canvas);
130            canvas.restore();
131        }
132    };
133
134    @Override
135    public void onEyePositionChanged(float x, float y, float z) {
136        mRootPane.lockRendering();
137        mX = x;
138        mY = y;
139        mZ = z;
140        mRootPane.unlockRendering();
141        mRootPane.invalidate();
142    }
143
144    private void onDown(int index) {
145        mSelectionDrawer.setPressedIndex(index);
146    }
147
148    private void onUp() {
149        mSelectionDrawer.setPressedIndex(-1);
150    }
151
152    public void onSingleTapUp(int slotIndex) {
153        MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex);
154        if (targetSet == null) return; // Content is dirty, we shall reload soon
155
156        // ignore selection action if the target set does not support cache
157        // operation (like a local album).
158        if ((targetSet.getSupportedOperations()
159                & MediaSet.SUPPORT_CACHE) == 0) {
160            showToastForLocalAlbum();
161            return;
162        }
163
164        Path path = targetSet.getPath();
165        boolean isFullyCached =
166                (targetSet.getCacheFlag() == MediaObject.CACHE_FLAG_FULL);
167        boolean isSelected = mSelectionManager.isItemSelected(path);
168
169        if (!isFullyCached) {
170            // We only count the media sets that will be made available offline
171            // in this session.
172            if (isSelected) {
173                --mAlbumCountToMakeAvailableOffline;
174            } else {
175                ++mAlbumCountToMakeAvailableOffline;
176            }
177        }
178
179        long sizeOfTarget = targetSet.getCacheSize();
180        mCacheStorageInfo.increaseTargetCacheSize(
181                (isFullyCached ^ isSelected) ? -sizeOfTarget : sizeOfTarget);
182        refreshCacheStorageInfo();
183
184        mSelectionManager.toggle(path);
185        mSlotView.invalidate();
186    }
187
188    @Override
189    public void onCreate(Bundle data, Bundle restoreState) {
190        mCacheStorageInfo = new CacheStorageUsageInfo(mActivity);
191        initializeViews();
192        initializeData(data);
193        mEyePosition = new EyePosition(mActivity.getAndroidContext(), this);
194        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
195            @Override
196            public void handleMessage(Message message) {
197                switch (message.what) {
198                    case MSG_REFRESH_STORAGE_INFO:
199                        refreshCacheStorageInfo();
200                        break;
201                    case MSG_REQUEST_LAYOUT: {
202                        mLayoutReady = true;
203                        removeMessages(MSG_REQUEST_LAYOUT);
204                        mRootPane.requestLayout();
205                        break;
206                    }
207                }
208            }
209        };
210    }
211
212    @Override
213    public void onConfigurationChanged(Configuration config) {
214        // We use different layout resources for different configs
215        initializeFooterViews();
216        FrameLayout layout = (FrameLayout) ((Activity) mActivity).findViewById(R.id.footer);
217        if (layout.getVisibility() == View.VISIBLE) {
218            layout.removeAllViews();
219            layout.addView(mFooterContent);
220        }
221    }
222
223    @Override
224    public void onPause() {
225        super.onPause();
226        mAlbumSetDataAdapter.pause();
227        mSelectionDrawer.pause();
228        mEyePosition.pause();
229
230        if (mUpdateStorageInfo != null) {
231            mUpdateStorageInfo.cancel();
232            mUpdateStorageInfo = null;
233        }
234        mHandler.removeMessages(MSG_REFRESH_STORAGE_INFO);
235
236        FrameLayout layout = (FrameLayout) ((Activity) mActivity).findViewById(R.id.footer);
237        layout.removeAllViews();
238        layout.setVisibility(View.INVISIBLE);
239    }
240
241    private Job<Void> mUpdateStorageInfoJob = new Job<Void>() {
242        @Override
243        public Void run(JobContext jc) {
244            mCacheStorageInfo.loadStorageInfo(jc);
245            if (!jc.isCancelled()) {
246                mHandler.sendEmptyMessage(MSG_REFRESH_STORAGE_INFO);
247            }
248            return null;
249        }
250    };
251
252    @Override
253    public void onResume() {
254        super.onResume();
255        setContentPane(mRootPane);
256        mAlbumSetDataAdapter.resume();
257        mSelectionDrawer.resume();
258        mEyePosition.resume();
259        mUpdateStorageInfo = mActivity.getThreadPool().submit(mUpdateStorageInfoJob);
260        FrameLayout layout = (FrameLayout) ((Activity) mActivity).findViewById(R.id.footer);
261        layout.addView(mFooterContent);
262        layout.setVisibility(View.VISIBLE);
263    }
264
265    private void initializeData(Bundle data) {
266        String mediaPath = data.getString(ManageCachePage.KEY_MEDIA_PATH);
267        mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
268        mSelectionManager.setSourceMediaSet(mMediaSet);
269
270        // We will always be in selection mode in this page.
271        mSelectionManager.setAutoLeaveSelectionMode(false);
272        mSelectionManager.enterSelectionMode();
273
274        mAlbumSetDataAdapter = new AlbumSetDataLoader(
275                mActivity, mMediaSet, DATA_CACHE_SIZE);
276        mSelectionDrawer.setModel(mAlbumSetDataAdapter);
277    }
278
279    private void initializeViews() {
280        Activity activity = (Activity) mActivity;
281
282        mSelectionManager = new SelectionManager(mActivity, true);
283        mSelectionManager.setSelectionListener(this);
284
285        Config.ManageCachePage config = Config.ManageCachePage.get(activity);
286        mSlotView = new SlotView(mActivity, config.slotViewSpec);
287        mSelectionDrawer = new ManageCacheDrawer(mActivity, mSelectionManager, mSlotView,
288                config.labelSpec, config.cachePinSize, config.cachePinMargin);
289        mSlotView.setSlotRenderer(mSelectionDrawer);
290        mSlotView.setListener(new SlotView.SimpleListener() {
291            @Override
292            public void onDown(int index) {
293                ManageCachePage.this.onDown(index);
294            }
295
296            @Override
297            public void onUp(boolean followedByLongPress) {
298                ManageCachePage.this.onUp();
299            }
300
301            @Override
302            public void onSingleTapUp(int slotIndex) {
303                ManageCachePage.this.onSingleTapUp(slotIndex);
304            }
305        });
306        mRootPane.addComponent(mSlotView);
307        initializeFooterViews();
308    }
309
310    private void initializeFooterViews() {
311        Activity activity = (Activity) mActivity;
312
313        FrameLayout footer = (FrameLayout) activity.findViewById(R.id.footer);
314        LayoutInflater inflater = activity.getLayoutInflater();
315        mFooterContent = inflater.inflate(R.layout.manage_offline_bar, null);
316
317        mFooterContent.findViewById(R.id.done).setOnClickListener(this);
318        refreshCacheStorageInfo();
319    }
320
321    @Override
322    public void onClick(View view) {
323        Utils.assertTrue(view.getId() == R.id.done);
324        GLRoot root = mActivity.getGLRoot();
325        root.lockRenderThread();
326        try {
327            ArrayList<Path> ids = mSelectionManager.getSelected(false);
328            if (ids.size() == 0) {
329                onBackPressed();
330                return;
331            }
332            showToast();
333
334            MenuExecutor menuExecutor = new MenuExecutor(mActivity, mSelectionManager);
335            menuExecutor.startAction(R.id.action_toggle_full_caching,
336                    R.string.process_caching_requests, this);
337        } finally {
338            root.unlockRenderThread();
339        }
340    }
341
342    private void showToast() {
343        if (mAlbumCountToMakeAvailableOffline > 0) {
344            Activity activity = (Activity) mActivity;
345            Toast.makeText(activity, activity.getResources().getQuantityString(
346                    R.plurals.make_albums_available_offline,
347                    mAlbumCountToMakeAvailableOffline),
348                    Toast.LENGTH_SHORT).show();
349        }
350    }
351
352    private void showToastForLocalAlbum() {
353        Activity activity = (Activity) mActivity;
354        Toast.makeText(activity, activity.getResources().getString(
355            R.string.try_to_set_local_album_available_offline),
356            Toast.LENGTH_SHORT).show();
357    }
358
359    private void refreshCacheStorageInfo() {
360        ProgressBar progressBar = (ProgressBar) mFooterContent.findViewById(R.id.progress);
361        TextView status = (TextView) mFooterContent.findViewById(R.id.status);
362        progressBar.setMax(PROGRESS_BAR_MAX);
363        long totalBytes = mCacheStorageInfo.getTotalBytes();
364        long usedBytes = mCacheStorageInfo.getUsedBytes();
365        long expectedBytes = mCacheStorageInfo.getExpectedUsedBytes();
366        long freeBytes = mCacheStorageInfo.getFreeBytes();
367
368        Activity activity = (Activity) mActivity;
369        if (totalBytes == 0) {
370            progressBar.setProgress(0);
371            progressBar.setSecondaryProgress(0);
372
373            // TODO: get the string translated
374            String label = activity.getString(R.string.free_space_format, "-");
375            status.setText(label);
376        } else {
377            progressBar.setProgress((int) (usedBytes * PROGRESS_BAR_MAX / totalBytes));
378            progressBar.setSecondaryProgress(
379                    (int) (expectedBytes * PROGRESS_BAR_MAX / totalBytes));
380            String label = activity.getString(R.string.free_space_format,
381                    Formatter.formatFileSize(activity, freeBytes));
382            status.setText(label);
383        }
384    }
385
386    @Override
387    public void onProgressComplete(int result) {
388        onBackPressed();
389    }
390
391    @Override
392    public void onProgressUpdate(int index) {
393    }
394
395    @Override
396    public void onSelectionModeChange(int mode) {
397    }
398
399    @Override
400    public void onSelectionChange(Path path, boolean selected) {
401    }
402}
403