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        LayoutInflater inflater = activity.getLayoutInflater();
314        mFooterContent = inflater.inflate(R.layout.manage_offline_bar, null);
315
316        mFooterContent.findViewById(R.id.done).setOnClickListener(this);
317        refreshCacheStorageInfo();
318    }
319
320    @Override
321    public void onClick(View view) {
322        Utils.assertTrue(view.getId() == R.id.done);
323        GLRoot root = mActivity.getGLRoot();
324        root.lockRenderThread();
325        try {
326            ArrayList<Path> ids = mSelectionManager.getSelected(false);
327            if (ids.size() == 0) {
328                onBackPressed();
329                return;
330            }
331            showToast();
332
333            MenuExecutor menuExecutor = new MenuExecutor(mActivity, mSelectionManager);
334            menuExecutor.startAction(R.id.action_toggle_full_caching,
335                    R.string.process_caching_requests, this);
336        } finally {
337            root.unlockRenderThread();
338        }
339    }
340
341    private void showToast() {
342        if (mAlbumCountToMakeAvailableOffline > 0) {
343            Activity activity = (Activity) mActivity;
344            Toast.makeText(activity, activity.getResources().getQuantityString(
345                    R.plurals.make_albums_available_offline,
346                    mAlbumCountToMakeAvailableOffline),
347                    Toast.LENGTH_SHORT).show();
348        }
349    }
350
351    private void showToastForLocalAlbum() {
352        Activity activity = (Activity) mActivity;
353        Toast.makeText(activity, activity.getResources().getString(
354            R.string.try_to_set_local_album_available_offline),
355            Toast.LENGTH_SHORT).show();
356    }
357
358    private void refreshCacheStorageInfo() {
359        ProgressBar progressBar = (ProgressBar) mFooterContent.findViewById(R.id.progress);
360        TextView status = (TextView) mFooterContent.findViewById(R.id.status);
361        progressBar.setMax(PROGRESS_BAR_MAX);
362        long totalBytes = mCacheStorageInfo.getTotalBytes();
363        long usedBytes = mCacheStorageInfo.getUsedBytes();
364        long expectedBytes = mCacheStorageInfo.getExpectedUsedBytes();
365        long freeBytes = mCacheStorageInfo.getFreeBytes();
366
367        Activity activity = (Activity) mActivity;
368        if (totalBytes == 0) {
369            progressBar.setProgress(0);
370            progressBar.setSecondaryProgress(0);
371
372            // TODO: get the string translated
373            String label = activity.getString(R.string.free_space_format, "-");
374            status.setText(label);
375        } else {
376            progressBar.setProgress((int) (usedBytes * PROGRESS_BAR_MAX / totalBytes));
377            progressBar.setSecondaryProgress(
378                    (int) (expectedBytes * PROGRESS_BAR_MAX / totalBytes));
379            String label = activity.getString(R.string.free_space_format,
380                    Formatter.formatFileSize(activity, freeBytes));
381            status.setText(label);
382        }
383    }
384
385    @Override
386    public void onProgressComplete(int result) {
387        onBackPressed();
388    }
389
390    @Override
391    public void onProgressUpdate(int index) {
392    }
393
394    @Override
395    public void onSelectionModeChange(int mode) {
396    }
397
398    @Override
399    public void onSelectionChange(Path path, boolean selected) {
400    }
401
402    @Override
403    public void onConfirmDialogDismissed(boolean confirmed) {
404    }
405
406    @Override
407    public void onConfirmDialogShown() {
408    }
409}
410