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 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