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