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 
17 package com.android.gallery3d.app;
18 
19 import android.app.Activity;
20 import android.content.res.Configuration;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.os.Message;
24 import android.text.format.Formatter;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.View.OnClickListener;
28 import android.widget.FrameLayout;
29 import android.widget.ProgressBar;
30 import android.widget.TextView;
31 import android.widget.Toast;
32 
33 import com.android.gallery3d.R;
34 import com.android.gallery3d.common.Utils;
35 import com.android.gallery3d.data.MediaObject;
36 import com.android.gallery3d.data.MediaSet;
37 import com.android.gallery3d.data.Path;
38 import com.android.gallery3d.glrenderer.GLCanvas;
39 import com.android.gallery3d.ui.CacheStorageUsageInfo;
40 import com.android.gallery3d.ui.GLRoot;
41 import com.android.gallery3d.ui.GLView;
42 import com.android.gallery3d.ui.ManageCacheDrawer;
43 import com.android.gallery3d.ui.MenuExecutor;
44 import com.android.gallery3d.ui.SelectionManager;
45 import com.android.gallery3d.ui.SlotView;
46 import com.android.gallery3d.ui.SynchronizedHandler;
47 import com.android.gallery3d.util.Future;
48 import com.android.gallery3d.util.GalleryUtils;
49 import com.android.gallery3d.util.ThreadPool.Job;
50 import com.android.gallery3d.util.ThreadPool.JobContext;
51 
52 import java.util.ArrayList;
53 
54 public 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
getBackgroundColorId()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
onEyePositionChanged(float x, float y, float z)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 
onDown(int index)149     private void onDown(int index) {
150         mSelectionDrawer.setPressedIndex(index);
151     }
152 
onUp()153     private void onUp() {
154         mSelectionDrawer.setPressedIndex(-1);
155     }
156 
onSingleTapUp(int slotIndex)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
onCreate(Bundle data, Bundle restoreState)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
onConfigurationChanged(Configuration config)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
onPause()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
onResume()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 
initializeData(Bundle data)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 
initializeViews()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 
initializeFooterViews()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
onClick(View view)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 
showToast()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 
showToastForLocalAlbum()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 
refreshCacheStorageInfo()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
onProgressComplete(int result)392     public void onProgressComplete(int result) {
393         onBackPressed();
394     }
395 
396     @Override
onProgressUpdate(int index)397     public void onProgressUpdate(int index) {
398     }
399 
400     @Override
onSelectionModeChange(int mode)401     public void onSelectionModeChange(int mode) {
402     }
403 
404     @Override
onSelectionChange(Path path, boolean selected)405     public void onSelectionChange(Path path, boolean selected) {
406     }
407 
408     @Override
onConfirmDialogDismissed(boolean confirmed)409     public void onConfirmDialogDismissed(boolean confirmed) {
410     }
411 
412     @Override
onConfirmDialogShown()413     public void onConfirmDialogShown() {
414     }
415 
416     @Override
onProgressStart()417     public void onProgressStart() {
418     }
419 }
420