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 com.android.gallery3d.R;
20import com.android.gallery3d.common.Utils;
21import com.android.gallery3d.data.MediaObject;
22import com.android.gallery3d.data.MediaSet;
23import com.android.gallery3d.data.Path;
24import com.android.gallery3d.ui.AlbumSetView;
25import com.android.gallery3d.ui.CacheStorageUsageInfo;
26import com.android.gallery3d.ui.GLCanvas;
27import com.android.gallery3d.ui.GLView;
28import com.android.gallery3d.ui.ManageCacheDrawer;
29import com.android.gallery3d.ui.MenuExecutor;
30import com.android.gallery3d.ui.SelectionDrawer;
31import com.android.gallery3d.ui.SelectionManager;
32import com.android.gallery3d.ui.SlotView;
33import com.android.gallery3d.ui.StaticBackground;
34import com.android.gallery3d.ui.SynchronizedHandler;
35import com.android.gallery3d.util.Future;
36import com.android.gallery3d.util.GalleryUtils;
37import com.android.gallery3d.util.ThreadPool.Job;
38import com.android.gallery3d.util.ThreadPool.JobContext;
39
40import android.app.Activity;
41import android.content.Context;
42import android.content.res.Configuration;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.Message;
46import android.text.format.Formatter;
47import android.view.LayoutInflater;
48import android.view.View;
49import android.view.View.OnClickListener;
50import android.widget.FrameLayout;
51import android.widget.ProgressBar;
52import android.widget.TextView;
53import android.widget.Toast;
54
55import java.util.ArrayList;
56
57public class ManageCachePage extends ActivityState implements
58        SelectionManager.SelectionListener, MenuExecutor.ProgressListener,
59        EyePosition.EyePositionListener, OnClickListener {
60    public static final String KEY_MEDIA_PATH = "media-path";
61
62    private static final String TAG = "ManageCachePage";
63
64    private static final float USER_DISTANCE_METER = 0.3f;
65    private static final int DATA_CACHE_SIZE = 256;
66    private static final int MSG_REFRESH_STORAGE_INFO = 1;
67    private static final int MSG_REQUEST_LAYOUT = 2;
68    private static final int PROGRESS_BAR_MAX = 10000;
69
70    private StaticBackground mStaticBackground;
71    private AlbumSetView mAlbumSetView;
72
73    private MediaSet mMediaSet;
74
75    protected SelectionManager mSelectionManager;
76    protected SelectionDrawer mSelectionDrawer;
77    private AlbumSetDataAdapter mAlbumSetDataAdapter;
78    private float mUserDistance; // in pixel
79
80    private EyePosition mEyePosition;
81
82    // The eyes' position of the user, the origin is at the center of the
83    // device and the unit is in pixels.
84    private float mX;
85    private float mY;
86    private float mZ;
87
88    private int mAlbumCountToMakeAvailableOffline;
89    private View mFooterContent;
90    private CacheStorageUsageInfo mCacheStorageInfo;
91    private Future<Void> mUpdateStorageInfo;
92    private Handler mHandler;
93    private boolean mLayoutReady = false;
94
95    private GLView mRootPane = new GLView() {
96        private float mMatrix[] = new float[16];
97
98        @Override
99        protected void onLayout(
100                boolean changed, int left, int top, int right, int bottom) {
101            // Hack: our layout depends on other components on the screen.
102            // We assume the other components will complete before we get a change
103            // to run a message in main thread.
104            if (!mLayoutReady) {
105                mHandler.sendEmptyMessage(MSG_REQUEST_LAYOUT);
106                return;
107            }
108            mLayoutReady = false;
109
110            mStaticBackground.layout(0, 0, right - left, bottom - top);
111            mEyePosition.resetPosition();
112            Activity activity = (Activity) mActivity;
113            int slotViewTop = GalleryActionBar.getHeight(activity);
114            int slotViewBottom = bottom - top;
115
116            View footer = activity.findViewById(R.id.footer);
117            if (footer != null) {
118                int location[] = {0, 0};
119                footer.getLocationOnScreen(location);
120                slotViewBottom = location[1];
121            }
122
123            mAlbumSetView.layout(0, slotViewTop, right - left, slotViewBottom);
124        }
125
126        @Override
127        protected void render(GLCanvas canvas) {
128            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
129            GalleryUtils.setViewPointMatrix(mMatrix,
130                        getWidth() / 2 + mX, getHeight() / 2 + mY, mZ);
131            canvas.multiplyMatrix(mMatrix, 0);
132            super.render(canvas);
133            canvas.restore();
134        }
135    };
136
137    public void onEyePositionChanged(float x, float y, float z) {
138        mRootPane.lockRendering();
139        mX = x;
140        mY = y;
141        mZ = z;
142        mRootPane.unlockRendering();
143        mRootPane.invalidate();
144    }
145
146    private void onDown(int index) {
147        MediaSet set = mAlbumSetDataAdapter.getMediaSet(index);
148        Path path = (set == null) ? null : set.getPath();
149        mSelectionManager.setPressedPath(path);
150        mAlbumSetView.invalidate();
151    }
152
153    private void onUp() {
154        mSelectionManager.setPressedPath(null);
155        mAlbumSetView.invalidate();
156    }
157
158    public void onSingleTapUp(int slotIndex) {
159        MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex);
160        if (targetSet == null) return; // Content is dirty, we shall reload soon
161
162        // ignore selection action if the target set does not support cache
163        // operation (like a local album).
164        if ((targetSet.getSupportedOperations()
165                & MediaSet.SUPPORT_CACHE) == 0) {
166            showToastForLocalAlbum();
167            return;
168        }
169
170        Path path = targetSet.getPath();
171        boolean isFullyCached =
172                (targetSet.getCacheFlag() == MediaObject.CACHE_FLAG_FULL);
173        boolean isSelected = mSelectionManager.isItemSelected(path);
174
175        if (!isFullyCached) {
176            // We only count the media sets that will be made available offline
177            // in this session.
178            if (isSelected) {
179                --mAlbumCountToMakeAvailableOffline;
180            } else {
181                ++mAlbumCountToMakeAvailableOffline;
182            }
183        }
184
185        long sizeOfTarget = targetSet.getCacheSize();
186        mCacheStorageInfo.increaseTargetCacheSize(
187                (isFullyCached ^ isSelected) ? -sizeOfTarget : sizeOfTarget);
188        refreshCacheStorageInfo();
189
190        mSelectionManager.toggle(path);
191        mAlbumSetView.invalidate();
192    }
193
194    @Override
195    public void onCreate(Bundle data, Bundle 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        mAlbumSetView.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        mAlbumSetView.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        mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
273        String mediaPath = data.getString(ManageCachePage.KEY_MEDIA_PATH);
274        mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
275        mSelectionManager.setSourceMediaSet(mMediaSet);
276
277        // We will always be in selection mode in this page.
278        mSelectionManager.setAutoLeaveSelectionMode(false);
279        mSelectionManager.enterSelectionMode();
280
281        mAlbumSetDataAdapter = new AlbumSetDataAdapter(
282                mActivity, mMediaSet, DATA_CACHE_SIZE);
283        mAlbumSetView.setModel(mAlbumSetDataAdapter);
284    }
285
286    private void initializeViews() {
287        Activity activity = (Activity) mActivity;
288
289        mSelectionManager = new SelectionManager(mActivity, true);
290        mSelectionManager.setSelectionListener(this);
291        mStaticBackground = new StaticBackground(activity);
292        mRootPane.addComponent(mStaticBackground);
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        mStaticBackground.setImage(R.drawable.background, R.drawable.background_portrait);
328        refreshCacheStorageInfo();
329    }
330
331    @Override
332    public void onClick(View view) {
333        Utils.assertTrue(view.getId() == R.id.done);
334
335        ArrayList<Path> ids = mSelectionManager.getSelected(false);
336        if (ids.size() == 0) {
337            onBackPressed();
338            return;
339        }
340        showToast();
341
342        MenuExecutor menuExecutor = new MenuExecutor(mActivity, mSelectionManager);
343        menuExecutor.startAction(R.id.action_toggle_full_caching,
344                R.string.process_caching_requests, this);
345    }
346
347    private void showToast() {
348        if (mAlbumCountToMakeAvailableOffline > 0) {
349            Activity 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 = (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 = (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    public void onProgressComplete(int result) {
392        onBackPressed();
393    }
394
395    public void onProgressUpdate(int index) {
396    }
397
398    public void onSelectionModeChange(int mode) {
399    }
400
401    public void onSelectionChange(Path path, boolean selected) {
402    }
403
404}
405