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