public class BootReceiver extends BroadcastReceiver {
private static final String TAG = "BootReceiver";
private final Handler mHandler = new Handler();
+ private boolean mListenersInitialized = false;
@Override
public void onReceive(final Context context, Intent intent) {
CacheService.markDirty(context);
CacheService.startCache(context, true);
} else if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
- // Do nothing, wait for the mediascanner to be done after mounting.
- ;
+ if (!mListenersInitialized) {
+ // We add special listeners for the MediaProvider
+ mListenersInitialized = true;
+ final Handler handler = mHandler;
+ final ContentObserver localObserver = new ContentObserver(handler) {
+ public void onChange(boolean selfChange) {
+ if (!LocalDataSource.sObserverActive) {
+ CacheService.senseDirty(context, null);
+ }
+ }
+ };
+ // Start listening perpetually.
+ Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
+ Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
+ ContentResolver cr = context.getContentResolver();
+ cr.registerContentObserver(uriImages, false, localObserver);
+ cr.registerContentObserver(uriVideos, false, localObserver);
+ }
} else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)) {
final Uri fileUri = intent.getData();
final long bucketId = SingleDataSource.parseBucketIdFromFileUri(fileUri.toString());
if (!CacheService.isPresentInCache(bucketId)) {
CacheService.markDirty(context);
}
- } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- // We add special listeners for the MediaProvider
- final Handler handler = mHandler;
- final ContentObserver localObserver = new ContentObserver(handler) {
- public void onChange(boolean selfChange) {
- if (!LocalDataSource.sObserverActive) {
- CacheService.senseDirty(context, null);
- }
- }
- };
- // Start listening perpetually.
- Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
- Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
- ContentResolver cr = context.getContentResolver();
- cr.registerContentObserver(uriImages, false, localObserver);
- cr.registerContentObserver(uriVideos, false, localObserver);
} else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
LocalDataSource.sThumbnailCache.close();
LocalDataSource.sThumbnailCacheVideo.close();
PicasaDataSource.sThumbnailCache.close();
CacheService.sAlbumCache.close();
CacheService.sMetaAlbumCache.close();
+ CacheService.sSkipThumbnailIds.flush();
}
}
}
public static final String ACTION_CACHE = "com.cooliris.cache.action.CACHE";
public static final DiskCache sAlbumCache = new DiskCache("local-album-cache");
public static final DiskCache sMetaAlbumCache = new DiskCache("local-meta-cache");
+ public static final DiskCache sSkipThumbnailIds = new DiskCache("local-skip-cache");
private static final String TAG = "CacheService";
private static ImageList sList = null;
final long id = ids[i];
final long timeModifiedInSec = timestamp[i];
final long thumbnailId = thumbnailIds[i];
- if (!thumbnailCache.isDataAvailable(thumbnailId, timeModifiedInSec * 1000)) {
- buildThumbnailForId(context, thumbnailCache, thumbnailId, id, false, DEFAULT_THUMBNAIL_WIDTH,
- DEFAULT_THUMBNAIL_HEIGHT, timeModifiedInSec * 1000);
+ if (!isInThumbnailerSkipList(thumbnailId)) {
+ if (!thumbnailCache.isDataAvailable(thumbnailId, timeModifiedInSec * 1000)) {
+ byte[] retVal = buildThumbnailForId(context, thumbnailCache, thumbnailId, id, false, DEFAULT_THUMBNAIL_WIDTH,
+ DEFAULT_THUMBNAIL_HEIGHT, timeModifiedInSec * 1000);
+ if (retVal == null || retVal.length == 0) {
+ // There was an error in building the thumbnail.
+ // We record this thumbnail id
+ addToThumbnailerSkipList(thumbnailId);
+ }
+ }
}
}
Log.i(TAG, "DiskCache ready for all thumbnails.");
}
+ private static void addToThumbnailerSkipList(long thumbnailId) {
+ sSkipThumbnailIds.put(thumbnailId, sDummyData, 0);
+ sSkipThumbnailIds.flush();
+ }
+
+ private static boolean isInThumbnailerSkipList(long thumbnailId) {
+ if (sSkipThumbnailIds.isDataAvailable(thumbnailId, 0)) {
+ byte[] data = sSkipThumbnailIds.get(thumbnailId, 0);
+ if (data.length > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static final byte[] buildThumbnailForId(final Context context, final DiskCache thumbnailCache, final long thumbId,
final long origId, final boolean isVideo, final int thumbnailWidth, final int thumbnailHeight, final long timestamp) {
if (origId == Shared.INVALID) {
if (bitmap == null) {
return null;
}
- final byte[] retVal = writeBitmapToCache(thumbnailCache, thumbId, origId, bitmap, thumbnailWidth, thumbnailHeight, timestamp);
+ final byte[] retVal = writeBitmapToCache(thumbnailCache, thumbId, origId, bitmap, thumbnailWidth, thumbnailHeight,
+ timestamp);
return retVal;
} catch (InterruptedException e) {
return null;
private MediaScannerConnection mConnection;
private WakeLock mWakeLock;
private HashMap<String, Boolean> mAccountsEnabled = new HashMap<String, Boolean>();
+ private boolean mDockSlideshow = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- final boolean imageManagerHasStorage = ImageManager.quickHasStorage();
+ final boolean imageManagerHasStorage = ImageManager.hasStorage();
boolean slideshowIntent = false;
if (isViewIntent()) {
Bundle extras = getIntent().getExtras();
Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
finish();
} else {
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow.All");
- mWakeLock.acquire();
Slideshow slideshow = new Slideshow(this);
slideshow.setDataSource(new RandomDataSource());
setContentView(slideshow);
+ mDockSlideshow = true;
}
return;
}
- CacheService.computeDirtySets(this);
- final boolean isCacheReady = CacheService.isCacheReady(false);
if (PIXEL_DENSITY == 0.0f) {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mRenderView);
mRenderView.setRootLayer(mGridLayer);
setContentView(mRenderView);
+
+ Thread t = new Thread() {
+ public void run() {
+ int numRetries = 25;
+ if (!imageManagerHasStorage) {
+ showToast(getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG);
+ do {
+ --numRetries;
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ ;
+ }
+ } while (numRetries > 0 && !ImageManager.hasStorage());
+ }
+ final boolean imageManagerHasStorageAfterDelay = ImageManager.hasStorage();
+ CacheService.computeDirtySets(Gallery.this);
+ CacheService.startCache(Gallery.this, false);
+ final boolean isCacheReady = CacheService.isCacheReady(false);
- // Creating the DataSource objects.
- final PicasaDataSource picasaDataSource = new PicasaDataSource(this);
- final LocalDataSource localDataSource = new LocalDataSource(this);
- final ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
+ // Creating the DataSource objects.
+ final PicasaDataSource picasaDataSource = new PicasaDataSource(Gallery.this);
+ final LocalDataSource localDataSource = new LocalDataSource(Gallery.this);
+ final ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
- // Depending upon the intent, we assign the right dataSource.
- if (!isPickIntent() && !isViewIntent()) {
- if (imageManagerHasStorage) {
- mGridLayer.setDataSource(combinedDataSource);
- } else {
- mGridLayer.setDataSource(picasaDataSource);
- }
- if (!imageManagerHasStorage) {
- Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
- } else if (!isCacheReady) {
- Toast.makeText(this, getResources().getString(R.string.loading_new), Toast.LENGTH_LONG).show();
- }
- } else if (!isViewIntent()) {
- final Intent intent = getIntent();
- if (intent != null) {
- final String type = intent.resolveType(this);
- boolean includeImages = isImageType(type);
- boolean includeVideos = isVideoType(type);
- ((LocalDataSource) localDataSource).setMimeFilter(!includeImages, !includeVideos);
- if (includeImages) {
- if (imageManagerHasStorage) {
+ // Depending upon the intent, we assign the right dataSource.
+ if (!isPickIntent() && !isViewIntent()) {
+ if (imageManagerHasStorageAfterDelay) {
mGridLayer.setDataSource(combinedDataSource);
} else {
mGridLayer.setDataSource(picasaDataSource);
}
+ if (!isCacheReady && imageManagerHasStorageAfterDelay) {
+ showToast(getResources().getString(R.string.loading_new), Toast.LENGTH_LONG);
+ }
+ } else if (!isViewIntent()) {
+ final Intent intent = getIntent();
+ if (intent != null) {
+ final String type = intent.resolveType(Gallery.this);
+ boolean includeImages = isImageType(type);
+ boolean includeVideos = isVideoType(type);
+ ((LocalDataSource) localDataSource).setMimeFilter(!includeImages, !includeVideos);
+ if (includeImages) {
+ if (imageManagerHasStorageAfterDelay) {
+ mGridLayer.setDataSource(combinedDataSource);
+ } else {
+ mGridLayer.setDataSource(picasaDataSource);
+ }
+ } else {
+ mGridLayer.setDataSource(localDataSource);
+ }
+ mGridLayer.setPickIntent(true);
+ if (!imageManagerHasStorageAfterDelay) {
+ showToast(getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG);
+ } else {
+ showToast(getResources().getString(R.string.pick_prompt), Toast.LENGTH_LONG);
+ }
+ }
} else {
- mGridLayer.setDataSource(localDataSource);
- }
- mGridLayer.setPickIntent(true);
- if (!imageManagerHasStorage) {
- Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
- } else {
- Toast.makeText(this, getResources().getString(R.string.pick_prompt), Toast.LENGTH_LONG).show();
+ // View intent for images.
+ Uri uri = getIntent().getData();
+ boolean slideshow = getIntent().getBooleanExtra("slideshow", false);
+ final SingleDataSource singleDataSource = new SingleDataSource(Gallery.this, uri.toString(), slideshow);
+ final ConcatenatedDataSource singleCombinedDataSource = new ConcatenatedDataSource(singleDataSource, picasaDataSource);
+ mGridLayer.setDataSource(singleCombinedDataSource);
+ mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri(uri));
+ if (singleDataSource.isSingleImage()) {
+ mGridLayer.setSingleImage(false);
+ } else if (slideshow) {
+ mGridLayer.setSingleImage(true);
+ mGridLayer.startSlideshow();
+ }
}
}
- } else {
- // View intent for images.
- Uri uri = getIntent().getData();
- boolean slideshow = getIntent().getBooleanExtra("slideshow", false);
- final SingleDataSource singleDataSource = new SingleDataSource(this, uri.toString(), slideshow);
- final ConcatenatedDataSource singleCombinedDataSource = new ConcatenatedDataSource(singleDataSource, picasaDataSource);
- mGridLayer.setDataSource(singleCombinedDataSource);
- mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri(uri));
- if (singleDataSource.isSingleImage()) {
- mGridLayer.setSingleImage(false);
- } else if (slideshow) {
- mGridLayer.setSingleImage(true);
- mGridLayer.startSlideshow();
- }
- }
- // We record the set of enabled accounts for picasa.
+ };
+ t.start();
+ //We record the set of enabled accounts for picasa.
mAccountsEnabled = PicasaDataSource.getAccountStatus(this);
Log.i(TAG, "onCreate");
}
+
+ private void showToast(final String string, final int duration) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ Toast.makeText(Gallery.this, string, duration).show();
+ }
+ });
+ }
public ReverseGeocoder getReverseGeocoder() {
return mReverseGeocoder;
@Override
public void onResume() {
super.onResume();
- CacheService.computeDirtySets(this);
- CacheService.startCache(this, false);
+ if (mDockSlideshow) {
+ if (mWakeLock != null) {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow.All");
+ mWakeLock.acquire();
+ return;
+ }
+ if (ImageManager.hasStorage()) {
+ CacheService.computeDirtySets(this);
+ CacheService.startCache(this, false);
+ }
if (mRenderView != null) {
mRenderView.onResume();
}
super.onPause();
if (mRenderView != null)
mRenderView.onPause();
+ if (mWakeLock != null) {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ mWakeLock = null;
+ }
mPause = true;
}
}
mGridLayer.shutdown();
}
- if (mWakeLock != null) {
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- mWakeLock = null;
- }
if (mReverseGeocoder != null)
mReverseGeocoder.shutdown();
if (mRenderView != null) {
import android.view.KeyEvent;
import android.view.MotionEvent;
-public final class GridInputProcessor implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
+public final class GridInputProcessor implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener,
+ ScaleGestureDetector.OnScaleGestureListener {
private int mCurrentFocusSlot;
private boolean mCurrentFocusIsPressed;
private int mCurrentSelectedSlot;
private boolean mPrevHitEdge;
private boolean mTouchFeedbackDelivered;
private GestureDetector mGestureDetector;
+ private ScaleGestureDetector mScaleGestureDetector;
+ private boolean mZoomGesture;
public GridInputProcessor(Context context, GridCamera camera, GridLayer layer, RenderView view, Pool<Vector3f> pool,
DisplayItem[] displayItems) {
mContext = context;
mDisplayItems = displayItems;
mGestureDetector = new GestureDetector(context, this);
+ mScaleGestureDetector = new ScaleGestureDetector(context, this);
mGestureDetector.setIsLongpressEnabled(true);
+ mZoomGesture = false;
}
public int getCurrentFocusSlot() {
break;
}
mGestureDetector.onTouchEvent(event);
+ mScaleGestureDetector.onTouchEvent(event);
return true;
}
}
private void touchMoved(int posX, int posY, float timeElapsedx) {
- if (mProcessTouch) {
+ if (mProcessTouch && !mZoomGesture) {
GridLayer layer = mLayer;
GridCamera camera = mCamera;
float deltaX = -(posX - mPrevTouchPosX); // negation since the wall
}
private void touchEnded(int posX, int posY, float timeElapsedx) {
- if (mProcessTouch == false)
+ if (mProcessTouch == false) {
+ mZoomGesture = false;
return;
+ }
int maxPixelsBeforeSwitch = mCamera.mWidth / 8;
mCamera.mConvergenceSpeed = 2.0f;
GridLayer layer = mLayer;
- if (layer.getExpandedSlot() == Shared.INVALID && !layer.feedAboutToChange()) {
+ if (layer.getExpandedSlot() == Shared.INVALID && !layer.feedAboutToChange() && !mZoomGesture) {
if (mCurrentSelectedSlot != Shared.INVALID) {
if (layer.getState() == GridLayer.STATE_FULL_SCREEN) {
if (!mTouchMoved) {
mPrevTouchPosX = posX;
mPrevTouchPosY = posY;
mProcessTouch = false;
+ mZoomGesture = false;
}
private void constrainCamera(boolean b) {
private void vibrateLong() {
// mView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ final GridLayer layer = mLayer;
+ if (layer.getState() == GridLayer.STATE_FULL_SCREEN) {
+ float scale = detector.getScaleFactor();
+ float currentScale = layer.getZoomValue();
+ if (currentScale < 0.7f && scale < 1.0f) {
+ scale = 1.0f;
+ }
+ if (currentScale > 8.0f && scale > 1.0f) {
+ scale = 1.0f;
+ }
+ layer.setZoomValue(currentScale * scale);
+ }
+ return true;
+ }
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ mZoomGesture = true;
+ mLayer.getHud().hideZoomButtons(true);
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ final GridLayer layer = mLayer;
+ if (layer.getState() == GridLayer.STATE_FULL_SCREEN) {
+ float currentScale = layer.getZoomValue();
+ if (currentScale < 1.0f) {
+ currentScale = 1.0f;
+ } else if (currentScale > 6.0f) {
+ currentScale = 6.0f;
+ }
+ if (currentScale != layer.getZoomValue()) {
+ layer.setZoomValue(currentScale);
+ }
+ layer.constrainCameraForSlot(mCurrentSelectedSlot);
+ mLayer.getHud().hideZoomButtons(false);
+ }
+ }
}
public void renderBlended(RenderView view, GL11 gl) {
// We draw the placeholder for all visible slots.
if (mHud != null && mDrawManager != null) {
- mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, mHud.getMode(), mTimeElapsedSinceStackViewReady,
- mTimeElapsedSinceGridViewReady, sSelectedBucketList, sMarkedBucketList, mMediaFeed.getWaitingForMediaScanner()
- || mFeedAboutToChange || mMediaFeed.isLoading());
+ if (mMediaFeed != null) {
+ mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, mHud.getMode(),
+ mTimeElapsedSinceStackViewReady, mTimeElapsedSinceGridViewReady, sSelectedBucketList, sMarkedBucketList,
+ mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange || mMediaFeed.isLoading());
+ }
}
}
public Layer getMenuBar() {
return mFullscreenMenu;
}
+
+ public void hideZoomButtons(boolean hide) {
+ mZoomInButton.setHidden(hide);
+ mZoomOutButton.setHidden(hide);
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cooliris.media;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * Detects transformation gestures involving more than one pointer ("multitouch")
+ * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
+ * callback will notify users when a particular gesture event has occurred.
+ * This class should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * To use this class:
+ * <ul>
+ * <li>Create an instance of the {@code ScaleGestureDetector} for your
+ * {@link View}
+ * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ * callback will be executed when the events occur.
+ * </ul>
+ * @hide Pending API approval
+ */
+public class ScaleGestureDetector {
+ /**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnScaleGestureListener}.
+ *
+ * An application will receive events in the following order:
+ * <ul>
+ * <li>One {@link OnScaleGestureListener#onScaleBegin()}
+ * <li>Zero or more {@link OnScaleGestureListener#onScale()}
+ * <li>One {@link OnScaleGestureListener#onTransformEnd()}
+ * </ul>
+ */
+ public interface OnScaleGestureListener {
+ /**
+ * Responds to scaling events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should consider this event
+ * as handled. If an event was not handled, the detector
+ * will continue to accumulate movement until an event is
+ * handled. This can be useful if an application, for example,
+ * only wants to update scaling factors if the change is
+ * greater than 0.01.
+ */
+ public boolean onScale(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the beginning of a scaling gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should continue recognizing
+ * this gesture. For example, if a gesture is beginning
+ * with a focal point outside of a region where it makes
+ * sense, onScaleBegin() may return false to ignore the
+ * rest of the gesture.
+ */
+ public boolean onScaleBegin(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the end of a scale gesture. Reported by existing
+ * pointers going up. If the end of a gesture would result in a fling,
+ * {@link onTransformFling()} is called instead.
+ *
+ * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
+ * and {@link ScaleGestureDetector#getFocusY()} will return the location
+ * of the pointer remaining on the screen.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ */
+ public void onScaleEnd(ScaleGestureDetector detector);
+ }
+
+ /**
+ * A convenience class to extend when you only want to listen for a subset
+ * of scaling-related events. This implements all methods in
+ * {@link OnScaleGestureListener} but does nothing.
+ * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} and
+ * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} return
+ * {@code true}.
+ */
+ public class SimpleOnScaleGestureListener implements OnScaleGestureListener {
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ // Intentionally empty
+ }
+ }
+
+ private static final float PRESSURE_THRESHOLD = 0.67f;
+
+ private Context mContext;
+ private OnScaleGestureListener mListener;
+ private boolean mGestureInProgress;
+
+ private MotionEvent mPrevEvent;
+ private MotionEvent mCurrEvent;
+
+ private float mFocusX;
+ private float mFocusY;
+ private float mPrevFingerDiffX;
+ private float mPrevFingerDiffY;
+ private float mCurrFingerDiffX;
+ private float mCurrFingerDiffY;
+ private float mCurrLen;
+ private float mPrevLen;
+ private float mScaleFactor;
+ private float mCurrPressure;
+ private float mPrevPressure;
+ private long mTimeDelta;
+
+ public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+ mContext = context;
+ mListener = listener;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ boolean handled = true;
+
+ if (!mGestureInProgress) {
+ if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
+ action == MotionEvent.ACTION_POINTER_2_DOWN) &&
+ event.getPointerCount() >= 2) {
+ // We have a new multi-finger gesture
+
+ // Be paranoid in case we missed an event
+ reset();
+
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ setContext(event);
+ mGestureInProgress = mListener.onScaleBegin(this);
+ }
+ } else {
+ // Transform gesture in progress - attempt to handle it
+ switch (action) {
+ case MotionEvent.ACTION_POINTER_1_UP:
+ case MotionEvent.ACTION_POINTER_2_UP:
+ // Gesture ended
+ setContext(event);
+
+ // Set focus point to the remaining finger
+ int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+ >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+ mFocusX = event.getX(id);
+ mFocusY = event.getY(id);
+
+ mListener.onScaleEnd(this);
+ mGestureInProgress = false;
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mListener.onScaleEnd(this);
+ mGestureInProgress = false;
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ setContext(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit - this can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onScale(this);
+
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+ return handled;
+ }
+
+ private void setContext(MotionEvent curr) {
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ }
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ mCurrLen = -1;
+ mPrevLen = -1;
+ mScaleFactor = -1;
+
+ final MotionEvent prev = mPrevEvent;
+
+ final float px0 = prev.getX(0);
+ final float py0 = prev.getY(0);
+ final float px1 = prev.getX(1);
+ final float py1 = prev.getY(1);
+ final float cx0 = curr.getX(0);
+ final float cy0 = curr.getY(0);
+ final float cx1 = curr.getX(1);
+ final float cy1 = curr.getY(1);
+
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+
+ mFocusX = cx0 + cvx * 0.5f;
+ mFocusY = cy0 + cvy * 0.5f;
+ mTimeDelta = curr.getEventTime() - prev.getEventTime();
+ mCurrPressure = curr.getPressure(0) + curr.getPressure(1);
+ mPrevPressure = prev.getPressure(0) + prev.getPressure(1);
+ }
+
+ private void reset() {
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ }
+
+ /**
+ * Returns {@code true} if a two-finger scale gesture is in progress.
+ * @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
+ */
+ public boolean isInProgress() {
+ return mGestureInProgress;
+ }
+
+ /**
+ * Get the X coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is directly between
+ * the two pointers forming the gesture.
+ * If a gesture is ending, the focal point is the location of the
+ * remaining pointer on the screen.
+ * If {@link isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return X coordinate of the focal point in pixels.
+ */
+ public float getFocusX() {
+ return mFocusX;
+ }
+
+ /**
+ * Get the Y coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is directly between
+ * the two pointers forming the gesture.
+ * If a gesture is ending, the focal point is the location of the
+ * remaining pointer on the screen.
+ * If {@link isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return Y coordinate of the focal point in pixels.
+ */
+ public float getFocusY() {
+ return mFocusY;
+ }
+
+ /**
+ * Return the current distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
+ }
+ return mCurrLen;
+ }
+
+ /**
+ * Return the previous distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
+ }
+ return mPrevLen;
+ }
+
+ /**
+ * Return the scaling factor from the previous scale event to the current
+ * event. This value is defined as
+ * ({@link getCurrentSpan()} / {@link getPreviousSpan()}).
+ *
+ * @return The current scaling factor.
+ */
+ public float getScaleFactor() {
+ if (mScaleFactor == -1) {
+ mScaleFactor = getCurrentSpan() / getPreviousSpan();
+ }
+ return mScaleFactor;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous
+ * accepted scaling event and the current scaling event.
+ *
+ * @return Time difference since the last scaling event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mTimeDelta;
+ }
+
+ /**
+ * Return the event time of the current event being processed.
+ *
+ * @return Current event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrEvent.getEventTime();
+ }
+}
// Mark that photos changed.
// context.photosChanged = true;
- getContext().getContentResolver().notifyChange(ALBUMS_URI, null);
- getContext().getContentResolver().notifyChange(PHOTOS_URI, null);
+ getContext().getContentResolver().notifyChange(ALBUMS_URI, null, false);
+ getContext().getContentResolver().notifyChange(PHOTOS_URI, null, false);
}
private void deleteUser(SQLiteDatabase db, String account) {
// Send notifications if needed and reset state.
ContentResolver cr = getContext().getContentResolver();
if (albumsChanged) {
- cr.notifyChange(ALBUMS_URI, null);
+ cr.notifyChange(ALBUMS_URI, null, false);
}
if (photosChanged) {
- cr.notifyChange(PHOTOS_URI, null);
+ cr.notifyChange(PHOTOS_URI, null, false);
}
albumsChanged = false;
photosChanged = false;
package com.cooliris.picasa;
+import java.io.IOException;
+
import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
import android.content.AbstractThreadedSyncAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
-import android.util.Log;
public class PicasaSyncAdapter extends AbstractThreadedSyncAdapter {
private final Context mContext;
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient providerClient,
SyncResult syncResult) {
+ if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
+ try {
+ Account[] picasaAccounts = AccountManager.get(getContext())
+ .getAccountsByTypeAndFeatures(
+ PicasaService.ACCOUNT_TYPE,
+ new String[] { PicasaService.FEATURE_SERVICE_NAME },
+ null /* callback */, null /* handler */).getResult();
+ boolean isPicasaAccount = false;
+ for (Account picasaAccount : picasaAccounts) {
+ if (account.equals(picasaAccount)) {
+ isPicasaAccount = true;
+ break;
+ }
+ }
+ if (isPicasaAccount) {
+ ContentResolver.setIsSyncable(account, authority, 1);
+ ContentResolver.setSyncAutomatically(account, authority, true);
+ }
+ } catch (OperationCanceledException e) {
+ ;
+ } catch (IOException e) {
+ ;
+ } catch (AuthenticatorException e) {
+ ;
+ }
+ return;
+ }
PicasaService.performSync(mContext, account, extras, syncResult);
}