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