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.ui.CacheStorageUsageInfo; 39 import com.android.gallery3d.ui.GLCanvas; 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 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 onEyePositionChanged(float x, float y, float z)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 onDown(int index)144 private void onDown(int index) { 145 mSelectionDrawer.setPressedIndex(index); 146 } 147 onUp()148 private void onUp() { 149 mSelectionDrawer.setPressedIndex(-1); 150 } 151 onSingleTapUp(int slotIndex)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 onCreate(Bundle data, Bundle restoreState)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 onConfigurationChanged(Configuration config)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 onPause()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 onResume()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 initializeData(Bundle data)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 initializeViews()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 initializeFooterViews()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 onClick(View view)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 showToast()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 showToastForLocalAlbum()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 refreshCacheStorageInfo()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 onProgressComplete(int result)386 public void onProgressComplete(int result) { 387 onBackPressed(); 388 } 389 390 @Override onProgressUpdate(int index)391 public void onProgressUpdate(int index) { 392 } 393 394 @Override onSelectionModeChange(int mode)395 public void onSelectionModeChange(int mode) { 396 } 397 398 @Override onSelectionChange(Path path, boolean selected)399 public void onSelectionChange(Path path, boolean selected) { 400 } 401 402 @Override onConfirmDialogDismissed(boolean confirmed)403 public void onConfirmDialogDismissed(boolean confirmed) { 404 } 405 406 @Override onConfirmDialogShown()407 public void onConfirmDialogShown() { 408 } 409 } 410