diff options
Diffstat (limited to 'src/com/android/photos')
39 files changed, 0 insertions, 9083 deletions
diff --git a/src/com/android/photos/AlbumActivity.java b/src/com/android/photos/AlbumActivity.java deleted file mode 100644 index c616b998b..000000000 --- a/src/com/android/photos/AlbumActivity.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.Activity; -import android.os.Bundle; - -public class AlbumActivity extends Activity implements MultiChoiceManager.Provider { - - public static final String KEY_ALBUM_URI = AlbumFragment.KEY_ALBUM_URI; - public static final String KEY_ALBUM_TITLE = AlbumFragment.KEY_ALBUM_TITLE; - - private MultiChoiceManager mMultiChoiceManager; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle intentExtras = getIntent().getExtras(); - mMultiChoiceManager = new MultiChoiceManager(this); - if (savedInstanceState == null) { - AlbumFragment albumFragment = new AlbumFragment(); - mMultiChoiceManager.setDelegate(albumFragment); - albumFragment.setArguments(intentExtras); - getFragmentManager().beginTransaction().add(android.R.id.content, - albumFragment).commit(); - } - getActionBar().setTitle(intentExtras.getString(KEY_ALBUM_TITLE)); - } - - @Override - public MultiChoiceManager getMultiChoiceManager() { - return mMultiChoiceManager; - } -} diff --git a/src/com/android/photos/AlbumFragment.java b/src/com/android/photos/AlbumFragment.java deleted file mode 100644 index 406fd2a29..000000000 --- a/src/com/android/photos/AlbumFragment.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.LoaderManager.LoaderCallbacks; -import android.content.Context; -import android.content.Intent; -import android.content.Loader; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.gallery3d.R; -import com.android.gallery3d.app.Gallery; -import com.android.photos.adapters.PhotoThumbnailAdapter; -import com.android.photos.data.PhotoSetLoader; -import com.android.photos.shims.LoaderCompatShim; -import com.android.photos.shims.MediaItemsLoader; -import com.android.photos.views.HeaderGridView; - -import java.util.ArrayList; - -public class AlbumFragment extends MultiSelectGridFragment implements LoaderCallbacks<Cursor> { - - protected static final String KEY_ALBUM_URI = "AlbumUri"; - protected static final String KEY_ALBUM_TITLE = "AlbumTitle"; - private static final int LOADER_ALBUM = 1; - - private LoaderCompatShim<Cursor> mLoaderCompatShim; - private PhotoThumbnailAdapter mAdapter; - private String mAlbumPath; - private String mAlbumTitle; - private View mHeaderView; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Context context = getActivity(); - mAdapter = new PhotoThumbnailAdapter(context); - Bundle args = getArguments(); - if (args != null) { - mAlbumPath = args.getString(KEY_ALBUM_URI, null); - mAlbumTitle = args.getString(KEY_ALBUM_TITLE, null); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - getLoaderManager().initLoader(LOADER_ALBUM, null, this); - return inflater.inflate(R.layout.album_content, container, false); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - // TODO: Remove once UI stabilizes - getGridView().setColumnWidth(MediaItemsLoader.getThumbnailSize()); - } - - private void updateHeaderView() { - if (mHeaderView == null) { - mHeaderView = LayoutInflater.from(getActivity()) - .inflate(R.layout.album_header, getGridView(), false); - ((HeaderGridView) getGridView()).addHeaderView(mHeaderView, null, false); - - // TODO remove this when the data model stabilizes - mHeaderView.setMinimumHeight(200); - } - ImageView iv = (ImageView) mHeaderView.findViewById(R.id.album_header_image); - TextView title = (TextView) mHeaderView.findViewById(R.id.album_header_title); - TextView subtitle = (TextView) mHeaderView.findViewById(R.id.album_header_subtitle); - title.setText(mAlbumTitle); - int count = mAdapter.getCount(); - subtitle.setText(getActivity().getResources().getQuantityString( - R.plurals.number_of_photos, count, count)); - if (count > 0) { - iv.setImageDrawable(mLoaderCompatShim.drawableForItem(mAdapter.getItem(0), null)); - } - } - - @Override - public void onGridItemClick(GridView g, View v, int position, long id) { - if (mLoaderCompatShim == null) { - // Not fully initialized yet, discard - return; - } - Cursor item = (Cursor) getItemAtPosition(position); - Uri uri = mLoaderCompatShim.uriForItem(item); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.setClass(getActivity(), Gallery.class); - startActivity(intent); - } - - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - // TODO: Switch to PhotoSetLoader - MediaItemsLoader loader = new MediaItemsLoader(getActivity(), mAlbumPath); - mLoaderCompatShim = loader; - mAdapter.setDrawableFactory(mLoaderCompatShim); - return loader; - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, - Cursor data) { - mAdapter.swapCursor(data); - updateHeaderView(); - setAdapter(mAdapter); - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - } - - @Override - public int getItemMediaType(Object item) { - return ((Cursor) item).getInt(PhotoSetLoader.INDEX_MEDIA_TYPE); - } - - @Override - public int getItemSupportedOperations(Object item) { - return ((Cursor) item).getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS); - } - - private ArrayList<Uri> mSubItemUriTemp = new ArrayList<Uri>(1); - @Override - public ArrayList<Uri> getSubItemUrisForItem(Object item) { - mSubItemUriTemp.clear(); - mSubItemUriTemp.add(mLoaderCompatShim.uriForItem((Cursor) item)); - return mSubItemUriTemp; - } - - @Override - public void deleteItemWithPath(Object itemPath) { - mLoaderCompatShim.deleteItemWithPath(itemPath); - } - - @Override - public Uri getItemUri(Object item) { - return mLoaderCompatShim.uriForItem((Cursor) item); - } - - @Override - public Object getPathForItem(Object item) { - return mLoaderCompatShim.getPathForItem((Cursor) item); - } -} diff --git a/src/com/android/photos/AlbumSetFragment.java b/src/com/android/photos/AlbumSetFragment.java deleted file mode 100644 index bc5289ee1..000000000 --- a/src/com/android/photos/AlbumSetFragment.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.LoaderManager.LoaderCallbacks; -import android.content.Context; -import android.content.Intent; -import android.content.Loader; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.MediaStore.Files.FileColumns; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.GridView; - -import com.android.gallery3d.R; -import com.android.photos.adapters.AlbumSetCursorAdapter; -import com.android.photos.data.AlbumSetLoader; -import com.android.photos.shims.LoaderCompatShim; -import com.android.photos.shims.MediaSetLoader; - -import java.util.ArrayList; - - -public class AlbumSetFragment extends MultiSelectGridFragment implements LoaderCallbacks<Cursor> { - - private AlbumSetCursorAdapter mAdapter; - private LoaderCompatShim<Cursor> mLoaderCompatShim; - - private static final int LOADER_ALBUMSET = 1; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Context context = getActivity(); - mAdapter = new AlbumSetCursorAdapter(context); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View root = super.onCreateView(inflater, container, savedInstanceState); - getLoaderManager().initLoader(LOADER_ALBUMSET, null, this); - return root; - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - getGridView().setColumnWidth(getActivity().getResources() - .getDimensionPixelSize(R.dimen.album_set_item_width)); - } - - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - // TODO: Switch to AlbumSetLoader - MediaSetLoader loader = new MediaSetLoader(getActivity()); - mAdapter.setDrawableFactory(loader); - mLoaderCompatShim = loader; - return loader; - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, - Cursor data) { - mAdapter.swapCursor(data); - setAdapter(mAdapter); - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - } - - @Override - public void onGridItemClick(GridView g, View v, int position, long id) { - if (mLoaderCompatShim == null) { - // Not fully initialized yet, discard - return; - } - Cursor item = (Cursor) getItemAtPosition(position); - Context context = getActivity(); - Intent intent = new Intent(context, AlbumActivity.class); - intent.putExtra(AlbumActivity.KEY_ALBUM_URI, - mLoaderCompatShim.getPathForItem(item).toString()); - intent.putExtra(AlbumActivity.KEY_ALBUM_TITLE, - item.getString(AlbumSetLoader.INDEX_TITLE)); - context.startActivity(intent); - } - - @Override - public int getItemMediaType(Object item) { - return FileColumns.MEDIA_TYPE_NONE; - } - - @Override - public int getItemSupportedOperations(Object item) { - return ((Cursor) item).getInt(AlbumSetLoader.INDEX_SUPPORTED_OPERATIONS); - } - - @Override - public ArrayList<Uri> getSubItemUrisForItem(Object item) { - return mLoaderCompatShim.urisForSubItems((Cursor) item); - } - - @Override - public void deleteItemWithPath(Object itemPath) { - mLoaderCompatShim.deleteItemWithPath(itemPath); - } - - @Override - public Uri getItemUri(Object item) { - return mLoaderCompatShim.uriForItem((Cursor) item); - } - - @Override - public Object getPathForItem(Object item) { - return mLoaderCompatShim.getPathForItem((Cursor) item); - } -} diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java deleted file mode 100644 index d7d52f67a..000000000 --- a/src/com/android/photos/BitmapRegionTileSource.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.os.Build; -import android.os.Build.VERSION_CODES; -import android.util.Log; - -import com.android.gallery3d.glrenderer.BasicTexture; -import com.android.gallery3d.glrenderer.BitmapTexture; -import com.android.photos.views.TiledImageRenderer; - -import java.io.IOException; - -/** - * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using - * {@link BitmapRegionDecoder} to wrap a local file - */ -public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { - - private static final String TAG = "BitmapRegionTileSource"; - - private static final boolean REUSE_BITMAP = - Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; - private static final int MAX_PREVIEW_SIZE = 1024; - - BitmapRegionDecoder mDecoder; - int mWidth; - int mHeight; - int mTileSize; - private BasicTexture mPreview; - private final int mRotation; - - // For use only by getTile - private Rect mWantRegion = new Rect(); - private Rect mOverlapRegion = new Rect(); - private BitmapFactory.Options mOptions; - private Canvas mCanvas; - - public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) { - mTileSize = TiledImageRenderer.suggestedTileSize(context); - mRotation = rotation; - try { - mDecoder = BitmapRegionDecoder.newInstance(path, true); - mWidth = mDecoder.getWidth(); - mHeight = mDecoder.getHeight(); - } catch (IOException e) { - Log.w("BitmapRegionTileSource", "ctor failed", e); - } - mOptions = new BitmapFactory.Options(); - mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; - mOptions.inPreferQualityOverSpeed = true; - mOptions.inTempStorage = new byte[16 * 1024]; - if (previewSize != 0) { - previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE); - // Although this is the same size as the Bitmap that is likely already - // loaded, the lifecycle is different and interactions are on a different - // thread. Thus to simplify, this source will decode its own bitmap. - int sampleSize = (int) Math.ceil(Math.max( - mWidth / (float) previewSize, mHeight / (float) previewSize)); - mOptions.inSampleSize = Math.max(sampleSize, 1); - Bitmap preview = mDecoder.decodeRegion( - new Rect(0, 0, mWidth, mHeight), mOptions); - if (preview.getWidth() <= MAX_PREVIEW_SIZE && preview.getHeight() <= MAX_PREVIEW_SIZE) { - mPreview = new BitmapTexture(preview); - } else { - Log.w(TAG, String.format( - "Failed to create preview of apropriate size! " - + " in: %dx%d, sample: %d, out: %dx%d", - mWidth, mHeight, sampleSize, - preview.getWidth(), preview.getHeight())); - } - } - } - - @Override - public int getTileSize() { - return mTileSize; - } - - @Override - public int getImageWidth() { - return mWidth; - } - - @Override - public int getImageHeight() { - return mHeight; - } - - @Override - public BasicTexture getPreview() { - return mPreview; - } - - @Override - public int getRotation() { - return mRotation; - } - - @Override - public Bitmap getTile(int level, int x, int y, Bitmap bitmap) { - int tileSize = getTileSize(); - if (!REUSE_BITMAP) { - return getTileWithoutReusingBitmap(level, x, y, tileSize); - } - - int t = tileSize << level; - mWantRegion.set(x, y, x + t, y + t); - - if (bitmap == null) { - bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888); - } - - mOptions.inSampleSize = (1 << level); - mOptions.inBitmap = bitmap; - - try { - bitmap = mDecoder.decodeRegion(mWantRegion, mOptions); - } finally { - if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) { - mOptions.inBitmap = null; - } - } - - if (bitmap == null) { - Log.w("BitmapRegionTileSource", "fail in decoding region"); - } - return bitmap; - } - - private Bitmap getTileWithoutReusingBitmap( - int level, int x, int y, int tileSize) { - - int t = tileSize << level; - mWantRegion.set(x, y, x + t, y + t); - - mOverlapRegion.set(0, 0, mWidth, mHeight); - - mOptions.inSampleSize = (1 << level); - Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions); - - if (bitmap == null) { - Log.w(TAG, "fail in decoding region"); - } - - if (mWantRegion.equals(mOverlapRegion)) { - return bitmap; - } - - Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888); - if (mCanvas == null) { - mCanvas = new Canvas(); - } - mCanvas.setBitmap(result); - mCanvas.drawBitmap(bitmap, - (mOverlapRegion.left - mWantRegion.left) >> level, - (mOverlapRegion.top - mWantRegion.top) >> level, null); - mCanvas.setBitmap(null); - return result; - } -} diff --git a/src/com/android/photos/FullscreenViewer.java b/src/com/android/photos/FullscreenViewer.java deleted file mode 100644 index a3761395e..000000000 --- a/src/com/android/photos/FullscreenViewer.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.Activity; -import android.os.Bundle; -import com.android.photos.views.TiledImageView; - - -public class FullscreenViewer extends Activity { - - private TiledImageView mTextureView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - String path = getIntent().getData().toString(); - mTextureView = new TiledImageView(this); - mTextureView.setTileSource(new BitmapRegionTileSource(this, path, 0, 0), null); - setContentView(mTextureView); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - mTextureView.destroy(); - } - -} diff --git a/src/com/android/photos/GalleryActivity.java b/src/com/android/photos/GalleryActivity.java deleted file mode 100644 index 710767d77..000000000 --- a/src/com/android/photos/GalleryActivity.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.ActionBar; -import android.app.ActionBar.Tab; -import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.os.Bundle; -import android.support.v13.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; -import android.view.Menu; -import android.view.MenuItem; -import android.view.ViewGroup; - -import com.android.camera.CameraActivity; -import com.android.gallery3d.R; - -import java.util.ArrayList; - -public class GalleryActivity extends Activity implements MultiChoiceManager.Provider { - - private MultiChoiceManager mMultiChoiceManager; - private ViewPager mViewPager; - private TabsAdapter mTabsAdapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mMultiChoiceManager = new MultiChoiceManager(this); - mViewPager = new ViewPager(this); - mViewPager.setId(R.id.viewpager); - setContentView(mViewPager); - - ActionBar ab = getActionBar(); - ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - ab.setDisplayShowHomeEnabled(false); - ab.setDisplayShowTitleEnabled(false); - - mTabsAdapter = new TabsAdapter(this, mViewPager); - mTabsAdapter.addTab(ab.newTab().setText(R.string.tab_photos), - PhotoSetFragment.class, null); - mTabsAdapter.addTab(ab.newTab().setText(R.string.tab_albums), - AlbumSetFragment.class, null); - - if (savedInstanceState != null) { - ab.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.gallery, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_camera: - Intent intent = new Intent(this, CameraActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - public static class TabsAdapter extends FragmentPagerAdapter implements - ActionBar.TabListener, ViewPager.OnPageChangeListener { - - private final GalleryActivity mActivity; - private final ActionBar mActionBar; - private final ViewPager mViewPager; - private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); - - static final class TabInfo { - - private final Class<?> clss; - private final Bundle args; - - TabInfo(Class<?> _class, Bundle _args) { - clss = _class; - args = _args; - } - } - - public TabsAdapter(GalleryActivity activity, ViewPager pager) { - super(activity.getFragmentManager()); - mActivity = activity; - mActionBar = activity.getActionBar(); - mViewPager = pager; - mViewPager.setAdapter(this); - mViewPager.setOnPageChangeListener(this); - } - - public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) { - TabInfo info = new TabInfo(clss, args); - tab.setTag(info); - tab.setTabListener(this); - mTabs.add(info); - mActionBar.addTab(tab); - notifyDataSetChanged(); - } - - @Override - public int getCount() { - return mTabs.size(); - } - - @Override - public Fragment getItem(int position) { - TabInfo info = mTabs.get(position); - return Fragment.instantiate(mActivity, info.clss.getName(), - info.args); - } - - @Override - public void onPageScrolled(int position, float positionOffset, - int positionOffsetPixels) { - } - - @Override - public void onPageSelected(int position) { - mActionBar.setSelectedNavigationItem(position); - } - - @Override - public void setPrimaryItem(ViewGroup container, int position, Object object) { - super.setPrimaryItem(container, position, object); - mActivity.mMultiChoiceManager.setDelegate((MultiChoiceManager.Delegate) object); - } - - @Override - public void onPageScrollStateChanged(int state) { - } - - @Override - public void onTabSelected(Tab tab, FragmentTransaction ft) { - Object tag = tab.getTag(); - for (int i = 0; i < mTabs.size(); i++) { - if (mTabs.get(i) == tag) { - mViewPager.setCurrentItem(i); - } - } - } - - @Override - public void onTabUnselected(Tab tab, FragmentTransaction ft) { - } - - @Override - public void onTabReselected(Tab tab, FragmentTransaction ft) { - } - } - - @Override - public MultiChoiceManager getMultiChoiceManager() { - return mMultiChoiceManager; - } -} diff --git a/src/com/android/photos/MultiChoiceManager.java b/src/com/android/photos/MultiChoiceManager.java deleted file mode 100644 index 49519ca63..000000000 --- a/src/com/android/photos/MultiChoiceManager.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.AsyncTask; -import android.provider.MediaStore.Files.FileColumns; -import android.util.SparseBooleanArray; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.widget.AbsListView.MultiChoiceModeListener; -import android.widget.ShareActionProvider; -import android.widget.ShareActionProvider.OnShareTargetSelectedListener; - -import com.android.gallery3d.R; -import com.android.gallery3d.app.TrimVideo; -import com.android.gallery3d.data.MediaObject; -import com.android.gallery3d.filtershow.FilterShowActivity; -import com.android.gallery3d.filtershow.crop.CropActivity; -import com.android.gallery3d.util.GalleryUtils; - -import java.util.ArrayList; -import java.util.List; - -public class MultiChoiceManager implements MultiChoiceModeListener, - OnShareTargetSelectedListener, SelectionManager.SelectedUriSource { - - public interface Provider { - public MultiChoiceManager getMultiChoiceManager(); - } - - public interface Delegate { - public SparseBooleanArray getSelectedItemPositions(); - public int getSelectedItemCount(); - public int getItemMediaType(Object item); - public int getItemSupportedOperations(Object item); - public ArrayList<Uri> getSubItemUrisForItem(Object item); - public Uri getItemUri(Object item); - public Object getItemAtPosition(int position); - public Object getPathForItemAtPosition(int position); - public void deleteItemWithPath(Object itemPath); - } - - private SelectionManager mSelectionManager; - private ShareActionProvider mShareActionProvider; - private ActionMode mActionMode; - private Context mContext; - private Delegate mDelegate; - - private ArrayList<Uri> mSelectedShareableUrisArray = new ArrayList<Uri>(); - - public MultiChoiceManager(Activity activity) { - mContext = activity; - mSelectionManager = new SelectionManager(activity); - } - - public void setDelegate(Delegate delegate) { - if (mDelegate == delegate) { - return; - } - if (mActionMode != null) { - mActionMode.finish(); - } - mDelegate = delegate; - } - - @Override - public ArrayList<Uri> getSelectedShareableUris() { - return mSelectedShareableUrisArray; - } - - private void updateSelectedTitle(ActionMode mode) { - int count = mDelegate.getSelectedItemCount(); - mode.setTitle(mContext.getResources().getQuantityString( - R.plurals.number_of_items_selected, count, count)); - } - - private String getItemMimetype(Object item) { - int type = mDelegate.getItemMediaType(item); - if (type == FileColumns.MEDIA_TYPE_IMAGE) { - return GalleryUtils.MIME_TYPE_IMAGE; - } else if (type == FileColumns.MEDIA_TYPE_VIDEO) { - return GalleryUtils.MIME_TYPE_VIDEO; - } else { - return GalleryUtils.MIME_TYPE_ALL; - } - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - updateSelectedTitle(mode); - Object item = mDelegate.getItemAtPosition(position); - - int supported = mDelegate.getItemSupportedOperations(item); - - if ((supported & MediaObject.SUPPORT_SHARE) > 0) { - ArrayList<Uri> subItems = mDelegate.getSubItemUrisForItem(item); - if (checked) { - mSelectedShareableUrisArray.addAll(subItems); - } else { - mSelectedShareableUrisArray.removeAll(subItems); - } - } - - mSelectionManager.onItemSelectedStateChanged(mShareActionProvider, - mDelegate.getItemMediaType(item), - supported, - checked); - updateActionItemVisibilities(mode.getMenu(), - mSelectionManager.getSupportedOperations()); - } - - private void updateActionItemVisibilities(Menu menu, int supportedOperations) { - MenuItem editItem = menu.findItem(R.id.menu_edit); - MenuItem deleteItem = menu.findItem(R.id.menu_delete); - MenuItem shareItem = menu.findItem(R.id.menu_share); - MenuItem cropItem = menu.findItem(R.id.menu_crop); - MenuItem trimItem = menu.findItem(R.id.menu_trim); - MenuItem muteItem = menu.findItem(R.id.menu_mute); - MenuItem setAsItem = menu.findItem(R.id.menu_set_as); - - editItem.setVisible((supportedOperations & MediaObject.SUPPORT_EDIT) > 0); - deleteItem.setVisible((supportedOperations & MediaObject.SUPPORT_DELETE) > 0); - shareItem.setVisible((supportedOperations & MediaObject.SUPPORT_SHARE) > 0); - cropItem.setVisible((supportedOperations & MediaObject.SUPPORT_CROP) > 0); - trimItem.setVisible((supportedOperations & MediaObject.SUPPORT_TRIM) > 0); - muteItem.setVisible((supportedOperations & MediaObject.SUPPORT_MUTE) > 0); - setAsItem.setVisible((supportedOperations & MediaObject.SUPPORT_SETAS) > 0); - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mSelectionManager.setSelectedUriSource(this); - mActionMode = mode; - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.gallery_multiselect, menu); - MenuItem menuItem = menu.findItem(R.id.menu_share); - mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider(); - mShareActionProvider.setOnShareTargetSelectedListener(this); - updateSelectedTitle(mode); - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - // onDestroyActionMode gets called when the share target was selected, - // but apparently before the ArrayList is serialized in the intent - // so we can't clear the old one here. - mSelectedShareableUrisArray = new ArrayList<Uri>(); - mSelectionManager.onClearSelection(); - mSelectionManager.setSelectedUriSource(null); - mShareActionProvider = null; - mActionMode = null; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - updateSelectedTitle(mode); - return false; - } - - @Override - public boolean onShareTargetSelected(ShareActionProvider provider, Intent intent) { - mActionMode.finish(); - return false; - } - - private static class BulkDeleteTask extends AsyncTask<Void, Void, Void> { - private Delegate mDelegate; - private List<Object> mPaths; - - public BulkDeleteTask(Delegate delegate, List<Object> paths) { - mDelegate = delegate; - mPaths = paths; - } - - @Override - protected Void doInBackground(Void... ignored) { - for (Object path : mPaths) { - mDelegate.deleteItemWithPath(path); - } - return null; - } - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - int actionItemId = item.getItemId(); - switch (actionItemId) { - case R.id.menu_delete: - BulkDeleteTask deleteTask = new BulkDeleteTask(mDelegate, - getPathsForSelectedItems()); - deleteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - mode.finish(); - return true; - case R.id.menu_edit: - case R.id.menu_crop: - case R.id.menu_trim: - case R.id.menu_mute: - case R.id.menu_set_as: - singleItemAction(getSelectedItem(), actionItemId); - mode.finish(); - return true; - default: - return false; - } - } - - private void singleItemAction(Object item, int actionItemId) { - Intent intent = new Intent(); - String mime = getItemMimetype(item); - Uri uri = mDelegate.getItemUri(item); - switch (actionItemId) { - case R.id.menu_edit: - intent.setDataAndType(uri, mime) - .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .setAction(Intent.ACTION_EDIT); - mContext.startActivity(Intent.createChooser(intent, null)); - return; - case R.id.menu_crop: - intent.setDataAndType(uri, mime) - .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .setAction(CropActivity.CROP_ACTION) - .setClass(mContext, FilterShowActivity.class); - mContext.startActivity(intent); - return; - case R.id.menu_trim: - intent.setData(uri) - .setClass(mContext, TrimVideo.class); - mContext.startActivity(intent); - return; - case R.id.menu_mute: - /* TODO need a way to get the file path of an item - MuteVideo muteVideo = new MuteVideo(filePath, - uri, (Activity) mContext); - muteVideo.muteInBackground(); - */ - return; - case R.id.menu_set_as: - intent.setDataAndType(uri, mime) - .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .setAction(Intent.ACTION_ATTACH_DATA) - .putExtra("mimeType", mime); - mContext.startActivity(Intent.createChooser( - intent, mContext.getString(R.string.set_as))); - return; - default: - return; - } - } - - private List<Object> getPathsForSelectedItems() { - List<Object> paths = new ArrayList<Object>(); - SparseBooleanArray selected = mDelegate.getSelectedItemPositions(); - for (int i = 0; i < selected.size(); i++) { - if (selected.valueAt(i)) { - paths.add(mDelegate.getPathForItemAtPosition(i)); - } - } - return paths; - } - - public Object getSelectedItem() { - if (mDelegate.getSelectedItemCount() != 1) { - return null; - } - SparseBooleanArray selected = mDelegate.getSelectedItemPositions(); - for (int i = 0; i < selected.size(); i++) { - if (selected.valueAt(i)) { - return mDelegate.getItemAtPosition(selected.keyAt(i)); - } - } - return null; - } -} diff --git a/src/com/android/photos/MultiSelectGridFragment.java b/src/com/android/photos/MultiSelectGridFragment.java deleted file mode 100644 index dda9fe443..000000000 --- a/src/com/android/photos/MultiSelectGridFragment.java +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.Activity; -import android.app.Fragment; -import android.os.Bundle; -import android.os.Handler; -import android.util.SparseBooleanArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.AdapterView; -import android.widget.GridView; -import android.widget.ListAdapter; -import android.widget.TextView; - -import com.android.gallery3d.R; - -public abstract class MultiSelectGridFragment extends Fragment - implements MultiChoiceManager.Delegate, AdapterView.OnItemClickListener { - - final private Handler mHandler = new Handler(); - - final private Runnable mRequestFocus = new Runnable() { - @Override - public void run() { - mGrid.focusableViewAvailable(mGrid); - } - }; - - ListAdapter mAdapter; - GridView mGrid; - TextView mEmptyView; - View mProgressContainer; - View mGridContainer; - CharSequence mEmptyText; - boolean mGridShown; - MultiChoiceManager.Provider mHost; - - public MultiSelectGridFragment() { - } - - /** - * Provide default implementation to return a simple grid view. Subclasses - * can override to replace with their own layout. If doing so, the returned - * view hierarchy <em>must</em> have a GridView whose id is - * {@link android.R.id#grid android.R.id.list} and can optionally have a - * sibling text view id {@link android.R.id#empty android.R.id.empty} that - * is to be shown when the grid is empty. - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.multigrid_content, container, false); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mHost = (MultiChoiceManager.Provider) activity; - if (mGrid != null) { - mGrid.setMultiChoiceModeListener(mHost.getMultiChoiceManager()); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mHost = null; - } - - /** - * Attach to grid view once the view hierarchy has been created. - */ - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - ensureGrid(); - } - - /** - * Detach from grid view. - */ - @Override - public void onDestroyView() { - mHandler.removeCallbacks(mRequestFocus); - mGrid = null; - mGridShown = false; - mEmptyView = null; - mProgressContainer = mGridContainer = null; - super.onDestroyView(); - } - - /** - * This method will be called when an item in the grid is selected. - * Subclasses should override. Subclasses can call - * getGridView().getItemAtPosition(position) if they need to access the data - * associated with the selected item. - * - * @param g The GridView where the click happened - * @param v The view that was clicked within the GridView - * @param position The position of the view in the grid - * @param id The id of the item that was clicked - */ - public void onGridItemClick(GridView g, View v, int position, long id) { - } - - /** - * Provide the cursor for the grid view. - */ - public void setAdapter(ListAdapter adapter) { - boolean hadAdapter = mAdapter != null; - mAdapter = adapter; - if (mGrid != null) { - mGrid.setAdapter(adapter); - if (!mGridShown && !hadAdapter) { - // The grid was hidden, and previously didn't have an - // adapter. It is now time to show it. - setGridShown(true, getView().getWindowToken() != null); - } - } - } - - /** - * Set the currently selected grid item to the specified position with the - * adapter's data - * - * @param position - */ - public void setSelection(int position) { - ensureGrid(); - mGrid.setSelection(position); - } - - /** - * Get the position of the currently selected grid item. - */ - public int getSelectedItemPosition() { - ensureGrid(); - return mGrid.getSelectedItemPosition(); - } - - /** - * Get the cursor row ID of the currently selected grid item. - */ - public long getSelectedItemId() { - ensureGrid(); - return mGrid.getSelectedItemId(); - } - - /** - * Get the activity's grid view widget. - */ - public GridView getGridView() { - ensureGrid(); - return mGrid; - } - - /** - * The default content for a MultiSelectGridFragment has a TextView that can - * be shown when the grid is empty. If you would like to have it shown, call - * this method to supply the text it should use. - */ - public void setEmptyText(CharSequence text) { - ensureGrid(); - if (mEmptyView == null) { - return; - } - mEmptyView.setText(text); - if (mEmptyText == null) { - mGrid.setEmptyView(mEmptyView); - } - mEmptyText = text; - } - - /** - * Control whether the grid is being displayed. You can make it not - * displayed if you are waiting for the initial data to show in it. During - * this time an indeterminate progress indicator will be shown instead. - * <p> - * Applications do not normally need to use this themselves. The default - * behavior of MultiSelectGridFragment is to start with the grid not being - * shown, only showing it once an adapter is given with - * {@link #setAdapter(ListAdapter)}. If the grid at that point had not been - * shown, when it does get shown it will be do without the user ever seeing - * the hidden state. - * - * @param shown If true, the grid view is shown; if false, the progress - * indicator. The initial value is true. - */ - public void setGridShown(boolean shown) { - setGridShown(shown, true); - } - - /** - * Like {@link #setGridShown(boolean)}, but no animation is used when - * transitioning from the previous state. - */ - public void setGridShownNoAnimation(boolean shown) { - setGridShown(shown, false); - } - - /** - * Control whether the grid is being displayed. You can make it not - * displayed if you are waiting for the initial data to show in it. During - * this time an indeterminate progress indicator will be shown instead. - * - * @param shown If true, the grid view is shown; if false, the progress - * indicator. The initial value is true. - * @param animate If true, an animation will be used to transition to the - * new state. - */ - private void setGridShown(boolean shown, boolean animate) { - ensureGrid(); - if (mProgressContainer == null) { - throw new IllegalStateException("Can't be used with a custom content view"); - } - if (mGridShown == shown) { - return; - } - mGridShown = shown; - if (shown) { - if (animate) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_out)); - mGridContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_in)); - } else { - mProgressContainer.clearAnimation(); - mGridContainer.clearAnimation(); - } - mProgressContainer.setVisibility(View.GONE); - mGridContainer.setVisibility(View.VISIBLE); - } else { - if (animate) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_in)); - mGridContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_out)); - } else { - mProgressContainer.clearAnimation(); - mGridContainer.clearAnimation(); - } - mProgressContainer.setVisibility(View.VISIBLE); - mGridContainer.setVisibility(View.GONE); - } - } - - /** - * Get the ListAdapter associated with this activity's GridView. - */ - public ListAdapter getAdapter() { - return mGrid.getAdapter(); - } - - private void ensureGrid() { - if (mGrid != null) { - return; - } - View root = getView(); - if (root == null) { - throw new IllegalStateException("Content view not yet created"); - } - if (root instanceof GridView) { - mGrid = (GridView) root; - } else { - View empty = root.findViewById(android.R.id.empty); - if (empty != null && empty instanceof TextView) { - mEmptyView = (TextView) empty; - } - mProgressContainer = root.findViewById(R.id.progressContainer); - mGridContainer = root.findViewById(R.id.gridContainer); - View rawGridView = root.findViewById(android.R.id.list); - if (!(rawGridView instanceof GridView)) { - throw new RuntimeException( - "Content has view with id attribute 'android.R.id.list' " - + "that is not a GridView class"); - } - mGrid = (GridView) rawGridView; - if (mGrid == null) { - throw new RuntimeException( - "Your content must have a GridView whose id attribute is " + - "'android.R.id.list'"); - } - if (mEmptyView != null) { - mGrid.setEmptyView(mEmptyView); - } - } - mGridShown = true; - mGrid.setOnItemClickListener(this); - mGrid.setMultiChoiceModeListener(mHost.getMultiChoiceManager()); - if (mAdapter != null) { - ListAdapter adapter = mAdapter; - mAdapter = null; - setAdapter(adapter); - } else { - // We are starting without an adapter, so assume we won't - // have our data right away and start with the progress indicator. - if (mProgressContainer != null) { - setGridShown(false, false); - } - } - mHandler.post(mRequestFocus); - } - - @Override - public Object getItemAtPosition(int position) { - return getAdapter().getItem(position); - } - - @Override - public Object getPathForItemAtPosition(int position) { - return getPathForItem(getItemAtPosition(position)); - } - - @Override - public SparseBooleanArray getSelectedItemPositions() { - return mGrid.getCheckedItemPositions(); - } - - @Override - public int getSelectedItemCount() { - return mGrid.getCheckedItemCount(); - } - - public abstract Object getPathForItem(Object item); - - @Override - public void onItemClick(AdapterView<?> parent, View v, int position, long id) { - onGridItemClick((GridView) parent, v, position, id); - } -} diff --git a/src/com/android/photos/PhotoFragment.java b/src/com/android/photos/PhotoFragment.java deleted file mode 100644 index 3be6313f2..000000000 --- a/src/com/android/photos/PhotoFragment.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.Fragment; - - -public class PhotoFragment extends Fragment { - -} diff --git a/src/com/android/photos/PhotoSetFragment.java b/src/com/android/photos/PhotoSetFragment.java deleted file mode 100644 index 961fd0bf2..000000000 --- a/src/com/android/photos/PhotoSetFragment.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.LoaderManager.LoaderCallbacks; -import android.content.Context; -import android.content.Intent; -import android.content.Loader; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.GridView; - -import com.android.gallery3d.app.Gallery; -import com.android.photos.adapters.PhotoThumbnailAdapter; -import com.android.photos.data.PhotoSetLoader; -import com.android.photos.shims.LoaderCompatShim; -import com.android.photos.shims.MediaItemsLoader; - -import java.util.ArrayList; - -public class PhotoSetFragment extends MultiSelectGridFragment implements LoaderCallbacks<Cursor> { - - private static final int LOADER_PHOTOSET = 1; - - private LoaderCompatShim<Cursor> mLoaderCompatShim; - private PhotoThumbnailAdapter mAdapter; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Context context = getActivity(); - mAdapter = new PhotoThumbnailAdapter(context); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View root = super.onCreateView(inflater, container, savedInstanceState); - getLoaderManager().initLoader(LOADER_PHOTOSET, null, this); - return root; - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - // TODO: Remove once UI stabilizes - getGridView().setColumnWidth(MediaItemsLoader.getThumbnailSize()); - } - - @Override - public void onGridItemClick(GridView g, View v, int position, long id) { - if (mLoaderCompatShim == null) { - // Not fully initialized yet, discard - return; - } - Cursor item = (Cursor) getItemAtPosition(position); - Uri uri = mLoaderCompatShim.uriForItem(item); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.setClass(getActivity(), Gallery.class); - startActivity(intent); - } - - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - // TODO: Switch to PhotoSetLoader - MediaItemsLoader loader = new MediaItemsLoader(getActivity()); - mLoaderCompatShim = loader; - mAdapter.setDrawableFactory(mLoaderCompatShim); - return loader; - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, - Cursor data) { - mAdapter.swapCursor(data); - setAdapter(mAdapter); - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - } - - @Override - public int getItemMediaType(Object item) { - return ((Cursor) item).getInt(PhotoSetLoader.INDEX_MEDIA_TYPE); - } - - @Override - public int getItemSupportedOperations(Object item) { - return ((Cursor) item).getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS); - } - - private ArrayList<Uri> mSubItemUriTemp = new ArrayList<Uri>(1); - @Override - public ArrayList<Uri> getSubItemUrisForItem(Object item) { - mSubItemUriTemp.clear(); - mSubItemUriTemp.add(mLoaderCompatShim.uriForItem((Cursor) item)); - return mSubItemUriTemp; - } - - @Override - public void deleteItemWithPath(Object itemPath) { - mLoaderCompatShim.deleteItemWithPath(itemPath); - } - - @Override - public Uri getItemUri(Object item) { - return mLoaderCompatShim.uriForItem((Cursor) item); - } - - @Override - public Object getPathForItem(Object item) { - return mLoaderCompatShim.getPathForItem((Cursor) item); - } -} diff --git a/src/com/android/photos/SelectionManager.java b/src/com/android/photos/SelectionManager.java deleted file mode 100644 index 9bfb9be75..000000000 --- a/src/com/android/photos/SelectionManager.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.nfc.NfcAdapter; -import android.nfc.NfcAdapter.CreateBeamUrisCallback; -import android.nfc.NfcEvent; -import android.provider.MediaStore.Files.FileColumns; -import android.widget.ShareActionProvider; - -import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.data.MediaObject; -import com.android.gallery3d.util.GalleryUtils; - -import java.util.ArrayList; - -public class SelectionManager { - private Activity mActivity; - private NfcAdapter mNfcAdapter; - private SelectedUriSource mUriSource; - private Intent mShareIntent = new Intent(); - - public interface SelectedUriSource { - public ArrayList<Uri> getSelectedShareableUris(); - } - - public SelectionManager(Activity activity) { - mActivity = activity; - if (ApiHelper.AT_LEAST_16) { - mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity); - mNfcAdapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() { - @Override - public Uri[] createBeamUris(NfcEvent arg0) { - // This will have been preceded by a call to onItemSelectedStateChange - if (mCachedShareableUris == null) return null; - return mCachedShareableUris.toArray( - new Uri[mCachedShareableUris.size()]); - } - }, mActivity); - } - } - - public void setSelectedUriSource(SelectedUriSource source) { - mUriSource = source; - } - - private int mSelectedTotalCount = 0; - private int mSelectedShareableCount = 0; - private int mSelectedShareableImageCount = 0; - private int mSelectedShareableVideoCount = 0; - private int mSelectedDeletableCount = 0; - private int mSelectedEditableCount = 0; - private int mSelectedCroppableCount = 0; - private int mSelectedSetableCount = 0; - private int mSelectedTrimmableCount = 0; - private int mSelectedMuteableCount = 0; - - private ArrayList<Uri> mCachedShareableUris = null; - - public void onItemSelectedStateChanged(ShareActionProvider share, - int itemType, int itemSupportedOperations, boolean selected) { - int increment = selected ? 1 : -1; - - mSelectedTotalCount += increment; - mCachedShareableUris = null; - - if ((itemSupportedOperations & MediaObject.SUPPORT_DELETE) > 0) { - mSelectedDeletableCount += increment; - } - if ((itemSupportedOperations & MediaObject.SUPPORT_EDIT) > 0) { - mSelectedEditableCount += increment; - } - if ((itemSupportedOperations & MediaObject.SUPPORT_CROP) > 0) { - mSelectedCroppableCount += increment; - } - if ((itemSupportedOperations & MediaObject.SUPPORT_SETAS) > 0) { - mSelectedSetableCount += increment; - } - if ((itemSupportedOperations & MediaObject.SUPPORT_TRIM) > 0) { - mSelectedTrimmableCount += increment; - } - if ((itemSupportedOperations & MediaObject.SUPPORT_MUTE) > 0) { - mSelectedMuteableCount += increment; - } - if ((itemSupportedOperations & MediaObject.SUPPORT_SHARE) > 0) { - mSelectedShareableCount += increment; - if (itemType == FileColumns.MEDIA_TYPE_IMAGE) { - mSelectedShareableImageCount += increment; - } else if (itemType == FileColumns.MEDIA_TYPE_VIDEO) { - mSelectedShareableVideoCount += increment; - } - } - - mShareIntent.removeExtra(Intent.EXTRA_STREAM); - if (mSelectedShareableCount == 0) { - mShareIntent.setAction(null).setType(null); - } else if (mSelectedShareableCount >= 1) { - mCachedShareableUris = mUriSource.getSelectedShareableUris(); - if (mCachedShareableUris.size() == 0) { - mShareIntent.setAction(null).setType(null); - } else { - if (mSelectedShareableImageCount == mSelectedShareableCount) { - mShareIntent.setType(GalleryUtils.MIME_TYPE_IMAGE); - } else if (mSelectedShareableVideoCount == mSelectedShareableCount) { - mShareIntent.setType(GalleryUtils.MIME_TYPE_VIDEO); - } else { - mShareIntent.setType(GalleryUtils.MIME_TYPE_ALL); - } - if (mCachedShareableUris.size() == 1) { - mShareIntent.setAction(Intent.ACTION_SEND); - mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris.get(0)); - } else { - mShareIntent.setAction(Intent.ACTION_SEND_MULTIPLE); - mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris); - } - } - } - share.setShareIntent(mShareIntent); - } - - public int getSupportedOperations() { - if (mSelectedTotalCount == 0) { - return 0; - } - int supported = 0; - if (mSelectedTotalCount == 1) { - if (mSelectedCroppableCount == 1) { - supported |= MediaObject.SUPPORT_CROP; - } - if (mSelectedEditableCount == 1) { - supported |= MediaObject.SUPPORT_EDIT; - } - if (mSelectedSetableCount == 1) { - supported |= MediaObject.SUPPORT_SETAS; - } - if (mSelectedTrimmableCount == 1) { - supported |= MediaObject.SUPPORT_TRIM; - } - if (mSelectedMuteableCount == 1) { - supported |= MediaObject.SUPPORT_MUTE; - } - } - if (mSelectedDeletableCount == mSelectedTotalCount) { - supported |= MediaObject.SUPPORT_DELETE; - } - if (mSelectedShareableCount > 0) { - supported |= MediaObject.SUPPORT_SHARE; - } - return supported; - } - - public void onClearSelection() { - mSelectedTotalCount = 0; - mSelectedShareableCount = 0; - mSelectedShareableImageCount = 0; - mSelectedShareableVideoCount = 0; - mSelectedDeletableCount = 0; - mSelectedEditableCount = 0; - mSelectedCroppableCount = 0; - mSelectedSetableCount = 0; - mSelectedTrimmableCount = 0; - mSelectedMuteableCount = 0; - mCachedShareableUris = null; - mShareIntent.removeExtra(Intent.EXTRA_STREAM); - mShareIntent.setAction(null).setType(null); - } -} diff --git a/src/com/android/photos/adapters/AlbumSetCursorAdapter.java b/src/com/android/photos/adapters/AlbumSetCursorAdapter.java deleted file mode 100644 index ab99cde70..000000000 --- a/src/com/android/photos/adapters/AlbumSetCursorAdapter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.adapters; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.text.format.DateFormat; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.android.gallery3d.R; -import com.android.photos.data.AlbumSetLoader; -import com.android.photos.shims.LoaderCompatShim; - -import java.util.Date; - -public class AlbumSetCursorAdapter extends CursorAdapter { - - private LoaderCompatShim<Cursor> mDrawableFactory; - - public void setDrawableFactory(LoaderCompatShim<Cursor> factory) { - mDrawableFactory = factory; - } - - public AlbumSetCursorAdapter(Context context) { - super(context, null, false); - } - - @Override - public void bindView(View v, Context context, Cursor cursor) { - TextView titleTextView = (TextView) v.findViewById( - R.id.album_set_item_title); - titleTextView.setText(cursor.getString(AlbumSetLoader.INDEX_TITLE)); - - TextView countTextView = (TextView) v.findViewById( - R.id.album_set_item_count); - int count = cursor.getInt(AlbumSetLoader.INDEX_COUNT); - countTextView.setText(context.getResources().getQuantityString( - R.plurals.number_of_photos, count, count)); - - ImageView thumbImageView = (ImageView) v.findViewById( - R.id.album_set_item_image); - Drawable recycle = thumbImageView.getDrawable(); - Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle); - if (recycle != drawable) { - thumbImageView.setImageDrawable(drawable); - } - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return LayoutInflater.from(context).inflate( - R.layout.album_set_item, parent, false); - } -} diff --git a/src/com/android/photos/adapters/PhotoThumbnailAdapter.java b/src/com/android/photos/adapters/PhotoThumbnailAdapter.java deleted file mode 100644 index 1190b8c85..000000000 --- a/src/com/android/photos/adapters/PhotoThumbnailAdapter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.adapters; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.ImageView; - -import com.android.gallery3d.R; -import com.android.photos.data.PhotoSetLoader; -import com.android.photos.shims.LoaderCompatShim; -import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter; - - -public class PhotoThumbnailAdapter extends CursorAdapter implements GalleryThumbnailAdapter { - private LayoutInflater mInflater; - private LoaderCompatShim<Cursor> mDrawableFactory; - - public PhotoThumbnailAdapter(Context context) { - super(context, null, false); - mInflater = LayoutInflater.from(context); - } - - public void setDrawableFactory(LoaderCompatShim<Cursor> factory) { - mDrawableFactory = factory; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - ImageView iv = (ImageView) view.findViewById(R.id.thumbnail); - Drawable recycle = iv.getDrawable(); - Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle); - if (recycle != drawable) { - iv.setImageDrawable(drawable); - } - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = mInflater.inflate(R.layout.photo_set_item, parent, false); - return view; - } - - @Override - public float getIntrinsicAspectRatio(int position) { - Cursor cursor = getItem(position); - float width = cursor.getInt(PhotoSetLoader.INDEX_WIDTH); - float height = cursor.getInt(PhotoSetLoader.INDEX_HEIGHT); - return width / height; - } - - @Override - public Cursor getItem(int position) { - return (Cursor) super.getItem(position); - } -}
\ No newline at end of file diff --git a/src/com/android/photos/data/AlbumSetLoader.java b/src/com/android/photos/data/AlbumSetLoader.java deleted file mode 100644 index 940473255..000000000 --- a/src/com/android/photos/data/AlbumSetLoader.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.android.photos.data; - -import android.database.MatrixCursor; - - -public class AlbumSetLoader { - public static final int INDEX_ID = 0; - public static final int INDEX_TITLE = 1; - public static final int INDEX_TIMESTAMP = 2; - public static final int INDEX_THUMBNAIL_URI = 3; - public static final int INDEX_THUMBNAIL_WIDTH = 4; - public static final int INDEX_THUMBNAIL_HEIGHT = 5; - public static final int INDEX_COUNT_PENDING_UPLOAD = 6; - public static final int INDEX_COUNT = 7; - public static final int INDEX_SUPPORTED_OPERATIONS = 8; - - public static final String[] PROJECTION = { - "_id", - "title", - "timestamp", - "thumb_uri", - "thumb_width", - "thumb_height", - "count_pending_upload", - "_count", - "supported_operations" - }; - public static final MatrixCursor MOCK = createRandomCursor(30); - - private static MatrixCursor createRandomCursor(int count) { - MatrixCursor c = new MatrixCursor(PROJECTION, count); - for (int i = 0; i < count; i++) { - c.addRow(createRandomRow()); - } - return c; - } - - private static Object[] createRandomRow() { - double random = Math.random(); - int id = (int) (500 * random); - Object[] row = { - id, - "Fun times " + id, - (long) (System.currentTimeMillis() * random), - null, - 0, - 0, - (random < .3 ? 1 : 0), - 1, - 0 - }; - return row; - } -}
\ No newline at end of file diff --git a/src/com/android/photos/data/BitmapDecoder.java b/src/com/android/photos/data/BitmapDecoder.java deleted file mode 100644 index 0671e73ca..000000000 --- a/src/com/android/photos/data/BitmapDecoder.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.BitmapFactory.Options; -import android.util.Log; -import android.util.Pools.Pool; -import android.util.Pools.SynchronizedPool; - -import com.android.gallery3d.common.BitmapUtils; -import com.android.gallery3d.common.Utils; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -/** - * BitmapDecoder keeps a pool of temporary storage to reuse for decoding - * bitmaps. It also simplifies the multi-stage decoding required to efficiently - * use GalleryBitmapPool. The static methods decode and decodeFile can be used - * to decode a bitmap from GalleryBitmapPool. The bitmap may be returned - * directly to GalleryBitmapPool or use the put method here when the bitmap is - * ready to be recycled. - */ -public class BitmapDecoder { - private static final String TAG = BitmapDecoder.class.getSimpleName(); - private static final int POOL_SIZE = 4; - private static final int TEMP_STORAGE_SIZE_BYTES = 16 * 1024; - private static final int HEADER_MAX_SIZE = 128 * 1024; - private static final int NO_SCALING = -1; - - private static final Pool<BitmapFactory.Options> sOptions = - new SynchronizedPool<BitmapFactory.Options>(POOL_SIZE); - - private interface Decoder<T> { - Bitmap decode(T input, BitmapFactory.Options options); - - boolean decodeBounds(T input, BitmapFactory.Options options); - } - - private static abstract class OnlyDecode<T> implements Decoder<T> { - @Override - public boolean decodeBounds(T input, BitmapFactory.Options options) { - decode(input, options); - return true; - } - } - - private static final Decoder<InputStream> sStreamDecoder = new Decoder<InputStream>() { - @Override - public Bitmap decode(InputStream is, Options options) { - return BitmapFactory.decodeStream(is, null, options); - } - - @Override - public boolean decodeBounds(InputStream is, Options options) { - is.mark(HEADER_MAX_SIZE); - BitmapFactory.decodeStream(is, null, options); - try { - is.reset(); - return true; - } catch (IOException e) { - Log.e(TAG, "Could not decode stream to bitmap", e); - return false; - } - } - }; - - private static final Decoder<String> sFileDecoder = new OnlyDecode<String>() { - @Override - public Bitmap decode(String filePath, Options options) { - return BitmapFactory.decodeFile(filePath, options); - } - }; - - private static final Decoder<byte[]> sByteArrayDecoder = new OnlyDecode<byte[]>() { - @Override - public Bitmap decode(byte[] data, Options options) { - return BitmapFactory.decodeByteArray(data, 0, data.length, options); - } - }; - - private static <T> Bitmap delegateDecode(Decoder<T> decoder, T input, int width, int height) { - BitmapFactory.Options options = getOptions(); - GalleryBitmapPool pool = GalleryBitmapPool.getInstance(); - try { - options.inJustDecodeBounds = true; - if (!decoder.decodeBounds(input, options)) { - return null; - } - options.inJustDecodeBounds = false; - Bitmap reuseBitmap = null; - if (width != NO_SCALING && options.outWidth >= width && options.outHeight >= height) { - setScaling(options, width, height); - } else { - reuseBitmap = pool.get(options.outWidth, options.outHeight); - } - options.inBitmap = reuseBitmap; - Bitmap decodedBitmap = decoder.decode(input, options); - if (reuseBitmap != null && decodedBitmap != reuseBitmap) { - pool.put(reuseBitmap); - } - return decodedBitmap; - } catch (IllegalArgumentException e) { - if (options.inBitmap == null) { - throw e; - } - pool.put(options.inBitmap); - options.inBitmap = null; - return decoder.decode(input, options); - } finally { - options.inBitmap = null; - options.inJustDecodeBounds = false; - sOptions.release(options); - } - } - - public static Bitmap decode(InputStream in) { - try { - if (!in.markSupported()) { - in = new BufferedInputStream(in); - } - return delegateDecode(sStreamDecoder, in, NO_SCALING, NO_SCALING); - } finally { - Utils.closeSilently(in); - } - } - - public static Bitmap decode(File file) { - return decodeFile(file.getPath()); - } - - public static Bitmap decodeFile(String path) { - return delegateDecode(sFileDecoder, path, NO_SCALING, NO_SCALING); - } - - public static Bitmap decodeByteArray(byte[] data) { - return delegateDecode(sByteArrayDecoder, data, NO_SCALING, NO_SCALING); - } - - public static void put(Bitmap bitmap) { - GalleryBitmapPool.getInstance().put(bitmap); - } - - /** - * Decodes to a specific size. If the dimensions of the image don't match - * width x height, the resulting image will be in the proportions of the - * decoded image, but will be scaled to fill the dimensions. For example, if - * width and height are 10x10 and the image is 200x100, the resulting image - * will be scaled/sampled to 20x10. - */ - public static Bitmap decodeFile(String path, int width, int height) { - return delegateDecode(sFileDecoder, path, width, height); - } - - /** @see #decodeFile(String, int, int) */ - public static Bitmap decodeByteArray(byte[] data, int width, int height) { - return delegateDecode(sByteArrayDecoder, data, width, height); - } - - /** @see #decodeFile(String, int, int) */ - public static Bitmap decode(InputStream in, int width, int height) { - try { - if (!in.markSupported()) { - in = new BufferedInputStream(in); - } - return delegateDecode(sStreamDecoder, in, width, height); - } finally { - Utils.closeSilently(in); - } - } - - private static BitmapFactory.Options getOptions() { - BitmapFactory.Options opts = sOptions.acquire(); - if (opts == null) { - opts = new BitmapFactory.Options(); - opts.inMutable = true; - opts.inPreferredConfig = Config.ARGB_8888; - opts.inTempStorage = new byte[TEMP_STORAGE_SIZE_BYTES]; - } - opts.inSampleSize = 1; - opts.inDensity = 1; - opts.inTargetDensity = 1; - - return opts; - } - - // Sets the options to sample then scale the image so that the image's - // minimum dimension will match side. - private static void setScaling(BitmapFactory.Options options, int width, int height) { - float widthScale = ((float)options.outWidth)/ width; - float heightScale = ((float) options.outHeight)/height; - int side = (widthScale < heightScale) ? width : height; - options.inSampleSize = BitmapUtils.computeSampleSize(options.outWidth, options.outHeight, - side, BitmapUtils.UNCONSTRAINED); - int constraint; - if (options.outWidth < options.outHeight) { - // Width is the constraint. Scale so that width = side. - constraint = options.outWidth; - } else { - // Height is the constraint. Scale so that height = side. - constraint = options.outHeight; - } - options.inDensity = constraint / options.inSampleSize; - options.inTargetDensity = side; - } -} diff --git a/src/com/android/photos/data/FileRetriever.java b/src/com/android/photos/data/FileRetriever.java deleted file mode 100644 index eb7686ef6..000000000 --- a/src/com/android/photos/data/FileRetriever.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.graphics.Bitmap; -import android.media.ExifInterface; -import android.net.Uri; -import android.util.Log; -import android.webkit.MimeTypeMap; - -import com.android.gallery3d.common.BitmapUtils; - -import java.io.File; -import java.io.IOException; - -public class FileRetriever implements MediaRetriever { - private static final String TAG = FileRetriever.class.getSimpleName(); - - @Override - public File getLocalFile(Uri contentUri) { - return new File(contentUri.getPath()); - } - - @Override - public MediaSize getFastImageSize(Uri contentUri, MediaSize size) { - if (isVideo(contentUri)) { - return null; - } - return MediaSize.TemporaryThumbnail; - } - - @Override - public byte[] getTemporaryImage(Uri contentUri, MediaSize fastImageSize) { - - try { - ExifInterface exif = new ExifInterface(contentUri.getPath()); - if (exif.hasThumbnail()) { - return exif.getThumbnail(); - } - } catch (IOException e) { - Log.w(TAG, "Unable to load exif for " + contentUri); - } - return null; - } - - @Override - public boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile) { - if (imageSize == MediaSize.Original) { - return false; // getLocalFile should always return the original. - } - if (imageSize == MediaSize.Thumbnail) { - File preview = MediaCache.getInstance().getCachedFile(contentUri, MediaSize.Preview); - if (preview != null) { - // Just downsample the preview, it is faster. - return MediaCacheUtils.downsample(preview, imageSize, tempFile); - } - } - File highRes = new File(contentUri.getPath()); - boolean success; - if (!isVideo(contentUri)) { - success = MediaCacheUtils.downsample(highRes, imageSize, tempFile); - } else { - // Video needs to extract the bitmap. - Bitmap bitmap = BitmapUtils.createVideoThumbnail(highRes.getPath()); - if (bitmap == null) { - return false; - } else if (imageSize == MediaSize.Thumbnail - && !MediaCacheUtils.needsDownsample(bitmap, MediaSize.Preview) - && MediaCacheUtils.writeToFile(bitmap, tempFile)) { - // Opportunistically save preview - MediaCache mediaCache = MediaCache.getInstance(); - mediaCache.insertIntoCache(contentUri, MediaSize.Preview, tempFile); - } - // Now scale the image - success = MediaCacheUtils.downsample(bitmap, imageSize, tempFile); - } - return success; - } - - @Override - public Uri normalizeUri(Uri contentUri, MediaSize size) { - return contentUri; - } - - @Override - public MediaSize normalizeMediaSize(Uri contentUri, MediaSize size) { - return size; - } - - private static boolean isVideo(Uri uri) { - MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); - String mimeType = mimeTypeMap.getMimeTypeFromExtension(extension); - return (mimeType != null && mimeType.startsWith("video/")); - } -} diff --git a/src/com/android/photos/data/GalleryBitmapPool.java b/src/com/android/photos/data/GalleryBitmapPool.java deleted file mode 100644 index 390a0d42f..000000000 --- a/src/com/android/photos/data/GalleryBitmapPool.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.graphics.Bitmap; -import android.graphics.Point; -import android.util.Pools.Pool; -import android.util.Pools.SynchronizedPool; - -import com.android.photos.data.SparseArrayBitmapPool.Node; - -/** - * Pool allowing the efficient reuse of bitmaps in order to avoid long - * garbage collection pauses. - */ -public class GalleryBitmapPool { - - private static final int CAPACITY_BYTES = 20971520; - - // We found that Gallery uses bitmaps that are either square (for example, - // tiles of large images or square thumbnails), match one of the common - // photo aspect ratios (4x3, 3x2, or 16x9), or, less commonly, are of some - // other aspect ratio. Taking advantage of this information, we use 3 - // SparseArrayBitmapPool instances to back the GalleryBitmapPool, which affords - // O(1) lookups for square bitmaps, and average-case - but *not* asymptotically - - // O(1) lookups for common photo aspect ratios and other miscellaneous aspect - // ratios. Beware of the pathological case where there are many bitmaps added - // to the pool with different non-square aspect ratios but the same width, as - // performance will degrade and the average case lookup will approach - // O(# of different aspect ratios). - private static final int POOL_INDEX_NONE = -1; - private static final int POOL_INDEX_SQUARE = 0; - private static final int POOL_INDEX_PHOTO = 1; - private static final int POOL_INDEX_MISC = 2; - - private static final Point[] COMMON_PHOTO_ASPECT_RATIOS = - { new Point(4, 3), new Point(3, 2), new Point(16, 9) }; - - private int mCapacityBytes; - private SparseArrayBitmapPool [] mPools; - private Pool<Node> mSharedNodePool = new SynchronizedPool<Node>(128); - - private GalleryBitmapPool(int capacityBytes) { - mPools = new SparseArrayBitmapPool[3]; - mPools[POOL_INDEX_SQUARE] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); - mPools[POOL_INDEX_PHOTO] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); - mPools[POOL_INDEX_MISC] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool); - mCapacityBytes = capacityBytes; - } - - private static GalleryBitmapPool sInstance = new GalleryBitmapPool(CAPACITY_BYTES); - - public static GalleryBitmapPool getInstance() { - return sInstance; - } - - private SparseArrayBitmapPool getPoolForDimensions(int width, int height) { - int index = getPoolIndexForDimensions(width, height); - if (index == POOL_INDEX_NONE) { - return null; - } else { - return mPools[index]; - } - } - - private int getPoolIndexForDimensions(int width, int height) { - if (width <= 0 || height <= 0) { - return POOL_INDEX_NONE; - } - if (width == height) { - return POOL_INDEX_SQUARE; - } - int min, max; - if (width > height) { - min = height; - max = width; - } else { - min = width; - max = height; - } - for (Point ar : COMMON_PHOTO_ASPECT_RATIOS) { - if (min * ar.x == max * ar.y) { - return POOL_INDEX_PHOTO; - } - } - return POOL_INDEX_MISC; - } - - /** - * @return Capacity of the pool in bytes. - */ - public synchronized int getCapacity() { - return mCapacityBytes; - } - - /** - * @return Approximate total size in bytes of the bitmaps stored in the pool. - */ - public int getSize() { - // Note that this only returns an approximate size, since multiple threads - // might be getting and putting Bitmaps from the pool and we lock at the - // sub-pool level to avoid unnecessary blocking. - int total = 0; - for (SparseArrayBitmapPool p : mPools) { - total += p.getSize(); - } - return total; - } - - /** - * @return Bitmap from the pool with the desired height/width or null if none available. - */ - public Bitmap get(int width, int height) { - SparseArrayBitmapPool pool = getPoolForDimensions(width, height); - if (pool == null) { - return null; - } else { - return pool.get(width, height); - } - } - - /** - * Adds the given bitmap to the pool. - * @return Whether the bitmap was added to the pool. - */ - public boolean put(Bitmap b) { - if (b == null || b.getConfig() != Bitmap.Config.ARGB_8888) { - return false; - } - SparseArrayBitmapPool pool = getPoolForDimensions(b.getWidth(), b.getHeight()); - if (pool == null) { - b.recycle(); - return false; - } else { - return pool.put(b); - } - } - - /** - * Empty the pool, recycling all the bitmaps currently in it. - */ - public void clear() { - for (SparseArrayBitmapPool p : mPools) { - p.clear(); - } - } -} diff --git a/src/com/android/photos/data/MediaCache.java b/src/com/android/photos/data/MediaCache.java deleted file mode 100644 index 0952a4017..000000000 --- a/src/com/android/photos/data/MediaCache.java +++ /dev/null @@ -1,676 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.os.Environment; -import android.util.Log; - -import com.android.photos.data.MediaCacheDatabase.Action; -import com.android.photos.data.MediaRetriever.MediaSize; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; - -/** - * MediaCache keeps a cache of images, videos, thumbnails and previews. Calls to - * retrieve a specific media item are executed asynchronously. The caller has an - * option to receive a notification for lower resolution images that happen to - * be available prior to the one requested. - * <p> - * When an media item has been retrieved, the notification for it is called on a - * separate notifier thread. This thread should not be held for a long time so - * that other notifications may happen. - * </p> - * <p> - * Media items are uniquely identified by their content URIs. Each - * scheme/authority can offer its own MediaRetriever, running in its own thread. - * </p> - * <p> - * The MediaCache is an LRU cache, but does not allow the thumbnail cache to - * drop below a minimum size. This prevents browsing through original images to - * wipe out the thumbnails. - * </p> - */ -public class MediaCache { - static final String TAG = MediaCache.class.getSimpleName(); - /** Subdirectory containing the image cache. */ - static final String IMAGE_CACHE_SUBDIR = "image_cache"; - /** File name extension to use for cached images. */ - static final String IMAGE_EXTENSION = ".cache"; - /** File name extension to use for temporary cached images while retrieving. */ - static final String TEMP_IMAGE_EXTENSION = ".temp"; - - public static interface ImageReady { - void imageReady(InputStream bitmapInputStream); - } - - public static interface OriginalReady { - void originalReady(File originalFile); - } - - /** A Thread for each MediaRetriever */ - private class ProcessQueue extends Thread { - private Queue<ProcessingJob> mQueue; - - public ProcessQueue(Queue<ProcessingJob> queue) { - mQueue = queue; - } - - @Override - public void run() { - while (mRunning) { - ProcessingJob status; - synchronized (mQueue) { - while (mQueue.isEmpty()) { - try { - mQueue.wait(); - } catch (InterruptedException e) { - if (!mRunning) { - return; - } - Log.w(TAG, "Unexpected interruption", e); - } - } - status = mQueue.remove(); - } - processTask(status); - } - } - }; - - private interface NotifyReady { - void notifyReady(); - - void setFile(File file) throws FileNotFoundException; - - boolean isPrefetch(); - } - - private static class NotifyOriginalReady implements NotifyReady { - private final OriginalReady mCallback; - private File mFile; - - public NotifyOriginalReady(OriginalReady callback) { - mCallback = callback; - } - - @Override - public void notifyReady() { - if (mCallback != null) { - mCallback.originalReady(mFile); - } - } - - @Override - public void setFile(File file) { - mFile = file; - } - - @Override - public boolean isPrefetch() { - return mCallback == null; - } - } - - private static class NotifyImageReady implements NotifyReady { - private final ImageReady mCallback; - private InputStream mInputStream; - - public NotifyImageReady(ImageReady callback) { - mCallback = callback; - } - - @Override - public void notifyReady() { - if (mCallback != null) { - mCallback.imageReady(mInputStream); - } - } - - @Override - public void setFile(File file) throws FileNotFoundException { - mInputStream = new FileInputStream(file); - } - - public void setBytes(byte[] bytes) { - mInputStream = new ByteArrayInputStream(bytes); - } - - @Override - public boolean isPrefetch() { - return mCallback == null; - } - } - - /** A media item to be retrieved and its notifications. */ - private static class ProcessingJob { - public ProcessingJob(Uri uri, MediaSize size, NotifyReady complete, - NotifyImageReady lowResolution) { - this.contentUri = uri; - this.size = size; - this.complete = complete; - this.lowResolution = lowResolution; - } - public Uri contentUri; - public MediaSize size; - public NotifyImageReady lowResolution; - public NotifyReady complete; - } - - private boolean mRunning = true; - private static MediaCache sInstance; - private File mCacheDir; - private Context mContext; - private Queue<NotifyReady> mCallbacks = new LinkedList<NotifyReady>(); - private Map<String, MediaRetriever> mRetrievers = new HashMap<String, MediaRetriever>(); - private Map<String, List<ProcessingJob>> mTasks = new HashMap<String, List<ProcessingJob>>(); - private List<ProcessQueue> mProcessingThreads = new ArrayList<ProcessQueue>(); - private MediaCacheDatabase mDatabaseHelper; - private long mTempImageNumber = 1; - private Object mTempImageNumberLock = new Object(); - - private long mMaxCacheSize = 40 * 1024 * 1024; // 40 MB - private long mMinThumbCacheSize = 4 * 1024 * 1024; // 4 MB - private long mCacheSize = -1; - private long mThumbCacheSize = -1; - private Object mCacheSizeLock = new Object(); - - private Action mNotifyCachedLowResolution = new Action() { - @Override - public void execute(Uri uri, long id, MediaSize size, Object parameter) { - ProcessingJob job = (ProcessingJob) parameter; - File file = createCacheImagePath(id); - addNotification(job.lowResolution, file); - } - }; - - private Action mMoveTempToCache = new Action() { - @Override - public void execute(Uri uri, long id, MediaSize size, Object parameter) { - File tempFile = (File) parameter; - File cacheFile = createCacheImagePath(id); - tempFile.renameTo(cacheFile); - } - }; - - private Action mDeleteFile = new Action() { - @Override - public void execute(Uri uri, long id, MediaSize size, Object parameter) { - File file = createCacheImagePath(id); - file.delete(); - synchronized (mCacheSizeLock) { - if (mCacheSize != -1) { - long length = (Long) parameter; - mCacheSize -= length; - if (size == MediaSize.Thumbnail) { - mThumbCacheSize -= length; - } - } - } - } - }; - - /** The thread used to make ImageReady and OriginalReady callbacks. */ - private Thread mProcessNotifications = new Thread() { - @Override - public void run() { - while (mRunning) { - NotifyReady notifyImage; - synchronized (mCallbacks) { - while (mCallbacks.isEmpty()) { - try { - mCallbacks.wait(); - } catch (InterruptedException e) { - if (!mRunning) { - return; - } - Log.w(TAG, "Unexpected Interruption, continuing"); - } - } - notifyImage = mCallbacks.remove(); - } - - notifyImage.notifyReady(); - } - } - }; - - public static synchronized void initialize(Context context) { - if (sInstance == null) { - sInstance = new MediaCache(context); - MediaCacheUtils.initialize(context); - } - } - - public static MediaCache getInstance() { - return sInstance; - } - - public static synchronized void shutdown() { - sInstance.mRunning = false; - sInstance.mProcessNotifications.interrupt(); - for (ProcessQueue processingThread : sInstance.mProcessingThreads) { - processingThread.interrupt(); - } - sInstance = null; - } - - private MediaCache(Context context) { - mDatabaseHelper = new MediaCacheDatabase(context); - mProcessNotifications.start(); - mContext = context; - } - - // This is used for testing. - public void setCacheDir(File cacheDir) { - cacheDir.mkdirs(); - mCacheDir = cacheDir; - } - - public File getCacheDir() { - synchronized (mContext) { - if (mCacheDir == null) { - String state = Environment.getExternalStorageState(); - File baseDir; - if (Environment.MEDIA_MOUNTED.equals(state)) { - baseDir = mContext.getExternalCacheDir(); - } else { - // Stored in internal cache - baseDir = mContext.getCacheDir(); - } - mCacheDir = new File(baseDir, IMAGE_CACHE_SUBDIR); - mCacheDir.mkdirs(); - } - return mCacheDir; - } - } - - /** - * Invalidates all cached images related to a given contentUri. This call - * doesn't complete until the images have been removed from the cache. - */ - public void invalidate(Uri contentUri) { - mDatabaseHelper.delete(contentUri, mDeleteFile); - } - - public void clearCacheDir() { - File[] cachedFiles = getCacheDir().listFiles(); - if (cachedFiles != null) { - for (File cachedFile : cachedFiles) { - cachedFile.delete(); - } - } - } - - /** - * Add a MediaRetriever for a Uri scheme and authority. This MediaRetriever - * will be granted its own thread for retrieving images. - */ - public void addRetriever(String scheme, String authority, MediaRetriever retriever) { - String differentiator = getDifferentiator(scheme, authority); - synchronized (mRetrievers) { - mRetrievers.put(differentiator, retriever); - } - synchronized (mTasks) { - LinkedList<ProcessingJob> queue = new LinkedList<ProcessingJob>(); - mTasks.put(differentiator, queue); - new ProcessQueue(queue).start(); - } - } - - /** - * Retrieves a thumbnail. complete will be called when the thumbnail is - * available. If lowResolution is not null and a lower resolution thumbnail - * is available before the thumbnail, lowResolution will be called prior to - * complete. All callbacks will be made on a thread other than the calling - * thread. - * - * @param contentUri The URI for the full resolution image to search for. - * @param complete Callback for when the image has been retrieved. - * @param lowResolution If not null and a lower resolution image is - * available prior to retrieving the thumbnail, this will be - * called with the low resolution bitmap. - */ - public void retrieveThumbnail(Uri contentUri, ImageReady complete, ImageReady lowResolution) { - addTask(contentUri, complete, lowResolution, MediaSize.Thumbnail); - } - - /** - * Retrieves a preview. complete will be called when the preview is - * available. If lowResolution is not null and a lower resolution preview is - * available before the preview, lowResolution will be called prior to - * complete. All callbacks will be made on a thread other than the calling - * thread. - * - * @param contentUri The URI for the full resolution image to search for. - * @param complete Callback for when the image has been retrieved. - * @param lowResolution If not null and a lower resolution image is - * available prior to retrieving the preview, this will be called - * with the low resolution bitmap. - */ - public void retrievePreview(Uri contentUri, ImageReady complete, ImageReady lowResolution) { - addTask(contentUri, complete, lowResolution, MediaSize.Preview); - } - - /** - * Retrieves the original image or video. complete will be called when the - * media is available on the local file system. If lowResolution is not null - * and a lower resolution preview is available before the original, - * lowResolution will be called prior to complete. All callbacks will be - * made on a thread other than the calling thread. - * - * @param contentUri The URI for the full resolution image to search for. - * @param complete Callback for when the image has been retrieved. - * @param lowResolution If not null and a lower resolution image is - * available prior to retrieving the preview, this will be called - * with the low resolution bitmap. - */ - public void retrieveOriginal(Uri contentUri, OriginalReady complete, ImageReady lowResolution) { - File localFile = getLocalFile(contentUri); - if (localFile != null) { - addNotification(new NotifyOriginalReady(complete), localFile); - } else { - NotifyImageReady notifyLowResolution = (lowResolution == null) ? null - : new NotifyImageReady(lowResolution); - addTask(contentUri, new NotifyOriginalReady(complete), notifyLowResolution, - MediaSize.Original); - } - } - - /** - * Looks for an already cached media at a specific size. - * - * @param contentUri The original media item content URI - * @param size The target size to search for in the cache - * @return The cached file location or null if it is not cached. - */ - public File getCachedFile(Uri contentUri, MediaSize size) { - Long cachedId = mDatabaseHelper.getCached(contentUri, size); - File file = null; - if (cachedId != null) { - file = createCacheImagePath(cachedId); - if (!file.exists()) { - mDatabaseHelper.delete(contentUri, size, mDeleteFile); - file = null; - } - } - return file; - } - - /** - * Inserts a media item into the cache. - * - * @param contentUri The original media item URI. - * @param size The size of the media item to store in the cache. - * @param tempFile The temporary file where the image is stored. This file - * will no longer exist after executing this method. - * @return The new location, in the cache, of the media item or null if it - * wasn't possible to move into the cache. - */ - public File insertIntoCache(Uri contentUri, MediaSize size, File tempFile) { - long fileSize = tempFile.length(); - if (fileSize == 0) { - return null; - } - File cacheFile = null; - SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); - // Ensure that this step is atomic - db.beginTransaction(); - try { - Long id = mDatabaseHelper.getCached(contentUri, size); - if (id != null) { - cacheFile = createCacheImagePath(id); - if (tempFile.renameTo(cacheFile)) { - mDatabaseHelper.updateLength(id, fileSize); - } else { - Log.w(TAG, "Could not update cached file with " + tempFile); - tempFile.delete(); - cacheFile = null; - } - } else { - ensureFreeCacheSpace(tempFile.length(), size); - id = mDatabaseHelper.insert(contentUri, size, mMoveTempToCache, tempFile); - cacheFile = createCacheImagePath(id); - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - return cacheFile; - } - - /** - * For testing purposes. - */ - public void setMaxCacheSize(long maxCacheSize) { - synchronized (mCacheSizeLock) { - mMaxCacheSize = maxCacheSize; - mMinThumbCacheSize = mMaxCacheSize / 10; - mCacheSize = -1; - mThumbCacheSize = -1; - } - } - - private File createCacheImagePath(long id) { - return new File(getCacheDir(), String.valueOf(id) + IMAGE_EXTENSION); - } - - private void addTask(Uri contentUri, ImageReady complete, ImageReady lowResolution, - MediaSize size) { - NotifyReady notifyComplete = new NotifyImageReady(complete); - NotifyImageReady notifyLowResolution = null; - if (lowResolution != null) { - notifyLowResolution = new NotifyImageReady(lowResolution); - } - addTask(contentUri, notifyComplete, notifyLowResolution, size); - } - - private void addTask(Uri contentUri, NotifyReady complete, NotifyImageReady lowResolution, - MediaSize size) { - MediaRetriever retriever = getMediaRetriever(contentUri); - Uri uri = retriever.normalizeUri(contentUri, size); - if (uri == null) { - throw new IllegalArgumentException("No MediaRetriever for " + contentUri); - } - size = retriever.normalizeMediaSize(uri, size); - - File cachedFile = getCachedFile(uri, size); - if (cachedFile != null) { - addNotification(complete, cachedFile); - return; - } - String differentiator = getDifferentiator(uri.getScheme(), uri.getAuthority()); - synchronized (mTasks) { - List<ProcessingJob> tasks = mTasks.get(differentiator); - if (tasks == null) { - throw new IllegalArgumentException("Cannot find retriever for: " + uri); - } - synchronized (tasks) { - ProcessingJob job = new ProcessingJob(uri, size, complete, lowResolution); - if (complete.isPrefetch()) { - tasks.add(job); - } else { - int index = tasks.size() - 1; - while (index >= 0 && tasks.get(index).complete.isPrefetch()) { - index--; - } - tasks.add(index + 1, job); - } - tasks.notifyAll(); - } - } - } - - private MediaRetriever getMediaRetriever(Uri uri) { - String differentiator = getDifferentiator(uri.getScheme(), uri.getAuthority()); - MediaRetriever retriever; - synchronized (mRetrievers) { - retriever = mRetrievers.get(differentiator); - } - if (retriever == null) { - throw new IllegalArgumentException("No MediaRetriever for " + uri); - } - return retriever; - } - - private File getLocalFile(Uri uri) { - MediaRetriever retriever = getMediaRetriever(uri); - File localFile = null; - if (retriever != null) { - localFile = retriever.getLocalFile(uri); - } - return localFile; - } - - private MediaSize getFastImageSize(Uri uri, MediaSize size) { - MediaRetriever retriever = getMediaRetriever(uri); - return retriever.getFastImageSize(uri, size); - } - - private boolean isFastImageBetter(MediaSize fastImageType, MediaSize size) { - if (fastImageType == null) { - return false; - } - if (size == null) { - return true; - } - return fastImageType.isBetterThan(size); - } - - private byte[] getTemporaryImage(Uri uri, MediaSize fastImageType) { - MediaRetriever retriever = getMediaRetriever(uri); - return retriever.getTemporaryImage(uri, fastImageType); - } - - private void processTask(ProcessingJob job) { - File cachedFile = getCachedFile(job.contentUri, job.size); - if (cachedFile != null) { - addNotification(job.complete, cachedFile); - return; - } - - boolean hasLowResolution = job.lowResolution != null; - if (hasLowResolution) { - MediaSize cachedSize = mDatabaseHelper.executeOnBestCached(job.contentUri, job.size, - mNotifyCachedLowResolution); - MediaSize fastImageSize = getFastImageSize(job.contentUri, job.size); - if (isFastImageBetter(fastImageSize, cachedSize)) { - if (fastImageSize.isTemporary()) { - byte[] bytes = getTemporaryImage(job.contentUri, fastImageSize); - if (bytes != null) { - addNotification(job.lowResolution, bytes); - } - } else { - File lowFile = getMedia(job.contentUri, fastImageSize); - if (lowFile != null) { - addNotification(job.lowResolution, lowFile); - } - } - } - } - - // Now get the full size desired - File fullSizeFile = getMedia(job.contentUri, job.size); - if (fullSizeFile != null) { - addNotification(job.complete, fullSizeFile); - } - } - - private void addNotification(NotifyReady callback, File file) { - try { - callback.setFile(file); - synchronized (mCallbacks) { - mCallbacks.add(callback); - mCallbacks.notifyAll(); - } - } catch (FileNotFoundException e) { - Log.e(TAG, "Unable to read file " + file, e); - } - } - - private void addNotification(NotifyImageReady callback, byte[] bytes) { - callback.setBytes(bytes); - synchronized (mCallbacks) { - mCallbacks.add(callback); - mCallbacks.notifyAll(); - } - } - - private File getMedia(Uri uri, MediaSize size) { - long imageNumber; - synchronized (mTempImageNumberLock) { - imageNumber = mTempImageNumber++; - } - File tempFile = new File(getCacheDir(), String.valueOf(imageNumber) + TEMP_IMAGE_EXTENSION); - MediaRetriever retriever = getMediaRetriever(uri); - boolean retrieved = retriever.getMedia(uri, size, tempFile); - File cachedFile = null; - if (retrieved) { - ensureFreeCacheSpace(tempFile.length(), size); - long id = mDatabaseHelper.insert(uri, size, mMoveTempToCache, tempFile); - cachedFile = createCacheImagePath(id); - } - return cachedFile; - } - - private static String getDifferentiator(String scheme, String authority) { - if (authority == null) { - return scheme; - } - StringBuilder differentiator = new StringBuilder(scheme); - differentiator.append(':'); - differentiator.append(authority); - return differentiator.toString(); - } - - private void ensureFreeCacheSpace(long size, MediaSize mediaSize) { - synchronized (mCacheSizeLock) { - if (mCacheSize == -1 || mThumbCacheSize == -1) { - mCacheSize = mDatabaseHelper.getCacheSize(); - mThumbCacheSize = mDatabaseHelper.getThumbnailCacheSize(); - if (mCacheSize == -1 || mThumbCacheSize == -1) { - Log.e(TAG, "Can't determine size of the image cache"); - return; - } - } - mCacheSize += size; - if (mediaSize == MediaSize.Thumbnail) { - mThumbCacheSize += size; - } - if (mCacheSize > mMaxCacheSize) { - shrinkCacheLocked(); - } - } - } - - private void shrinkCacheLocked() { - long deleteSize = mMinThumbCacheSize; - boolean includeThumbnails = (mThumbCacheSize - deleteSize) > mMinThumbCacheSize; - mDatabaseHelper.deleteOldCached(includeThumbnails, deleteSize, mDeleteFile); - } -} diff --git a/src/com/android/photos/data/MediaCacheDatabase.java b/src/com/android/photos/data/MediaCacheDatabase.java deleted file mode 100644 index c92ac0fdf..000000000 --- a/src/com/android/photos/data/MediaCacheDatabase.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; -import android.provider.BaseColumns; - -import com.android.photos.data.MediaRetriever.MediaSize; - -import java.io.File; - -class MediaCacheDatabase extends SQLiteOpenHelper { - public static final int DB_VERSION = 1; - public static final String DB_NAME = "mediacache.db"; - - /** Internal database table used for the media cache */ - public static final String TABLE = "media_cache"; - - private static interface Columns extends BaseColumns { - /** The Content URI of the original image. */ - public static final String URI = "uri"; - /** MediaSize.getValue() values. */ - public static final String MEDIA_SIZE = "media_size"; - /** The last time this image was queried. */ - public static final String LAST_ACCESS = "last_access"; - /** The image size in bytes. */ - public static final String SIZE_IN_BYTES = "size"; - } - - static interface Action { - void execute(Uri uri, long id, MediaSize size, Object parameter); - } - - private static final String[] PROJECTION_ID = { - Columns._ID, - }; - - private static final String[] PROJECTION_CACHED = { - Columns._ID, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES, - }; - - private static final String[] PROJECTION_CACHE_SIZE = { - "SUM(" + Columns.SIZE_IN_BYTES + ")" - }; - - private static final String[] PROJECTION_DELETE_OLD = { - Columns._ID, Columns.URI, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES, Columns.LAST_ACCESS, - }; - - public static final String CREATE_TABLE = "CREATE TABLE " + TABLE + "(" - + Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + Columns.URI + " TEXT NOT NULL," - + Columns.MEDIA_SIZE + " INTEGER NOT NULL," - + Columns.LAST_ACCESS + " INTEGER NOT NULL," - + Columns.SIZE_IN_BYTES + " INTEGER NOT NULL," - + "UNIQUE(" + Columns.URI + ", " + Columns.MEDIA_SIZE + "))"; - - public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLE; - - public static final String WHERE_THUMBNAIL = Columns.MEDIA_SIZE + " = " - + MediaSize.Thumbnail.getValue(); - - public static final String WHERE_NOT_THUMBNAIL = Columns.MEDIA_SIZE + " <> " - + MediaSize.Thumbnail.getValue(); - - public static final String WHERE_CLEAR_CACHE = Columns.LAST_ACCESS + " <= ?"; - - public static final String WHERE_CLEAR_CACHE_LARGE = WHERE_CLEAR_CACHE + " AND " - + WHERE_NOT_THUMBNAIL; - - static class QueryCacheResults { - public QueryCacheResults(long id, int sizeVal) { - this.id = id; - this.size = MediaSize.fromInteger(sizeVal); - } - public long id; - public MediaSize size; - } - - public MediaCacheDatabase(Context context) { - super(context, DB_NAME, null, DB_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(CREATE_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL(DROP_TABLE); - onCreate(db); - MediaCache.getInstance().clearCacheDir(); - } - - public Long getCached(Uri uri, MediaSize size) { - String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?"; - SQLiteDatabase db = getWritableDatabase(); - String[] whereArgs = { - uri.toString(), String.valueOf(size.getValue()), - }; - Cursor cursor = db.query(TABLE, PROJECTION_ID, where, whereArgs, null, null, null); - Long id = null; - if (cursor.moveToNext()) { - id = cursor.getLong(0); - } - cursor.close(); - if (id != null) { - String[] updateArgs = { - id.toString() - }; - ContentValues values = new ContentValues(); - values.put(Columns.LAST_ACCESS, System.currentTimeMillis()); - db.beginTransaction(); - try { - db.update(TABLE, values, Columns._ID + " = ?", updateArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - return id; - } - - public MediaSize executeOnBestCached(Uri uri, MediaSize size, Action action) { - String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " < ?"; - String orderBy = Columns.MEDIA_SIZE + " DESC"; - SQLiteDatabase db = getReadableDatabase(); - String[] whereArgs = { - uri.toString(), String.valueOf(size.getValue()), - }; - Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, orderBy); - MediaSize bestSize = null; - if (cursor.moveToNext()) { - long id = cursor.getLong(0); - bestSize = MediaSize.fromInteger(cursor.getInt(1)); - long fileSize = cursor.getLong(2); - action.execute(uri, id, bestSize, fileSize); - } - cursor.close(); - return bestSize; - } - - public long insert(Uri uri, MediaSize size, Action action, File tempFile) { - SQLiteDatabase db = getWritableDatabase(); - db.beginTransaction(); - try { - ContentValues values = new ContentValues(); - values.put(Columns.LAST_ACCESS, System.currentTimeMillis()); - values.put(Columns.MEDIA_SIZE, size.getValue()); - values.put(Columns.URI, uri.toString()); - values.put(Columns.SIZE_IN_BYTES, tempFile.length()); - long id = db.insert(TABLE, null, values); - if (id != -1) { - action.execute(uri, id, size, tempFile); - db.setTransactionSuccessful(); - } - return id; - } finally { - db.endTransaction(); - } - } - - public void updateLength(long id, long fileSize) { - ContentValues values = new ContentValues(); - values.put(Columns.SIZE_IN_BYTES, fileSize); - String[] whereArgs = { - String.valueOf(id) - }; - SQLiteDatabase db = getWritableDatabase(); - db.beginTransaction(); - try { - db.update(TABLE, values, Columns._ID + " = ?", whereArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public void delete(Uri uri, MediaSize size, Action action) { - String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?"; - String[] whereArgs = { - uri.toString(), String.valueOf(size.getValue()), - }; - deleteRows(uri, where, whereArgs, action); - } - - public void delete(Uri uri, Action action) { - String where = Columns.URI + " = ?"; - String[] whereArgs = { - uri.toString() - }; - deleteRows(uri, where, whereArgs, action); - } - - private void deleteRows(Uri uri, String where, String[] whereArgs, Action action) { - SQLiteDatabase db = getWritableDatabase(); - // Make this an atomic operation - db.beginTransaction(); - Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, null); - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - MediaSize size = MediaSize.fromInteger(cursor.getInt(1)); - long length = cursor.getLong(2); - action.execute(uri, id, size, length); - } - cursor.close(); - try { - db.delete(TABLE, where, whereArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public void deleteOldCached(boolean includeThumbnails, long deleteSize, Action action) { - String where = includeThumbnails ? null : WHERE_NOT_THUMBNAIL; - long lastAccess = 0; - SQLiteDatabase db = getWritableDatabase(); - db.beginTransaction(); - try { - Cursor cursor = db.query(TABLE, PROJECTION_DELETE_OLD, where, null, null, null, - Columns.LAST_ACCESS); - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - String uri = cursor.getString(1); - MediaSize size = MediaSize.fromInteger(cursor.getInt(2)); - long length = cursor.getLong(3); - long imageLastAccess = cursor.getLong(4); - - if (imageLastAccess != lastAccess && deleteSize < 0) { - break; // We've deleted enough. - } - lastAccess = imageLastAccess; - action.execute(Uri.parse(uri), id, size, length); - deleteSize -= length; - } - cursor.close(); - String[] whereArgs = { - String.valueOf(lastAccess), - }; - String whereDelete = includeThumbnails ? WHERE_CLEAR_CACHE : WHERE_CLEAR_CACHE_LARGE; - db.delete(TABLE, whereDelete, whereArgs); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public long getCacheSize() { - return getCacheSize(null); - } - - public long getThumbnailCacheSize() { - return getCacheSize(WHERE_THUMBNAIL); - } - - private long getCacheSize(String where) { - SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = db.query(TABLE, PROJECTION_CACHE_SIZE, where, null, null, null, null); - long size = -1; - if (cursor.moveToNext()) { - size = cursor.getLong(0); - } - cursor.close(); - return size; - } -} diff --git a/src/com/android/photos/data/MediaCacheUtils.java b/src/com/android/photos/data/MediaCacheUtils.java deleted file mode 100644 index e3ccd1402..000000000 --- a/src/com/android/photos/data/MediaCacheUtils.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.BitmapFactory; -import android.util.Log; -import android.util.Pools.SimplePool; -import android.util.Pools.SynchronizedPool; - -import com.android.gallery3d.R; -import com.android.gallery3d.common.BitmapUtils; -import com.android.gallery3d.common.Utils; -import com.android.gallery3d.data.DecodeUtils; -import com.android.gallery3d.data.MediaItem; -import com.android.gallery3d.util.ThreadPool.CancelListener; -import com.android.gallery3d.util.ThreadPool.JobContext; -import com.android.photos.data.MediaRetriever.MediaSize; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class MediaCacheUtils { - private static final String TAG = MediaCacheUtils.class.getSimpleName(); - private static int QUALITY = 80; - private static final int BUFFER_SIZE = 4096; - private static final SimplePool<byte[]> mBufferPool = new SynchronizedPool<byte[]>(5); - - private static final JobContext sJobStub = new JobContext() { - - @Override - public boolean isCancelled() { - return false; - } - - @Override - public void setCancelListener(CancelListener listener) { - } - - @Override - public boolean setMode(int mode) { - return true; - } - }; - - private static int mTargetThumbnailSize; - private static int mTargetPreviewSize; - - public static void initialize(Context context) { - Resources resources = context.getResources(); - mTargetThumbnailSize = resources.getDimensionPixelSize(R.dimen.size_thumbnail); - mTargetPreviewSize = resources.getDimensionPixelSize(R.dimen.size_preview); - } - - public static int getTargetSize(MediaSize size) { - return (size == MediaSize.Thumbnail) ? mTargetThumbnailSize : mTargetPreviewSize; - } - - public static boolean downsample(File inBitmap, MediaSize targetSize, File outBitmap) { - if (MediaSize.Original == targetSize) { - return false; // MediaCache should use the local path for this. - } - int size = getTargetSize(targetSize); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - // TODO: remove unnecessary job context from DecodeUtils. - Bitmap bitmap = DecodeUtils.decodeThumbnail(sJobStub, inBitmap.getPath(), options, size, - MediaItem.TYPE_THUMBNAIL); - boolean success = (bitmap != null); - if (success) { - success = writeAndRecycle(bitmap, outBitmap); - } - return success; - } - - public static boolean downsample(Bitmap inBitmap, MediaSize size, File outBitmap) { - if (MediaSize.Original == size) { - return false; // MediaCache should use the local path for this. - } - int targetSize = getTargetSize(size); - boolean success; - if (!needsDownsample(inBitmap, size)) { - success = writeAndRecycle(inBitmap, outBitmap); - } else { - float maxDimension = Math.max(inBitmap.getWidth(), inBitmap.getHeight()); - float scale = targetSize / maxDimension; - int targetWidth = Math.round(scale * inBitmap.getWidth()); - int targetHeight = Math.round(scale * inBitmap.getHeight()); - Bitmap scaled = Bitmap.createScaledBitmap(inBitmap, targetWidth, targetHeight, false); - success = writeAndRecycle(scaled, outBitmap); - inBitmap.recycle(); - } - return success; - } - - public static boolean extractImageFromVideo(File inVideo, File outBitmap) { - Bitmap bitmap = BitmapUtils.createVideoThumbnail(inVideo.getPath()); - return writeAndRecycle(bitmap, outBitmap); - } - - public static boolean needsDownsample(Bitmap bitmap, MediaSize size) { - if (size == MediaSize.Original) { - return false; - } - int targetSize = getTargetSize(size); - int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); - return maxDimension > (targetSize * 4 / 3); - } - - public static boolean writeAndRecycle(Bitmap bitmap, File outBitmap) { - boolean success = writeToFile(bitmap, outBitmap); - bitmap.recycle(); - return success; - } - - public static boolean writeToFile(Bitmap bitmap, File outBitmap) { - boolean success = false; - try { - FileOutputStream out = new FileOutputStream(outBitmap); - success = bitmap.compress(CompressFormat.JPEG, QUALITY, out); - out.close(); - } catch (IOException e) { - Log.w(TAG, "Couldn't write bitmap to cache", e); - // success is already false - } - return success; - } - - public static int copyStream(InputStream in, OutputStream out) throws IOException { - byte[] buffer = mBufferPool.acquire(); - if (buffer == null) { - buffer = new byte[BUFFER_SIZE]; - } - try { - int totalWritten = 0; - int bytesRead; - while ((bytesRead = in.read(buffer)) >= 0) { - out.write(buffer, 0, bytesRead); - totalWritten += bytesRead; - } - return totalWritten; - } finally { - Utils.closeSilently(in); - Utils.closeSilently(out); - mBufferPool.release(buffer); - } - } -} diff --git a/src/com/android/photos/data/MediaRetriever.java b/src/com/android/photos/data/MediaRetriever.java deleted file mode 100644 index f383e5ffa..000000000 --- a/src/com/android/photos/data/MediaRetriever.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.net.Uri; - -import java.io.File; - -public interface MediaRetriever { - public enum MediaSize { - TemporaryThumbnail(5), Thumbnail(10), TemporaryPreview(15), Preview(20), Original(30); - - private final int mValue; - - private MediaSize(int value) { - mValue = value; - } - - public int getValue() { - return mValue; - } - - static MediaSize fromInteger(int value) { - switch (value) { - case 10: - return MediaSize.Thumbnail; - case 20: - return MediaSize.Preview; - case 30: - return MediaSize.Original; - default: - throw new IllegalArgumentException(); - } - } - - public boolean isBetterThan(MediaSize that) { - return mValue > that.mValue; - } - - public boolean isTemporary() { - return this == TemporaryThumbnail || this == TemporaryPreview; - } - } - - /** - * Returns the local File for the given Uri. If the image is not stored - * locally, null should be returned. The image should not be retrieved if it - * isn't already available. - * - * @param contentUri The media URI to search for. - * @return The local File of the image if it is available or null if it - * isn't. - */ - File getLocalFile(Uri contentUri); - - /** - * Returns the fast access image type for a given image size, if supported. - * This image should be smaller than size and should be quick to retrieve. - * It does not have to obey the expected aspect ratio. - * - * @param contentUri The original media Uri. - * @param size The target size to search for a fast-access image. - * @return The fast image type supported for the given image size or null of - * no fast image is supported. - */ - MediaSize getFastImageSize(Uri contentUri, MediaSize size); - - /** - * Returns a byte array containing the contents of the fast temporary image - * for a given image size. For example, a thumbnail may be smaller or of a - * different aspect ratio than the generated thumbnail. - * - * @param contentUri The original media Uri. - * @param temporarySize The target media size. Guaranteed to be a MediaSize - * for which isTemporary() returns true. - * @return A byte array of contents for for the given contentUri and - * fastImageType. null can be retrieved if the quick retrieval - * fails. - */ - byte[] getTemporaryImage(Uri contentUri, MediaSize temporarySize); - - /** - * Retrieves an image and saves it to a file. - * - * @param contentUri The original media Uri. - * @param size The target media size. - * @param tempFile The file to write the bitmap to. - * @return <code>true</code> on success. - */ - boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile); - - /** - * Normalizes a URI that may have additional parameters. It is fine to - * return contentUri. This is executed on the calling thread, so it must be - * a fast access operation and cannot depend, for example, on I/O. - * - * @param contentUri The URI to normalize - * @param size The size of the image being requested - * @return The normalized URI representation of contentUri. - */ - Uri normalizeUri(Uri contentUri, MediaSize size); - - /** - * Normalize the MediaSize for a given URI. Typically the size returned - * would be the passed-in size. Some URIs may only have one size used and - * should be treaded as Thumbnails, for example. This is executed on the - * calling thread, so it must be a fast access operation and cannot depend, - * for example, on I/O. - * - * @param contentUri The URI for the size being normalized. - * @param size The size to be normalized. - * @return The normalized size of the given URI. - */ - MediaSize normalizeMediaSize(Uri contentUri, MediaSize size); -} diff --git a/src/com/android/photos/data/NotificationWatcher.java b/src/com/android/photos/data/NotificationWatcher.java deleted file mode 100644 index 9041c236f..000000000 --- a/src/com/android/photos/data/NotificationWatcher.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.net.Uri; - -import com.android.photos.data.PhotoProvider.ChangeNotification; - -import java.util.ArrayList; - -/** - * Used for capturing notifications from PhotoProvider without relying on - * ContentResolver. MockContentResolver does not allow sending notification to - * ContentObservers, so PhotoProvider allows this alternative for testing. - */ -public class NotificationWatcher implements ChangeNotification { - private ArrayList<Uri> mUris = new ArrayList<Uri>(); - private boolean mSyncToNetwork = false; - - @Override - public void notifyChange(Uri uri, boolean syncToNetwork) { - mUris.add(uri); - mSyncToNetwork = mSyncToNetwork || syncToNetwork; - } - - public boolean isNotified(Uri uri) { - return mUris.contains(uri); - } - - public int notificationCount() { - return mUris.size(); - } - - public boolean syncToNetwork() { - return mSyncToNetwork; - } - - public void reset() { - mUris.clear(); - mSyncToNetwork = false; - } -} diff --git a/src/com/android/photos/data/PhotoDatabase.java b/src/com/android/photos/data/PhotoDatabase.java deleted file mode 100644 index 0c7b22730..000000000 --- a/src/com/android/photos/data/PhotoDatabase.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -import com.android.photos.data.PhotoProvider.Accounts; -import com.android.photos.data.PhotoProvider.Albums; -import com.android.photos.data.PhotoProvider.Metadata; -import com.android.photos.data.PhotoProvider.Photos; - -import java.util.ArrayList; -import java.util.List; - -/** - * Used in PhotoProvider to create and access the database containing - * information about photo and video information stored on the server. - */ -public class PhotoDatabase extends SQLiteOpenHelper { - @SuppressWarnings("unused") - private static final String TAG = PhotoDatabase.class.getSimpleName(); - static final int DB_VERSION = 3; - - private static final String SQL_CREATE_TABLE = "CREATE TABLE "; - - private static final String[][] CREATE_PHOTO = { - { Photos._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, - // Photos.ACCOUNT_ID is a foreign key to Accounts._ID - { Photos.ACCOUNT_ID, "INTEGER NOT NULL" }, - { Photos.WIDTH, "INTEGER NOT NULL" }, - { Photos.HEIGHT, "INTEGER NOT NULL" }, - { Photos.DATE_TAKEN, "INTEGER NOT NULL" }, - // Photos.ALBUM_ID is a foreign key to Albums._ID - { Photos.ALBUM_ID, "INTEGER" }, - { Photos.MIME_TYPE, "TEXT NOT NULL" }, - { Photos.TITLE, "TEXT" }, - { Photos.DATE_MODIFIED, "INTEGER" }, - { Photos.ROTATION, "INTEGER" }, - }; - - private static final String[][] CREATE_ALBUM = { - { Albums._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, - // Albums.ACCOUNT_ID is a foreign key to Accounts._ID - { Albums.ACCOUNT_ID, "INTEGER NOT NULL" }, - // Albums.PARENT_ID is a foreign key to Albums._ID - { Albums.PARENT_ID, "INTEGER" }, - { Albums.ALBUM_TYPE, "TEXT" }, - { Albums.VISIBILITY, "INTEGER NOT NULL" }, - { Albums.LOCATION_STRING, "TEXT" }, - { Albums.TITLE, "TEXT NOT NULL" }, - { Albums.SUMMARY, "TEXT" }, - { Albums.DATE_PUBLISHED, "INTEGER" }, - { Albums.DATE_MODIFIED, "INTEGER" }, - createUniqueConstraint(Albums.PARENT_ID, Albums.TITLE), - }; - - private static final String[][] CREATE_METADATA = { - { Metadata._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, - // Metadata.PHOTO_ID is a foreign key to Photos._ID - { Metadata.PHOTO_ID, "INTEGER NOT NULL" }, - { Metadata.KEY, "TEXT NOT NULL" }, - { Metadata.VALUE, "TEXT NOT NULL" }, - createUniqueConstraint(Metadata.PHOTO_ID, Metadata.KEY), - }; - - private static final String[][] CREATE_ACCOUNT = { - { Accounts._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, - { Accounts.ACCOUNT_NAME, "TEXT UNIQUE NOT NULL" }, - }; - - @Override - public void onCreate(SQLiteDatabase db) { - createTable(db, Accounts.TABLE, getAccountTableDefinition()); - createTable(db, Albums.TABLE, getAlbumTableDefinition()); - createTable(db, Photos.TABLE, getPhotoTableDefinition()); - createTable(db, Metadata.TABLE, getMetadataTableDefinition()); - } - - public PhotoDatabase(Context context, String dbName, int dbVersion) { - super(context, dbName, null, dbVersion); - } - - public PhotoDatabase(Context context, String dbName) { - super(context, dbName, null, DB_VERSION); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - recreate(db); - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - recreate(db); - } - - private void recreate(SQLiteDatabase db) { - dropTable(db, Metadata.TABLE); - dropTable(db, Photos.TABLE); - dropTable(db, Albums.TABLE); - dropTable(db, Accounts.TABLE); - onCreate(db); - } - - protected List<String[]> getAlbumTableDefinition() { - return tableCreationStrings(CREATE_ALBUM); - } - - protected List<String[]> getPhotoTableDefinition() { - return tableCreationStrings(CREATE_PHOTO); - } - - protected List<String[]> getMetadataTableDefinition() { - return tableCreationStrings(CREATE_METADATA); - } - - protected List<String[]> getAccountTableDefinition() { - return tableCreationStrings(CREATE_ACCOUNT); - } - - protected static void createTable(SQLiteDatabase db, String table, List<String[]> columns) { - StringBuilder create = new StringBuilder(SQL_CREATE_TABLE); - create.append(table).append('('); - boolean first = true; - for (String[] column : columns) { - if (!first) { - create.append(','); - } - first = false; - for (String val: column) { - create.append(val).append(' '); - } - } - create.append(')'); - db.beginTransaction(); - try { - db.execSQL(create.toString()); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - protected static String[] createUniqueConstraint(String column1, String column2) { - return new String[] { - "UNIQUE(", column1, ",", column2, ")" - }; - } - - protected static List<String[]> tableCreationStrings(String[][] createTable) { - ArrayList<String[]> create = new ArrayList<String[]>(createTable.length); - for (String[] line: createTable) { - create.add(line); - } - return create; - } - - protected static void addToTable(List<String[]> createTable, String[][] columns, String[][] constraints) { - if (columns != null) { - for (String[] column: columns) { - createTable.add(0, column); - } - } - if (constraints != null) { - for (String[] constraint: constraints) { - createTable.add(constraint); - } - } - } - - protected static void dropTable(SQLiteDatabase db, String table) { - db.beginTransaction(); - try { - db.execSQL("drop table if exists " + table); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } -} diff --git a/src/com/android/photos/data/PhotoProvider.java b/src/com/android/photos/data/PhotoProvider.java deleted file mode 100644 index d4310ca95..000000000 --- a/src/com/android/photos/data/PhotoProvider.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.media.ExifInterface; -import android.net.Uri; -import android.os.CancellationSignal; -import android.provider.BaseColumns; - -import com.android.gallery3d.common.ApiHelper; - -import java.util.List; - -/** - * A provider that gives access to photo and video information for media stored - * on the server. Only media that is or will be put on the server will be - * accessed by this provider. Use Photos.CONTENT_URI to query all photos and - * videos. Use Albums.CONTENT_URI to query all albums. Use Metadata.CONTENT_URI - * to query metadata about a photo or video, based on the ID of the media. Use - * ImageCache.THUMBNAIL_CONTENT_URI, ImageCache.PREVIEW_CONTENT_URI, or - * ImageCache.ORIGINAL_CONTENT_URI to query the path of the thumbnail, preview, - * or original-sized image respectfully. <br/> - * To add or update metadata, use the update function rather than insert. All - * values for the metadata must be in the ContentValues, even if they are also - * in the selection. The selection and selectionArgs are not used when updating - * metadata. If the metadata values are null, the row will be deleted. - */ -public class PhotoProvider extends SQLiteContentProvider { - @SuppressWarnings("unused") - private static final String TAG = PhotoProvider.class.getSimpleName(); - - protected static final String DB_NAME = "photo.db"; - public static final String AUTHORITY = PhotoProviderAuthority.AUTHORITY; - static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme("content").authority(AUTHORITY) - .build(); - - // Used to allow mocking out the change notification because - // MockContextResolver disallows system-wide notification. - public static interface ChangeNotification { - void notifyChange(Uri uri, boolean syncToNetwork); - } - - /** - * Contains columns that can be accessed via Accounts.CONTENT_URI - */ - public static interface Accounts extends BaseColumns { - /** - * Internal database table used for account information - */ - public static final String TABLE = "accounts"; - /** - * Content URI for account information - */ - public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); - /** - * User name for this account. - */ - public static final String ACCOUNT_NAME = "name"; - } - - /** - * Contains columns that can be accessed via Photos.CONTENT_URI. - */ - public static interface Photos extends BaseColumns { - /** - * The image_type query parameter required for requesting a specific - * size of image. - */ - public static final String MEDIA_SIZE_QUERY_PARAMETER = "media_size"; - - /** Internal database table used for basic photo information. */ - public static final String TABLE = "photos"; - /** Content URI for basic photo and video information. */ - public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); - - /** Long foreign key to Accounts._ID */ - public static final String ACCOUNT_ID = "account_id"; - /** Column name for the width of the original image. Integer value. */ - public static final String WIDTH = "width"; - /** Column name for the height of the original image. Integer value. */ - public static final String HEIGHT = "height"; - /** - * Column name for the date that the original image was taken. Long - * value indicating the milliseconds since epoch in the GMT time zone. - */ - public static final String DATE_TAKEN = "date_taken"; - /** - * Column name indicating the long value of the album id that this image - * resides in. Will be NULL if it it has not been uploaded to the - * server. - */ - public static final String ALBUM_ID = "album_id"; - /** The column name for the mime-type String. */ - public static final String MIME_TYPE = "mime_type"; - /** The title of the photo. String value. */ - public static final String TITLE = "title"; - /** The date the photo entry was last updated. Long value. */ - public static final String DATE_MODIFIED = "date_modified"; - /** - * The rotation of the photo in degrees, if rotation has not already - * been applied. Integer value. - */ - public static final String ROTATION = "rotation"; - } - - /** - * Contains columns and Uri for accessing album information. - */ - public static interface Albums extends BaseColumns { - /** Internal database table used album information. */ - public static final String TABLE = "albums"; - /** Content URI for album information. */ - public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); - - /** Long foreign key to Accounts._ID */ - public static final String ACCOUNT_ID = "account_id"; - /** Parent directory or null if this is in the root. */ - public static final String PARENT_ID = "parent_id"; - /** The type of album. Non-null, if album is auto-generated. String value. */ - public static final String ALBUM_TYPE = "album_type"; - /** - * Column name for the visibility level of the album. Can be any of the - * VISIBILITY_* values. - */ - public static final String VISIBILITY = "visibility"; - /** The user-specified location associated with the album. String value. */ - public static final String LOCATION_STRING = "location_string"; - /** The title of the album. String value. */ - public static final String TITLE = "title"; - /** A short summary of the contents of the album. String value. */ - public static final String SUMMARY = "summary"; - /** The date the album was created. Long value */ - public static final String DATE_PUBLISHED = "date_published"; - /** The date the album entry was last updated. Long value. */ - public static final String DATE_MODIFIED = "date_modified"; - - // Privacy values for Albums.VISIBILITY - public static final int VISIBILITY_PRIVATE = 1; - public static final int VISIBILITY_SHARED = 2; - public static final int VISIBILITY_PUBLIC = 3; - } - - /** - * Contains columns and Uri for accessing photo and video metadata - */ - public static interface Metadata extends BaseColumns { - /** Internal database table used metadata information. */ - public static final String TABLE = "metadata"; - /** Content URI for photo and video metadata. */ - public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); - /** Foreign key to photo_id. Long value. */ - public static final String PHOTO_ID = "photo_id"; - /** Metadata key. String value */ - public static final String KEY = "key"; - /** - * Metadata value. Type is based on key. - */ - public static final String VALUE = "value"; - - /** A short summary of the photo. String value. */ - public static final String KEY_SUMMARY = "summary"; - /** The date the photo was added. Long value. */ - public static final String KEY_PUBLISHED = "date_published"; - /** The date the photo was last updated. Long value. */ - public static final String KEY_DATE_UPDATED = "date_updated"; - /** The size of the photo is bytes. Integer value. */ - public static final String KEY_SIZE_IN_BTYES = "size"; - /** The latitude associated with the photo. Double value. */ - public static final String KEY_LATITUDE = "latitude"; - /** The longitude associated with the photo. Double value. */ - public static final String KEY_LONGITUDE = "longitude"; - - /** The make of the camera used. String value. */ - public static final String KEY_EXIF_MAKE = ExifInterface.TAG_MAKE; - /** The model of the camera used. String value. */ - public static final String KEY_EXIF_MODEL = ExifInterface.TAG_MODEL;; - /** The exposure time used. Float value. */ - public static final String KEY_EXIF_EXPOSURE = ExifInterface.TAG_EXPOSURE_TIME; - /** Whether the flash was used. Boolean value. */ - public static final String KEY_EXIF_FLASH = ExifInterface.TAG_FLASH; - /** The focal length used. Float value. */ - public static final String KEY_EXIF_FOCAL_LENGTH = ExifInterface.TAG_FOCAL_LENGTH; - /** The fstop value used. Float value. */ - public static final String KEY_EXIF_FSTOP = ExifInterface.TAG_APERTURE; - /** The ISO equivalent value used. Integer value. */ - public static final String KEY_EXIF_ISO = ExifInterface.TAG_ISO; - } - - // SQL used within this class. - protected static final String WHERE_ID = BaseColumns._ID + " = ?"; - protected static final String WHERE_METADATA_ID = Metadata.PHOTO_ID + " = ? AND " - + Metadata.KEY + " = ?"; - - protected static final String SELECT_ALBUM_ID = "SELECT " + Albums._ID + " FROM " - + Albums.TABLE; - protected static final String SELECT_PHOTO_ID = "SELECT " + Photos._ID + " FROM " - + Photos.TABLE; - protected static final String SELECT_PHOTO_COUNT = "SELECT COUNT(*) FROM " + Photos.TABLE; - protected static final String DELETE_PHOTOS = "DELETE FROM " + Photos.TABLE; - protected static final String DELETE_METADATA = "DELETE FROM " + Metadata.TABLE; - protected static final String SELECT_METADATA_COUNT = "SELECT COUNT(*) FROM " + Metadata.TABLE; - protected static final String WHERE = " WHERE "; - protected static final String IN = " IN "; - protected static final String NESTED_SELECT_START = "("; - protected static final String NESTED_SELECT_END = ")"; - protected static final String[] PROJECTION_COUNT = { - "COUNT(*)" - }; - - /** - * For selecting the mime-type for an image. - */ - private static final String[] PROJECTION_MIME_TYPE = { - Photos.MIME_TYPE, - }; - - protected static final String[] BASE_COLUMNS_ID = { - BaseColumns._ID, - }; - - protected ChangeNotification mNotifier = null; - protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - protected static final int MATCH_PHOTO = 1; - protected static final int MATCH_PHOTO_ID = 2; - protected static final int MATCH_ALBUM = 3; - protected static final int MATCH_ALBUM_ID = 4; - protected static final int MATCH_METADATA = 5; - protected static final int MATCH_METADATA_ID = 6; - protected static final int MATCH_ACCOUNT = 7; - protected static final int MATCH_ACCOUNT_ID = 8; - - static { - sUriMatcher.addURI(AUTHORITY, Photos.TABLE, MATCH_PHOTO); - // match against Photos._ID - sUriMatcher.addURI(AUTHORITY, Photos.TABLE + "/#", MATCH_PHOTO_ID); - sUriMatcher.addURI(AUTHORITY, Albums.TABLE, MATCH_ALBUM); - // match against Albums._ID - sUriMatcher.addURI(AUTHORITY, Albums.TABLE + "/#", MATCH_ALBUM_ID); - sUriMatcher.addURI(AUTHORITY, Metadata.TABLE, MATCH_METADATA); - // match against metadata/<Metadata._ID> - sUriMatcher.addURI(AUTHORITY, Metadata.TABLE + "/#", MATCH_METADATA_ID); - sUriMatcher.addURI(AUTHORITY, Accounts.TABLE, MATCH_ACCOUNT); - // match against Accounts._ID - sUriMatcher.addURI(AUTHORITY, Accounts.TABLE + "/#", MATCH_ACCOUNT_ID); - } - - @Override - public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, - boolean callerIsSyncAdapter) { - int match = matchUri(uri); - selection = addIdToSelection(match, selection); - selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs); - return deleteCascade(uri, match, selection, selectionArgs); - } - - @Override - public String getType(Uri uri) { - Cursor cursor = query(uri, PROJECTION_MIME_TYPE, null, null, null); - String mimeType = null; - if (cursor.moveToNext()) { - mimeType = cursor.getString(0); - } - cursor.close(); - return mimeType; - } - - @Override - public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) { - int match = matchUri(uri); - validateMatchTable(match); - String table = getTableFromMatch(match, uri); - SQLiteDatabase db = getDatabaseHelper().getWritableDatabase(); - Uri insertedUri = null; - long id = db.insert(table, null, values); - if (id != -1) { - // uri already matches the table. - insertedUri = ContentUris.withAppendedId(uri, id); - postNotifyUri(insertedUri); - } - return insertedUri; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - return query(uri, projection, selection, selectionArgs, sortOrder, null); - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder, CancellationSignal cancellationSignal) { - projection = replaceCount(projection); - int match = matchUri(uri); - selection = addIdToSelection(match, selection); - selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs); - String table = getTableFromMatch(match, uri); - Cursor c = query(table, projection, selection, selectionArgs, sortOrder, cancellationSignal); - if (c != null) { - c.setNotificationUri(getContext().getContentResolver(), uri); - } - return c; - } - - @Override - public int updateInTransaction(Uri uri, ContentValues values, String selection, - String[] selectionArgs, boolean callerIsSyncAdapter) { - int match = matchUri(uri); - int rowsUpdated = 0; - SQLiteDatabase db = getDatabaseHelper().getWritableDatabase(); - if (match == MATCH_METADATA) { - rowsUpdated = modifyMetadata(db, values); - } else { - selection = addIdToSelection(match, selection); - selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs); - String table = getTableFromMatch(match, uri); - rowsUpdated = db.update(table, values, selection, selectionArgs); - } - postNotifyUri(uri); - return rowsUpdated; - } - - public void setMockNotification(ChangeNotification notification) { - mNotifier = notification; - } - - protected static String addIdToSelection(int match, String selection) { - String where; - switch (match) { - case MATCH_PHOTO_ID: - case MATCH_ALBUM_ID: - case MATCH_METADATA_ID: - where = WHERE_ID; - break; - default: - return selection; - } - return DatabaseUtils.concatenateWhere(selection, where); - } - - protected static String[] addIdToSelectionArgs(int match, Uri uri, String[] selectionArgs) { - String[] whereArgs; - switch (match) { - case MATCH_PHOTO_ID: - case MATCH_ALBUM_ID: - case MATCH_METADATA_ID: - whereArgs = new String[] { - uri.getPathSegments().get(1), - }; - break; - default: - return selectionArgs; - } - return DatabaseUtils.appendSelectionArgs(selectionArgs, whereArgs); - } - - protected static String[] addMetadataKeysToSelectionArgs(String[] selectionArgs, Uri uri) { - List<String> segments = uri.getPathSegments(); - String[] additionalArgs = { - segments.get(1), - segments.get(2), - }; - - return DatabaseUtils.appendSelectionArgs(selectionArgs, additionalArgs); - } - - protected static String getTableFromMatch(int match, Uri uri) { - String table; - switch (match) { - case MATCH_PHOTO: - case MATCH_PHOTO_ID: - table = Photos.TABLE; - break; - case MATCH_ALBUM: - case MATCH_ALBUM_ID: - table = Albums.TABLE; - break; - case MATCH_METADATA: - case MATCH_METADATA_ID: - table = Metadata.TABLE; - break; - case MATCH_ACCOUNT: - case MATCH_ACCOUNT_ID: - table = Accounts.TABLE; - break; - default: - throw unknownUri(uri); - } - return table; - } - - @Override - public SQLiteOpenHelper getDatabaseHelper(Context context) { - return new PhotoDatabase(context, DB_NAME); - } - - private int modifyMetadata(SQLiteDatabase db, ContentValues values) { - int rowCount; - if (values.get(Metadata.VALUE) == null) { - String[] selectionArgs = { - values.getAsString(Metadata.PHOTO_ID), values.getAsString(Metadata.KEY), - }; - rowCount = db.delete(Metadata.TABLE, WHERE_METADATA_ID, selectionArgs); - } else { - long rowId = db.replace(Metadata.TABLE, null, values); - rowCount = (rowId == -1) ? 0 : 1; - } - return rowCount; - } - - private int matchUri(Uri uri) { - int match = sUriMatcher.match(uri); - if (match == UriMatcher.NO_MATCH) { - throw unknownUri(uri); - } - return match; - } - - @Override - protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) { - if (mNotifier != null) { - mNotifier.notifyChange(uri, syncToNetwork); - } else { - super.notifyChange(resolver, uri, syncToNetwork); - } - } - - protected static IllegalArgumentException unknownUri(Uri uri) { - return new IllegalArgumentException("Unknown Uri format: " + uri); - } - - protected static String nestWhere(String matchColumn, String table, String nestedWhere) { - String query = SQLiteQueryBuilder.buildQueryString(false, table, BASE_COLUMNS_ID, - nestedWhere, null, null, null, null); - return matchColumn + IN + NESTED_SELECT_START + query + NESTED_SELECT_END; - } - - protected static String metadataSelectionFromPhotos(String where) { - return nestWhere(Metadata.PHOTO_ID, Photos.TABLE, where); - } - - protected static String photoSelectionFromAlbums(String where) { - return nestWhere(Photos.ALBUM_ID, Albums.TABLE, where); - } - - protected static String photoSelectionFromAccounts(String where) { - return nestWhere(Photos.ACCOUNT_ID, Accounts.TABLE, where); - } - - protected static String albumSelectionFromAccounts(String where) { - return nestWhere(Albums.ACCOUNT_ID, Accounts.TABLE, where); - } - - protected int deleteCascade(Uri uri, int match, String selection, String[] selectionArgs) { - switch (match) { - case MATCH_PHOTO: - case MATCH_PHOTO_ID: - deleteCascade(Metadata.CONTENT_URI, MATCH_METADATA, - metadataSelectionFromPhotos(selection), selectionArgs); - break; - case MATCH_ALBUM: - case MATCH_ALBUM_ID: - deleteCascade(Photos.CONTENT_URI, MATCH_PHOTO, - photoSelectionFromAlbums(selection), selectionArgs); - break; - case MATCH_ACCOUNT: - case MATCH_ACCOUNT_ID: - deleteCascade(Photos.CONTENT_URI, MATCH_PHOTO, - photoSelectionFromAccounts(selection), selectionArgs); - deleteCascade(Albums.CONTENT_URI, MATCH_ALBUM, - albumSelectionFromAccounts(selection), selectionArgs); - break; - } - SQLiteDatabase db = getDatabaseHelper().getWritableDatabase(); - String table = getTableFromMatch(match, uri); - int deleted = db.delete(table, selection, selectionArgs); - if (deleted > 0) { - postNotifyUri(uri); - } - return deleted; - } - - private static void validateMatchTable(int match) { - switch (match) { - case MATCH_PHOTO: - case MATCH_ALBUM: - case MATCH_METADATA: - case MATCH_ACCOUNT: - break; - default: - throw new IllegalArgumentException("Operation not allowed on an existing row."); - } - } - - protected Cursor query(String table, String[] columns, String selection, - String[] selectionArgs, String orderBy, CancellationSignal cancellationSignal) { - SQLiteDatabase db = getDatabaseHelper().getReadableDatabase(); - if (ApiHelper.HAS_CANCELLATION_SIGNAL) { - return db.query(false, table, columns, selection, selectionArgs, null, null, - orderBy, null, cancellationSignal); - } else { - return db.query(table, columns, selection, selectionArgs, null, null, orderBy); - } - } - - protected static String[] replaceCount(String[] projection) { - if (projection != null && projection.length == 1 - && BaseColumns._COUNT.equals(projection[0])) { - return PROJECTION_COUNT; - } - return projection; - } -} diff --git a/src/com/android/photos/data/PhotoSetLoader.java b/src/com/android/photos/data/PhotoSetLoader.java deleted file mode 100644 index 56c82c4a9..000000000 --- a/src/com/android/photos/data/PhotoSetLoader.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.content.Context; -import android.content.CursorLoader; -import android.database.ContentObserver; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.provider.MediaStore; -import android.provider.MediaStore.Files; -import android.provider.MediaStore.Files.FileColumns; - -import com.android.photos.drawables.DataUriThumbnailDrawable; -import com.android.photos.shims.LoaderCompatShim; - -import java.util.ArrayList; - -public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cursor> { - - public static final String SUPPORTED_OPERATIONS = "supported_operations"; - - private static final Uri CONTENT_URI = Files.getContentUri("external"); - public static final String[] PROJECTION = new String[] { - FileColumns._ID, - FileColumns.DATA, - FileColumns.WIDTH, - FileColumns.HEIGHT, - FileColumns.DATE_ADDED, - FileColumns.MEDIA_TYPE, - SUPPORTED_OPERATIONS, - }; - - private static final String SORT_ORDER = FileColumns.DATE_ADDED + " DESC"; - private static final String SELECTION = - FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_IMAGE - + " OR " - + FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_VIDEO; - - public static final int INDEX_ID = 0; - public static final int INDEX_DATA = 1; - public static final int INDEX_WIDTH = 2; - public static final int INDEX_HEIGHT = 3; - public static final int INDEX_DATE_ADDED = 4; - public static final int INDEX_MEDIA_TYPE = 5; - public static final int INDEX_SUPPORTED_OPERATIONS = 6; - - private static final Uri GLOBAL_CONTENT_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/external/"); - private final ContentObserver mGlobalObserver = new ForceLoadContentObserver(); - - public PhotoSetLoader(Context context) { - super(context, CONTENT_URI, PROJECTION, SELECTION, null, SORT_ORDER); - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - getContext().getContentResolver().registerContentObserver(GLOBAL_CONTENT_URI, - true, mGlobalObserver); - } - - @Override - protected void onReset() { - super.onReset(); - getContext().getContentResolver().unregisterContentObserver(mGlobalObserver); - } - - @Override - public Drawable drawableForItem(Cursor item, Drawable recycle) { - DataUriThumbnailDrawable drawable = null; - if (recycle == null || !(recycle instanceof DataUriThumbnailDrawable)) { - drawable = new DataUriThumbnailDrawable(); - } else { - drawable = (DataUriThumbnailDrawable) recycle; - } - drawable.setImage(item.getString(INDEX_DATA), - item.getInt(INDEX_WIDTH), item.getInt(INDEX_HEIGHT)); - return drawable; - } - - @Override - public Uri uriForItem(Cursor item) { - return null; - } - - @Override - public ArrayList<Uri> urisForSubItems(Cursor item) { - return null; - } - - @Override - public void deleteItemWithPath(Object path) { - - } - - @Override - public Object getPathForItem(Cursor item) { - return null; - } -} diff --git a/src/com/android/photos/data/SQLiteContentProvider.java b/src/com/android/photos/data/SQLiteContentProvider.java deleted file mode 100644 index daffa6e79..000000000 --- a/src/com/android/photos/data/SQLiteContentProvider.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.content.ContentProvider; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.OperationApplicationException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -/** - * General purpose {@link ContentProvider} base class that uses SQLiteDatabase - * for storage. - */ -public abstract class SQLiteContentProvider extends ContentProvider { - - @SuppressWarnings("unused") - private static final String TAG = "SQLiteContentProvider"; - - private SQLiteOpenHelper mOpenHelper; - private Set<Uri> mChangedUris; - - private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>(); - private static final int SLEEP_AFTER_YIELD_DELAY = 4000; - - /** - * Maximum number of operations allowed in a batch between yield points. - */ - private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500; - - @Override - public boolean onCreate() { - Context context = getContext(); - mOpenHelper = getDatabaseHelper(context); - mChangedUris = new HashSet<Uri>(); - return true; - } - - @Override - public void shutdown() { - getDatabaseHelper().close(); - } - - /** - * Returns a {@link SQLiteOpenHelper} that can open the database. - */ - public abstract SQLiteOpenHelper getDatabaseHelper(Context context); - - /** - * The equivalent of the {@link #insert} method, but invoked within a - * transaction. - */ - public abstract Uri insertInTransaction(Uri uri, ContentValues values, - boolean callerIsSyncAdapter); - - /** - * The equivalent of the {@link #update} method, but invoked within a - * transaction. - */ - public abstract int updateInTransaction(Uri uri, ContentValues values, String selection, - String[] selectionArgs, boolean callerIsSyncAdapter); - - /** - * The equivalent of the {@link #delete} method, but invoked within a - * transaction. - */ - public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, - boolean callerIsSyncAdapter); - - /** - * Call this to add a URI to the list of URIs to be notified when the - * transaction is committed. - */ - protected void postNotifyUri(Uri uri) { - synchronized (mChangedUris) { - mChangedUris.add(uri); - } - } - - public boolean isCallerSyncAdapter(Uri uri) { - return false; - } - - public SQLiteOpenHelper getDatabaseHelper() { - return mOpenHelper; - } - - private boolean applyingBatch() { - return mApplyingBatch.get() != null && mApplyingBatch.get(); - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - Uri result = null; - boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); - boolean applyingBatch = applyingBatch(); - if (!applyingBatch) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - result = insertInTransaction(uri, values, callerIsSyncAdapter); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - onEndTransaction(callerIsSyncAdapter); - } else { - result = insertInTransaction(uri, values, callerIsSyncAdapter); - } - return result; - } - - @Override - public int bulkInsert(Uri uri, ContentValues[] values) { - int numValues = values.length; - boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - for (int i = 0; i < numValues; i++) { - @SuppressWarnings("unused") - Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter); - db.yieldIfContendedSafely(); - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - onEndTransaction(callerIsSyncAdapter); - return numValues; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - int count = 0; - boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); - boolean applyingBatch = applyingBatch(); - if (!applyingBatch) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - count = updateInTransaction(uri, values, selection, selectionArgs, - callerIsSyncAdapter); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - onEndTransaction(callerIsSyncAdapter); - } else { - count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter); - } - - return count; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - int count = 0; - boolean callerIsSyncAdapter = isCallerSyncAdapter(uri); - boolean applyingBatch = applyingBatch(); - if (!applyingBatch) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - onEndTransaction(callerIsSyncAdapter); - } else { - count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter); - } - return count; - } - - @Override - public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) - throws OperationApplicationException { - int ypCount = 0; - int opCount = 0; - boolean callerIsSyncAdapter = false; - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - mApplyingBatch.set(true); - final int numOperations = operations.size(); - final ContentProviderResult[] results = new ContentProviderResult[numOperations]; - for (int i = 0; i < numOperations; i++) { - if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) { - throw new OperationApplicationException( - "Too many content provider operations between yield points. " - + "The maximum number of operations per yield point is " - + MAX_OPERATIONS_PER_YIELD_POINT, ypCount); - } - final ContentProviderOperation operation = operations.get(i); - if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) { - callerIsSyncAdapter = true; - } - if (i > 0 && operation.isYieldAllowed()) { - opCount = 0; - if (db.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) { - ypCount++; - } - } - results[i] = operation.apply(this, results, i); - } - db.setTransactionSuccessful(); - return results; - } finally { - mApplyingBatch.set(false); - db.endTransaction(); - onEndTransaction(callerIsSyncAdapter); - } - } - - protected Set<Uri> onEndTransaction(boolean callerIsSyncAdapter) { - Set<Uri> changed; - synchronized (mChangedUris) { - changed = new HashSet<Uri>(mChangedUris); - mChangedUris.clear(); - } - ContentResolver resolver = getContext().getContentResolver(); - for (Uri uri : changed) { - boolean syncToNetwork = !callerIsSyncAdapter && syncToNetwork(uri); - notifyChange(resolver, uri, syncToNetwork); - } - return changed; - } - - protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) { - resolver.notifyChange(uri, null, syncToNetwork); - } - - protected boolean syncToNetwork(Uri uri) { - return false; - } -}
\ No newline at end of file diff --git a/src/com/android/photos/data/SparseArrayBitmapPool.java b/src/com/android/photos/data/SparseArrayBitmapPool.java deleted file mode 100644 index 95e10267b..000000000 --- a/src/com/android/photos/data/SparseArrayBitmapPool.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.data; - -import android.graphics.Bitmap; -import android.util.SparseArray; - -import android.util.Pools.Pool; -import android.util.Pools.SimplePool; - -/** - * Bitmap pool backed by a sparse array indexing linked lists of bitmaps - * sharing the same width. Performance will degrade if using this to store - * many bitmaps with the same width but many different heights. - */ -public class SparseArrayBitmapPool { - - private int mCapacityBytes; - private SparseArray<Node> mStore = new SparseArray<Node>(); - private int mSizeBytes = 0; - - private Pool<Node> mNodePool; - private Node mPoolNodesHead = null; - private Node mPoolNodesTail = null; - - protected static class Node { - Bitmap bitmap; - - // Each node is part of two doubly linked lists: - // - A pool-level list (accessed by mPoolNodesHead and mPoolNodesTail) - // that is used for FIFO eviction of nodes when the pool gets full. - // - A bucket-level list for each index of the sparse array, so that - // each index can store more than one item. - Node prevInBucket; - Node nextInBucket; - Node nextInPool; - Node prevInPool; - } - - /** - * @param capacityBytes Maximum capacity of the pool in bytes. - * @param nodePool Shared pool to use for recycling linked list nodes, or null. - */ - public SparseArrayBitmapPool(int capacityBytes, Pool<Node> nodePool) { - mCapacityBytes = capacityBytes; - if (nodePool == null) { - mNodePool = new SimplePool<Node>(32); - } else { - mNodePool = nodePool; - } - } - - /** - * Set the maximum capacity of the pool, and if necessary trim it down to size. - */ - public synchronized void setCapacity(int capacityBytes) { - mCapacityBytes = capacityBytes; - - // No-op unless current size exceeds the new capacity. - freeUpCapacity(0); - } - - private void freeUpCapacity(int bytesNeeded) { - int targetSize = mCapacityBytes - bytesNeeded; - // Repeatedly remove the oldest node until we have freed up at least bytesNeeded. - while (mPoolNodesTail != null && mSizeBytes > targetSize) { - unlinkAndRecycleNode(mPoolNodesTail, true); - } - } - - private void unlinkAndRecycleNode(Node n, boolean recycleBitmap) { - // Unlink the node from its sparse array bucket list. - if (n.prevInBucket != null) { - // This wasn't the head, update the previous node. - n.prevInBucket.nextInBucket = n.nextInBucket; - } else { - // This was the head of the bucket, replace it with the next node. - mStore.put(n.bitmap.getWidth(), n.nextInBucket); - } - if (n.nextInBucket != null) { - // This wasn't the tail, update the next node. - n.nextInBucket.prevInBucket = n.prevInBucket; - } - - // Unlink the node from the pool-wide list. - if (n.prevInPool != null) { - // This wasn't the head, update the previous node. - n.prevInPool.nextInPool = n.nextInPool; - } else { - // This was the head of the pool-wide list, update the head pointer. - mPoolNodesHead = n.nextInPool; - } - if (n.nextInPool != null) { - // This wasn't the tail, update the next node. - n.nextInPool.prevInPool = n.prevInPool; - } else { - // This was the tail, update the tail pointer. - mPoolNodesTail = n.prevInPool; - } - - // Recycle the node. - n.nextInBucket = null; - n.nextInPool = null; - n.prevInBucket = null; - n.prevInPool = null; - mSizeBytes -= n.bitmap.getByteCount(); - if (recycleBitmap) n.bitmap.recycle(); - n.bitmap = null; - mNodePool.release(n); - } - - /** - * @return Capacity of the pool in bytes. - */ - public synchronized int getCapacity() { - return mCapacityBytes; - } - - /** - * @return Total size in bytes of the bitmaps stored in the pool. - */ - public synchronized int getSize() { - return mSizeBytes; - } - - /** - * @return Bitmap from the pool with the desired height/width or null if none available. - */ - public synchronized Bitmap get(int width, int height) { - Node cur = mStore.get(width); - - // Traverse the list corresponding to the width bucket in the - // sparse array, and unlink and return the first bitmap that - // also has the correct height. - while (cur != null) { - if (cur.bitmap.getHeight() == height) { - Bitmap b = cur.bitmap; - unlinkAndRecycleNode(cur, false); - return b; - } - cur = cur.nextInBucket; - } - return null; - } - - /** - * Adds the given bitmap to the pool. - * @return Whether the bitmap was added to the pool. - */ - public synchronized boolean put(Bitmap b) { - if (b == null) { - return false; - } - - // Ensure there is enough room to contain the new bitmap. - int bytes = b.getByteCount(); - freeUpCapacity(bytes); - - Node newNode = mNodePool.acquire(); - if (newNode == null) { - newNode = new Node(); - } - newNode.bitmap = b; - - // We append to the head, and freeUpCapacity clears from the tail, - // resulting in FIFO eviction. - newNode.prevInBucket = null; - newNode.prevInPool = null; - newNode.nextInPool = mPoolNodesHead; - mPoolNodesHead = newNode; - - // Insert the node into its appropriate bucket based on width. - int key = b.getWidth(); - newNode.nextInBucket = mStore.get(key); - if (newNode.nextInBucket != null) { - // The bucket already had nodes, update the old head. - newNode.nextInBucket.prevInBucket = newNode; - } - mStore.put(key, newNode); - - if (newNode.nextInPool == null) { - // This is the only node in the list, update the tail pointer. - mPoolNodesTail = newNode; - } else { - newNode.nextInPool.prevInPool = newNode; - } - mSizeBytes += bytes; - return true; - } - - /** - * Empty the pool, recycling all the bitmaps currently in it. - */ - public synchronized void clear() { - // Clearing is equivalent to ensuring all the capacity is available. - freeUpCapacity(mCapacityBytes); - } -} diff --git a/src/com/android/photos/drawables/AutoThumbnailDrawable.java b/src/com/android/photos/drawables/AutoThumbnailDrawable.java deleted file mode 100644 index b51b6709f..000000000 --- a/src/com/android/photos/drawables/AutoThumbnailDrawable.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.drawables; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.Log; - -import com.android.photos.data.GalleryBitmapPool; - -import java.io.InputStream; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public abstract class AutoThumbnailDrawable<T> extends Drawable { - - private static final String TAG = "AutoThumbnailDrawable"; - - private static ExecutorService sThreadPool = Executors.newSingleThreadExecutor(); - private static GalleryBitmapPool sBitmapPool = GalleryBitmapPool.getInstance(); - private static byte[] sTempStorage = new byte[64 * 1024]; - - // UI thread only - private Paint mPaint = new Paint(); - private Matrix mDrawMatrix = new Matrix(); - - // Decoder thread only - private BitmapFactory.Options mOptions = new BitmapFactory.Options(); - - // Shared, guarded by mLock - private Object mLock = new Object(); - private Bitmap mBitmap; - protected T mData; - private boolean mIsQueued; - private int mImageWidth, mImageHeight; - private Rect mBounds = new Rect(); - private int mSampleSize = 1; - - public AutoThumbnailDrawable() { - mPaint.setAntiAlias(true); - mPaint.setFilterBitmap(true); - mDrawMatrix.reset(); - mOptions.inTempStorage = sTempStorage; - } - - protected abstract byte[] getPreferredImageBytes(T data); - protected abstract InputStream getFallbackImageStream(T data); - protected abstract boolean dataChangedLocked(T data); - - public void setImage(T data, int width, int height) { - if (!dataChangedLocked(data)) return; - synchronized (mLock) { - mImageWidth = width; - mImageHeight = height; - mData = data; - setBitmapLocked(null); - refreshSampleSizeLocked(); - } - invalidateSelf(); - } - - private void setBitmapLocked(Bitmap b) { - if (b == mBitmap) { - return; - } - if (mBitmap != null) { - sBitmapPool.put(mBitmap); - } - mBitmap = b; - } - - @Override - protected void onBoundsChange(Rect bounds) { - super.onBoundsChange(bounds); - synchronized (mLock) { - mBounds.set(bounds); - if (mBounds.isEmpty()) { - mBitmap = null; - } else { - refreshSampleSizeLocked(); - updateDrawMatrixLocked(); - } - } - invalidateSelf(); - } - - @Override - public void draw(Canvas canvas) { - if (mBitmap != null) { - canvas.save(); - canvas.clipRect(mBounds); - canvas.concat(mDrawMatrix); - canvas.drawBitmap(mBitmap, 0, 0, mPaint); - canvas.restore(); - } else { - // TODO: Draw placeholder...? - } - } - - private void updateDrawMatrixLocked() { - if (mBitmap == null || mBounds.isEmpty()) { - mDrawMatrix.reset(); - return; - } - - float scale; - float dx = 0, dy = 0; - - int dwidth = mBitmap.getWidth(); - int dheight = mBitmap.getHeight(); - int vwidth = mBounds.width(); - int vheight = mBounds.height(); - - // Calculates a matrix similar to ScaleType.CENTER_CROP - if (dwidth * vheight > vwidth * dheight) { - scale = (float) vheight / (float) dheight; - dx = (vwidth - dwidth * scale) * 0.5f; - } else { - scale = (float) vwidth / (float) dwidth; - dy = (vheight - dheight * scale) * 0.5f; - } - if (scale < .8f) { - Log.w(TAG, "sample size was too small! Overdrawing! " + scale + ", " + mSampleSize); - } else if (scale > 1.5f) { - Log.w(TAG, "Potential quality loss! " + scale + ", " + mSampleSize); - } - - mDrawMatrix.setScale(scale, scale); - mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); - } - - private int calculateSampleSizeLocked(int dwidth, int dheight) { - float scale; - - int vwidth = mBounds.width(); - int vheight = mBounds.height(); - - // Inverse of updateDrawMatrixLocked - if (dwidth * vheight > vwidth * dheight) { - scale = (float) dheight / (float) vheight; - } else { - scale = (float) dwidth / (float) vwidth; - } - int result = Math.round(scale); - return result > 0 ? result : 1; - } - - private void refreshSampleSizeLocked() { - if (mBounds.isEmpty() || mImageWidth == 0 || mImageHeight == 0) { - return; - } - - int sampleSize = calculateSampleSizeLocked(mImageWidth, mImageHeight); - if (sampleSize != mSampleSize || mBitmap == null) { - mSampleSize = sampleSize; - loadBitmapLocked(); - } - } - - private void loadBitmapLocked() { - if (!mIsQueued && !mBounds.isEmpty()) { - unscheduleSelf(mUpdateBitmap); - sThreadPool.execute(mLoadBitmap); - mIsQueued = true; - } - } - - public float getAspectRatio() { - return (float) mImageWidth / (float) mImageHeight; - } - - @Override - public int getIntrinsicWidth() { - return -1; - } - - @Override - public int getIntrinsicHeight() { - return -1; - } - - @Override - public int getOpacity() { - Bitmap bm = mBitmap; - return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ? - PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; - } - - @Override - public void setAlpha(int alpha) { - int oldAlpha = mPaint.getAlpha(); - if (alpha != oldAlpha) { - mPaint.setAlpha(alpha); - invalidateSelf(); - } - } - - @Override - public void setColorFilter(ColorFilter cf) { - mPaint.setColorFilter(cf); - invalidateSelf(); - } - - private final Runnable mLoadBitmap = new Runnable() { - @Override - public void run() { - T data; - synchronized (mLock) { - data = mData; - } - int preferredSampleSize = 1; - byte[] preferred = getPreferredImageBytes(data); - boolean hasPreferred = (preferred != null && preferred.length > 0); - if (hasPreferred) { - mOptions.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions); - mOptions.inJustDecodeBounds = false; - } - int sampleSize, width, height; - synchronized (mLock) { - if (dataChangedLocked(data)) { - return; - } - width = mImageWidth; - height = mImageHeight; - if (hasPreferred) { - preferredSampleSize = calculateSampleSizeLocked( - mOptions.outWidth, mOptions.outHeight); - } - sampleSize = calculateSampleSizeLocked(width, height); - mIsQueued = false; - } - Bitmap b = null; - InputStream is = null; - try { - if (hasPreferred) { - mOptions.inSampleSize = preferredSampleSize; - mOptions.inBitmap = sBitmapPool.get( - mOptions.outWidth / preferredSampleSize, - mOptions.outHeight / preferredSampleSize); - b = BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions); - if (mOptions.inBitmap != null && b != mOptions.inBitmap) { - sBitmapPool.put(mOptions.inBitmap); - mOptions.inBitmap = null; - } - } - if (b == null) { - is = getFallbackImageStream(data); - mOptions.inSampleSize = sampleSize; - mOptions.inBitmap = sBitmapPool.get(width / sampleSize, height / sampleSize); - b = BitmapFactory.decodeStream(is, null, mOptions); - if (mOptions.inBitmap != null && b != mOptions.inBitmap) { - sBitmapPool.put(mOptions.inBitmap); - mOptions.inBitmap = null; - } - } - } catch (Exception e) { - Log.d(TAG, "Failed to fetch bitmap", e); - return; - } finally { - try { - if (is != null) { - is.close(); - } - } catch (Exception e) {} - if (b != null) { - synchronized (mLock) { - if (!dataChangedLocked(data)) { - setBitmapLocked(b); - scheduleSelf(mUpdateBitmap, 0); - } - } - } - } - } - }; - - private final Runnable mUpdateBitmap = new Runnable() { - @Override - public void run() { - synchronized (AutoThumbnailDrawable.this) { - updateDrawMatrixLocked(); - invalidateSelf(); - } - } - }; - -} diff --git a/src/com/android/photos/drawables/DataUriThumbnailDrawable.java b/src/com/android/photos/drawables/DataUriThumbnailDrawable.java deleted file mode 100644 index c83b0c8fa..000000000 --- a/src/com/android/photos/drawables/DataUriThumbnailDrawable.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.drawables; - -import android.media.ExifInterface; -import android.text.TextUtils; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -public class DataUriThumbnailDrawable extends AutoThumbnailDrawable<String> { - - @Override - protected byte[] getPreferredImageBytes(String data) { - byte[] thumbnail = null; - try { - ExifInterface exif = new ExifInterface(data); - if (exif.hasThumbnail()) { - thumbnail = exif.getThumbnail(); - } - } catch (IOException e) { } - return thumbnail; - } - - @Override - protected InputStream getFallbackImageStream(String data) { - try { - return new FileInputStream(data); - } catch (FileNotFoundException e) { - return null; - } - } - - @Override - protected boolean dataChangedLocked(String data) { - return !TextUtils.equals(mData, data); - } -} diff --git a/src/com/android/photos/shims/BitmapJobDrawable.java b/src/com/android/photos/shims/BitmapJobDrawable.java deleted file mode 100644 index 32dbc8078..000000000 --- a/src/com/android/photos/shims/BitmapJobDrawable.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.shims; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; - -import com.android.gallery3d.data.MediaItem; -import com.android.gallery3d.ui.BitmapLoader; -import com.android.gallery3d.util.Future; -import com.android.gallery3d.util.FutureListener; -import com.android.gallery3d.util.ThreadPool; -import com.android.photos.data.GalleryBitmapPool; - - -public class BitmapJobDrawable extends Drawable implements Runnable { - - private ThumbnailLoader mLoader; - private MediaItem mItem; - private Bitmap mBitmap; - private Paint mPaint = new Paint(); - private Matrix mDrawMatrix = new Matrix(); - private int mRotation = 0; - - public BitmapJobDrawable() { - } - - public void setMediaItem(MediaItem item) { - if (mItem == item) return; - - if (mLoader != null) { - mLoader.cancelLoad(); - } - mItem = item; - if (mBitmap != null) { - GalleryBitmapPool.getInstance().put(mBitmap); - mBitmap = null; - } - if (mItem != null) { - // TODO: Figure out why ThumbnailLoader doesn't like to be re-used - mLoader = new ThumbnailLoader(this); - mLoader.startLoad(); - mRotation = mItem.getRotation(); - } - invalidateSelf(); - } - - @Override - public void run() { - Bitmap bitmap = mLoader.getBitmap(); - if (bitmap != null) { - mBitmap = bitmap; - updateDrawMatrix(); - } - } - - @Override - protected void onBoundsChange(Rect bounds) { - super.onBoundsChange(bounds); - updateDrawMatrix(); - } - - @Override - public void draw(Canvas canvas) { - Rect bounds = getBounds(); - if (mBitmap != null) { - canvas.save(); - canvas.clipRect(bounds); - canvas.concat(mDrawMatrix); - canvas.rotate(mRotation, bounds.centerX(), bounds.centerY()); - canvas.drawBitmap(mBitmap, 0, 0, mPaint); - canvas.restore(); - } else { - mPaint.setColor(0xFFCCCCCC); - canvas.drawRect(bounds, mPaint); - } - } - - private void updateDrawMatrix() { - Rect bounds = getBounds(); - if (mBitmap == null || bounds.isEmpty()) { - mDrawMatrix.reset(); - return; - } - - float scale; - float dx = 0, dy = 0; - - int dwidth = mBitmap.getWidth(); - int dheight = mBitmap.getHeight(); - int vwidth = bounds.width(); - int vheight = bounds.height(); - - // Calculates a matrix similar to ScaleType.CENTER_CROP - if (dwidth * vheight > vwidth * dheight) { - scale = (float) vheight / (float) dheight; - dx = (vwidth - dwidth * scale) * 0.5f; - } else { - scale = (float) vwidth / (float) dwidth; - dy = (vheight - dheight * scale) * 0.5f; - } - - mDrawMatrix.setScale(scale, scale); - mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); - invalidateSelf(); - } - - @Override - public int getIntrinsicWidth() { - return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL); - } - - @Override - public int getIntrinsicHeight() { - return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL); - } - - @Override - public int getOpacity() { - Bitmap bm = mBitmap; - return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ? - PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; - } - - @Override - public void setAlpha(int alpha) { - int oldAlpha = mPaint.getAlpha(); - if (alpha != oldAlpha) { - mPaint.setAlpha(alpha); - invalidateSelf(); - } - } - - @Override - public void setColorFilter(ColorFilter cf) { - mPaint.setColorFilter(cf); - invalidateSelf(); - } - - private static class ThumbnailLoader extends BitmapLoader { - private static final ThreadPool sThreadPool = new ThreadPool(0, 2); - private BitmapJobDrawable mParent; - - public ThumbnailLoader(BitmapJobDrawable parent) { - mParent = parent; - } - - @Override - protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) { - return sThreadPool.submit( - mParent.mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this); - } - - @Override - protected void onLoadComplete(Bitmap bitmap) { - mParent.scheduleSelf(mParent, 0); - } - } - -} diff --git a/src/com/android/photos/shims/LoaderCompatShim.java b/src/com/android/photos/shims/LoaderCompatShim.java deleted file mode 100644 index d5bf710de..000000000 --- a/src/com/android/photos/shims/LoaderCompatShim.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.shims; - -import android.graphics.drawable.Drawable; -import android.net.Uri; - -import java.util.ArrayList; - - -public interface LoaderCompatShim<T> { - Drawable drawableForItem(T item, Drawable recycle); - Uri uriForItem(T item); - ArrayList<Uri> urisForSubItems(T item); - void deleteItemWithPath(Object path); - Object getPathForItem(T item); -} diff --git a/src/com/android/photos/shims/MediaItemsLoader.java b/src/com/android/photos/shims/MediaItemsLoader.java deleted file mode 100644 index 6142355a9..000000000 --- a/src/com/android/photos/shims/MediaItemsLoader.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.shims; - -import android.content.AsyncTaskLoader; -import android.content.Context; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.provider.MediaStore.Files.FileColumns; -import android.util.SparseArray; - -import com.android.gallery3d.data.ContentListener; -import com.android.gallery3d.data.DataManager; -import com.android.gallery3d.data.MediaItem; -import com.android.gallery3d.data.MediaObject; -import com.android.gallery3d.data.MediaSet; -import com.android.gallery3d.data.MediaSet.ItemConsumer; -import com.android.gallery3d.data.MediaSet.SyncListener; -import com.android.gallery3d.data.Path; -import com.android.gallery3d.util.Future; -import com.android.photos.data.PhotoSetLoader; - -import java.util.ArrayList; - -/** - * Returns all MediaItems in a MediaSet, wrapping them in a cursor to appear - * like a PhotoSetLoader - */ -public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderCompatShim<Cursor> { - - private static final SyncListener sNullListener = new SyncListener() { - @Override - public void onSyncDone(MediaSet mediaSet, int resultCode) { - } - }; - - private final MediaSet mMediaSet; - private final DataManager mDataManager; - private Future<Integer> mSyncTask = null; - private ContentListener mObserver = new ContentListener() { - @Override - public void onContentDirty() { - onContentChanged(); - } - }; - private SparseArray<MediaItem> mMediaItems; - - public MediaItemsLoader(Context context) { - super(context); - mDataManager = DataManager.from(context); - String path = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL); - mMediaSet = mDataManager.getMediaSet(path); - } - - public MediaItemsLoader(Context context, String parentPath) { - super(context); - mDataManager = DataManager.from(getContext()); - mMediaSet = mDataManager.getMediaSet(parentPath); - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - mMediaSet.addContentListener(mObserver); - mSyncTask = mMediaSet.requestSync(sNullListener); - forceLoad(); - } - - @Override - protected boolean onCancelLoad() { - if (mSyncTask != null) { - mSyncTask.cancel(); - mSyncTask = null; - } - return super.onCancelLoad(); - } - - @Override - protected void onStopLoading() { - super.onStopLoading(); - cancelLoad(); - mMediaSet.removeContentListener(mObserver); - } - - @Override - protected void onReset() { - super.onReset(); - onStopLoading(); - } - - @Override - public Cursor loadInBackground() { - // TODO: This probably doesn't work - mMediaSet.reload(); - final MatrixCursor cursor = new MatrixCursor(PhotoSetLoader.PROJECTION); - final Object[] row = new Object[PhotoSetLoader.PROJECTION.length]; - final SparseArray<MediaItem> mediaItems = new SparseArray<MediaItem>(); - mMediaSet.enumerateTotalMediaItems(new ItemConsumer() { - @Override - public void consume(int index, MediaItem item) { - row[PhotoSetLoader.INDEX_ID] = index; - row[PhotoSetLoader.INDEX_DATA] = item.getContentUri().toString(); - row[PhotoSetLoader.INDEX_DATE_ADDED] = item.getDateInMs(); - row[PhotoSetLoader.INDEX_HEIGHT] = item.getHeight(); - row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth(); - row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth(); - int rawMediaType = item.getMediaType(); - int mappedMediaType = FileColumns.MEDIA_TYPE_NONE; - if (rawMediaType == MediaItem.MEDIA_TYPE_IMAGE) { - mappedMediaType = FileColumns.MEDIA_TYPE_IMAGE; - } else if (rawMediaType == MediaItem.MEDIA_TYPE_VIDEO) { - mappedMediaType = FileColumns.MEDIA_TYPE_VIDEO; - } - row[PhotoSetLoader.INDEX_MEDIA_TYPE] = mappedMediaType; - row[PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS] = - item.getSupportedOperations(); - cursor.addRow(row); - mediaItems.append(index, item); - } - }); - synchronized (mMediaSet) { - mMediaItems = mediaItems; - } - return cursor; - } - - @Override - public Drawable drawableForItem(Cursor item, Drawable recycle) { - BitmapJobDrawable drawable = null; - if (recycle == null || !(recycle instanceof BitmapJobDrawable)) { - drawable = new BitmapJobDrawable(); - } else { - drawable = (BitmapJobDrawable) recycle; - } - int index = item.getInt(PhotoSetLoader.INDEX_ID); - drawable.setMediaItem(mMediaItems.get(index)); - return drawable; - } - - public static int getThumbnailSize() { - return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL); - } - - @Override - public Uri uriForItem(Cursor item) { - int index = item.getInt(PhotoSetLoader.INDEX_ID); - MediaItem mi = mMediaItems.get(index); - return mi == null ? null : mi.getContentUri(); - } - - @Override - public ArrayList<Uri> urisForSubItems(Cursor item) { - return null; - } - - @Override - public void deleteItemWithPath(Object path) { - MediaObject o = mDataManager.getMediaObject((Path) path); - if (o != null) { - o.delete(); - } - } - - @Override - public Object getPathForItem(Cursor item) { - int index = item.getInt(PhotoSetLoader.INDEX_ID); - MediaItem mi = mMediaItems.get(index); - if (mi != null) { - return mi.getPath(); - } - return null; - } - -} diff --git a/src/com/android/photos/shims/MediaSetLoader.java b/src/com/android/photos/shims/MediaSetLoader.java deleted file mode 100644 index 9093bc139..000000000 --- a/src/com/android/photos/shims/MediaSetLoader.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.shims; - -import android.content.AsyncTaskLoader; -import android.content.Context; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; - -import com.android.gallery3d.data.ContentListener; -import com.android.gallery3d.data.DataManager; -import com.android.gallery3d.data.MediaItem; -import com.android.gallery3d.data.MediaObject; -import com.android.gallery3d.data.MediaSet; -import com.android.gallery3d.data.Path; -import com.android.gallery3d.data.MediaSet.SyncListener; -import com.android.gallery3d.util.Future; -import com.android.photos.data.AlbumSetLoader; - -import java.util.ArrayList; - -/** - * Returns all MediaSets in a MediaSet, wrapping them in a cursor to appear - * like a AlbumSetLoader. - */ -public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCompatShim<Cursor>{ - - private static final SyncListener sNullListener = new SyncListener() { - @Override - public void onSyncDone(MediaSet mediaSet, int resultCode) { - } - }; - - private final MediaSet mMediaSet; - private final DataManager mDataManager; - private Future<Integer> mSyncTask = null; - private ContentListener mObserver = new ContentListener() { - @Override - public void onContentDirty() { - onContentChanged(); - } - }; - - private ArrayList<MediaItem> mCoverItems; - - public MediaSetLoader(Context context) { - super(context); - mDataManager = DataManager.from(context); - String path = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL); - mMediaSet = mDataManager.getMediaSet(path); - } - - public MediaSetLoader(Context context, String path) { - super(context); - mDataManager = DataManager.from(getContext()); - mMediaSet = mDataManager.getMediaSet(path); - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - mMediaSet.addContentListener(mObserver); - mSyncTask = mMediaSet.requestSync(sNullListener); - forceLoad(); - } - - @Override - protected boolean onCancelLoad() { - if (mSyncTask != null) { - mSyncTask.cancel(); - mSyncTask = null; - } - return super.onCancelLoad(); - } - - @Override - protected void onStopLoading() { - super.onStopLoading(); - cancelLoad(); - mMediaSet.removeContentListener(mObserver); - } - - @Override - protected void onReset() { - super.onReset(); - onStopLoading(); - } - - @Override - public Cursor loadInBackground() { - // TODO: This probably doesn't work - mMediaSet.reload(); - final MatrixCursor cursor = new MatrixCursor(AlbumSetLoader.PROJECTION); - final Object[] row = new Object[AlbumSetLoader.PROJECTION.length]; - int count = mMediaSet.getSubMediaSetCount(); - ArrayList<MediaItem> coverItems = new ArrayList<MediaItem>(count); - for (int i = 0; i < count; i++) { - MediaSet m = mMediaSet.getSubMediaSet(i); - m.reload(); - row[AlbumSetLoader.INDEX_ID] = i; - row[AlbumSetLoader.INDEX_TITLE] = m.getName(); - row[AlbumSetLoader.INDEX_COUNT] = m.getMediaItemCount(); - row[AlbumSetLoader.INDEX_SUPPORTED_OPERATIONS] = m.getSupportedOperations(); - MediaItem coverItem = m.getCoverMediaItem(); - if (coverItem != null) { - row[AlbumSetLoader.INDEX_TIMESTAMP] = coverItem.getDateInMs(); - } - coverItems.add(coverItem); - cursor.addRow(row); - } - synchronized (mMediaSet) { - mCoverItems = coverItems; - } - return cursor; - } - - @Override - public Drawable drawableForItem(Cursor item, Drawable recycle) { - BitmapJobDrawable drawable = null; - if (recycle == null || !(recycle instanceof BitmapJobDrawable)) { - drawable = new BitmapJobDrawable(); - } else { - drawable = (BitmapJobDrawable) recycle; - } - int index = item.getInt(AlbumSetLoader.INDEX_ID); - drawable.setMediaItem(mCoverItems.get(index)); - return drawable; - } - - public static int getThumbnailSize() { - return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL); - } - - @Override - public Uri uriForItem(Cursor item) { - int index = item.getInt(AlbumSetLoader.INDEX_ID); - MediaSet ms = mMediaSet.getSubMediaSet(index); - return ms == null ? null : ms.getContentUri(); - } - - @Override - public ArrayList<Uri> urisForSubItems(Cursor item) { - int index = item.getInt(AlbumSetLoader.INDEX_ID); - MediaSet ms = mMediaSet.getSubMediaSet(index); - if (ms == null) return null; - final ArrayList<Uri> result = new ArrayList<Uri>(); - ms.enumerateMediaItems(new MediaSet.ItemConsumer() { - @Override - public void consume(int index, MediaItem item) { - if (item != null) { - result.add(item.getContentUri()); - } - } - }); - return result; - } - - @Override - public void deleteItemWithPath(Object path) { - MediaObject o = mDataManager.getMediaObject((Path) path); - if (o != null) { - o.delete(); - } - } - - @Override - public Object getPathForItem(Cursor item) { - int index = item.getInt(AlbumSetLoader.INDEX_ID); - MediaSet ms = mMediaSet.getSubMediaSet(index); - if (ms != null) { - return ms.getPath(); - } - return null; - } -} diff --git a/src/com/android/photos/views/BlockingGLTextureView.java b/src/com/android/photos/views/BlockingGLTextureView.java deleted file mode 100644 index 8a0505185..000000000 --- a/src/com/android/photos/views/BlockingGLTextureView.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.views; - -import android.content.Context; -import android.graphics.SurfaceTexture; -import android.opengl.GLSurfaceView.Renderer; -import android.opengl.GLUtils; -import android.util.Log; -import android.view.TextureView; -import android.view.TextureView.SurfaceTextureListener; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL10; - -/** - * A TextureView that supports blocking rendering for synchronous drawing - */ -public class BlockingGLTextureView extends TextureView - implements SurfaceTextureListener { - - private RenderThread mRenderThread; - - public BlockingGLTextureView(Context context) { - super(context); - setSurfaceTextureListener(this); - } - - public void setRenderer(Renderer renderer) { - if (mRenderThread != null) { - throw new IllegalArgumentException("Renderer already set"); - } - mRenderThread = new RenderThread(renderer); - } - - public void render() { - mRenderThread.render(); - } - - public void destroy() { - if (mRenderThread != null) { - mRenderThread.finish(); - mRenderThread = null; - } - } - - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, - int height) { - mRenderThread.setSurface(surface); - mRenderThread.setSize(width, height); - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, - int height) { - mRenderThread.setSize(width, height); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - if (mRenderThread != null) { - mRenderThread.setSurface(null); - } - return false; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - } - - @Override - protected void finalize() throws Throwable { - try { - destroy(); - } catch (Throwable t) { - // Ignore - } - super.finalize(); - } - - /** - * An EGL helper class. - */ - - private static class EglHelper { - private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - private static final int EGL_OPENGL_ES2_BIT = 4; - - EGL10 mEgl; - EGLDisplay mEglDisplay; - EGLSurface mEglSurface; - EGLConfig mEglConfig; - EGLContext mEglContext; - - private EGLConfig chooseEglConfig() { - int[] configsCount = new int[1]; - EGLConfig[] configs = new EGLConfig[1]; - int[] configSpec = getConfig(); - if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { - throw new IllegalArgumentException("eglChooseConfig failed " + - GLUtils.getEGLErrorString(mEgl.eglGetError())); - } else if (configsCount[0] > 0) { - return configs[0]; - } - return null; - } - - private static int[] getConfig() { - return new int[] { - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 8, - EGL10.EGL_DEPTH_SIZE, 0, - EGL10.EGL_STENCIL_SIZE, 0, - EGL10.EGL_NONE - }; - } - - EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { - int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; - return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList); - } - - /** - * Initialize EGL for a given configuration spec. - */ - public void start() { - /* - * Get an EGL instance - */ - mEgl = (EGL10) EGLContext.getEGL(); - - /* - * Get to the default display. - */ - mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { - throw new RuntimeException("eglGetDisplay failed"); - } - - /* - * We can now initialize EGL for that display - */ - int[] version = new int[2]; - if (!mEgl.eglInitialize(mEglDisplay, version)) { - throw new RuntimeException("eglInitialize failed"); - } - mEglConfig = chooseEglConfig(); - - /* - * Create an EGL context. We want to do this as rarely as we can, because an - * EGL context is a somewhat heavy object. - */ - mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); - - if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { - mEglContext = null; - throwEglException("createContext"); - } - - mEglSurface = null; - } - - /** - * Create an egl surface for the current SurfaceTexture surface. If a surface - * already exists, destroy it before creating the new surface. - * - * @return true if the surface was created successfully. - */ - public boolean createSurface(SurfaceTexture surface) { - /* - * Check preconditions. - */ - if (mEgl == null) { - throw new RuntimeException("egl not initialized"); - } - if (mEglDisplay == null) { - throw new RuntimeException("eglDisplay not initialized"); - } - if (mEglConfig == null) { - throw new RuntimeException("mEglConfig not initialized"); - } - - /* - * The window size has changed, so we need to create a new - * surface. - */ - destroySurfaceImp(); - - /* - * Create an EGL surface we can render into. - */ - if (surface != null) { - mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null); - } else { - mEglSurface = null; - } - - if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { - int error = mEgl.eglGetError(); - if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { - Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); - } - return false; - } - - /* - * Before we can issue GL commands, we need to make sure - * the context is current and bound to a surface. - */ - if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - /* - * Could not make the context current, probably because the underlying - * SurfaceView surface has been destroyed. - */ - logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); - return false; - } - - return true; - } - - /** - * Create a GL object for the current EGL context. - */ - public GL10 createGL() { - return (GL10) mEglContext.getGL(); - } - - /** - * Display the current render surface. - * @return the EGL error code from eglSwapBuffers. - */ - public int swap() { - if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { - return mEgl.eglGetError(); - } - return EGL10.EGL_SUCCESS; - } - - public void destroySurface() { - destroySurfaceImp(); - } - - private void destroySurfaceImp() { - if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { - mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_CONTEXT); - mEgl.eglDestroySurface(mEglDisplay, mEglSurface); - mEglSurface = null; - } - } - - public void finish() { - if (mEglContext != null) { - mEgl.eglDestroyContext(mEglDisplay, mEglContext); - mEglContext = null; - } - if (mEglDisplay != null) { - mEgl.eglTerminate(mEglDisplay); - mEglDisplay = null; - } - } - - private void throwEglException(String function) { - throwEglException(function, mEgl.eglGetError()); - } - - public static void throwEglException(String function, int error) { - String message = formatEglError(function, error); - throw new RuntimeException(message); - } - - public static void logEglErrorAsWarning(String tag, String function, int error) { - Log.w(tag, formatEglError(function, error)); - } - - public static String formatEglError(String function, int error) { - return function + " failed: " + error; - } - - } - - private static class RenderThread extends Thread { - private static final int INVALID = -1; - private static final int RENDER = 1; - private static final int CHANGE_SURFACE = 2; - private static final int RESIZE_SURFACE = 3; - private static final int FINISH = 4; - - private EglHelper mEglHelper = new EglHelper(); - - private Object mLock = new Object(); - private int mExecMsgId = INVALID; - private SurfaceTexture mSurface; - private Renderer mRenderer; - private int mWidth, mHeight; - - private boolean mFinished = false; - private GL10 mGL; - - public RenderThread(Renderer renderer) { - super("RenderThread"); - mRenderer = renderer; - start(); - } - - private void checkRenderer() { - if (mRenderer == null) { - throw new IllegalArgumentException("Renderer is null!"); - } - } - - private void checkSurface() { - if (mSurface == null) { - throw new IllegalArgumentException("surface is null!"); - } - } - - public void setSurface(SurfaceTexture surface) { - // If the surface is null we're being torn down, don't need a - // renderer then - if (surface != null) { - checkRenderer(); - } - mSurface = surface; - exec(CHANGE_SURFACE); - } - - public void setSize(int width, int height) { - checkRenderer(); - checkSurface(); - mWidth = width; - mHeight = height; - exec(RESIZE_SURFACE); - } - - public void render() { - checkRenderer(); - if (mSurface != null) { - exec(RENDER); - mSurface.updateTexImage(); - } - } - - public void finish() { - mSurface = null; - exec(FINISH); - try { - join(); - } catch (InterruptedException e) { - // Ignore - } - } - - private void exec(int msgid) { - synchronized (mLock) { - if (mExecMsgId != INVALID) { - throw new IllegalArgumentException( - "Message already set - multithreaded access?"); - } - mExecMsgId = msgid; - mLock.notify(); - try { - mLock.wait(); - } catch (InterruptedException e) { - // Ignore - } - } - } - - private void handleMessageLocked(int what) { - switch (what) { - case CHANGE_SURFACE: - if (mEglHelper.createSurface(mSurface)) { - mGL = mEglHelper.createGL(); - mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig); - } - break; - case RESIZE_SURFACE: - mRenderer.onSurfaceChanged(mGL, mWidth, mHeight); - break; - case RENDER: - mRenderer.onDrawFrame(mGL); - mEglHelper.swap(); - break; - case FINISH: - mEglHelper.destroySurface(); - mEglHelper.finish(); - mFinished = true; - break; - } - } - - @Override - public void run() { - synchronized (mLock) { - mEglHelper.start(); - while (!mFinished) { - while (mExecMsgId == INVALID) { - try { - mLock.wait(); - } catch (InterruptedException e) { - // Ignore - } - } - handleMessageLocked(mExecMsgId); - mExecMsgId = INVALID; - mLock.notify(); - } - mExecMsgId = FINISH; - } - } - } -} diff --git a/src/com/android/photos/views/GalleryThumbnailView.java b/src/com/android/photos/views/GalleryThumbnailView.java deleted file mode 100644 index e5dd6f2ff..000000000 --- a/src/com/android/photos/views/GalleryThumbnailView.java +++ /dev/null @@ -1,883 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.VelocityTrackerCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.EdgeEffectCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.util.SparseArray; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.widget.ListAdapter; -import android.widget.OverScroller; - -import java.util.ArrayList; - -public class GalleryThumbnailView extends ViewGroup { - - public interface GalleryThumbnailAdapter extends ListAdapter { - /** - * @param position Position to get the intrinsic aspect ratio for - * @return width / height - */ - float getIntrinsicAspectRatio(int position); - } - - private static final String TAG = "GalleryThumbnailView"; - private static final float ASPECT_RATIO = (float) Math.sqrt(1.5f); - private static final int LAND_UNITS = 2; - private static final int PORT_UNITS = 3; - - private GalleryThumbnailAdapter mAdapter; - - private final RecycleBin mRecycler = new RecycleBin(); - - private final AdapterDataSetObserver mObserver = new AdapterDataSetObserver(); - - private boolean mDataChanged; - private int mOldItemCount; - private int mItemCount; - private boolean mHasStableIds; - - private int mFirstPosition; - - private boolean mPopulating; - private boolean mInLayout; - - private int mTouchSlop; - private int mMaximumVelocity; - private int mFlingVelocity; - private float mLastTouchX; - private float mTouchRemainderX; - private int mActivePointerId; - - private static final int TOUCH_MODE_IDLE = 0; - private static final int TOUCH_MODE_DRAGGING = 1; - private static final int TOUCH_MODE_FLINGING = 2; - - private int mTouchMode; - private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); - private final OverScroller mScroller; - - private final EdgeEffectCompat mLeftEdge; - private final EdgeEffectCompat mRightEdge; - - private int mLargeColumnWidth; - private int mSmallColumnWidth; - private int mLargeColumnUnitCount = 8; - private int mSmallColumnUnitCount = 10; - - public GalleryThumbnailView(Context context) { - this(context, null); - } - - public GalleryThumbnailView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public GalleryThumbnailView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - final ViewConfiguration vc = ViewConfiguration.get(context); - mTouchSlop = vc.getScaledTouchSlop(); - mMaximumVelocity = vc.getScaledMaximumFlingVelocity(); - mFlingVelocity = vc.getScaledMinimumFlingVelocity(); - mScroller = new OverScroller(context); - - mLeftEdge = new EdgeEffectCompat(context); - mRightEdge = new EdgeEffectCompat(context); - setWillNotDraw(false); - setClipToPadding(false); - } - - @Override - public void requestLayout() { - if (!mPopulating) { - super.requestLayout(); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - if (widthMode != MeasureSpec.EXACTLY) { - Log.e(TAG, "onMeasure: must have an exact width or match_parent! " + - "Using fallback spec of EXACTLY " + widthSize); - } - if (heightMode != MeasureSpec.EXACTLY) { - Log.e(TAG, "onMeasure: must have an exact height or match_parent! " + - "Using fallback spec of EXACTLY " + heightSize); - } - - setMeasuredDimension(widthSize, heightSize); - - float portSpaces = mLargeColumnUnitCount / PORT_UNITS; - float height = getMeasuredHeight() / portSpaces; - mLargeColumnWidth = (int) (height / ASPECT_RATIO); - portSpaces++; - height = getMeasuredHeight() / portSpaces; - mSmallColumnWidth = (int) (height / ASPECT_RATIO); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mInLayout = true; - populate(); - mInLayout = false; - - final int width = r - l; - final int height = b - t; - mLeftEdge.setSize(width, height); - mRightEdge.setSize(width, height); - } - - private void populate() { - if (getWidth() == 0 || getHeight() == 0) { - return; - } - - // TODO: Handle size changing -// final int colCount = mColCount; -// if (mItemTops == null || mItemTops.length != colCount) { -// mItemTops = new int[colCount]; -// mItemBottoms = new int[colCount]; -// final int top = getPaddingTop(); -// final int offset = top + Math.min(mRestoreOffset, 0); -// Arrays.fill(mItemTops, offset); -// Arrays.fill(mItemBottoms, offset); -// mLayoutRecords.clear(); -// if (mInLayout) { -// removeAllViewsInLayout(); -// } else { -// removeAllViews(); -// } -// mRestoreOffset = 0; -// } - - mPopulating = true; - layoutChildren(mDataChanged); - fillRight(mFirstPosition + getChildCount(), 0); - fillLeft(mFirstPosition - 1, 0); - mPopulating = false; - mDataChanged = false; - } - - final void layoutChildren(boolean queryAdapter) { -// TODO -// final int childCount = getChildCount(); -// for (int i = 0; i < childCount; i++) { -// View child = getChildAt(i); -// -// if (child.isLayoutRequested()) { -// final int widthSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY); -// final int heightSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY); -// child.measure(widthSpec, heightSpec); -// child.layout(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); -// } -// -// int childTop = mItemBottoms[col] > Integer.MIN_VALUE ? -// mItemBottoms[col] + mItemMargin : child.getTop(); -// if (span > 1) { -// int lowest = childTop; -// for (int j = col + 1; j < col + span; j++) { -// final int bottom = mItemBottoms[j] + mItemMargin; -// if (bottom > lowest) { -// lowest = bottom; -// } -// } -// childTop = lowest; -// } -// final int childHeight = child.getMeasuredHeight(); -// final int childBottom = childTop + childHeight; -// final int childLeft = paddingLeft + col * (colWidth + itemMargin); -// final int childRight = childLeft + child.getMeasuredWidth(); -// child.layout(childLeft, childTop, childRight, childBottom); -// } - } - - /** - * Obtain the view and add it to our list of children. The view can be made - * fresh, converted from an unused view, or used as is if it was in the - * recycle bin. - * - * @param startPosition Logical position in the list to start from - * @param x Left or right edge of the view to add - * @param forward If true, align left edge to x and increase position. - * If false, align right edge to x and decrease position. - * @return Number of views added - */ - private int makeAndAddColumn(int startPosition, int x, boolean forward) { - int columnWidth = mLargeColumnWidth; - int addViews = 0; - for (int remaining = mLargeColumnUnitCount, i = 0; - remaining > 0 && startPosition + i >= 0 && startPosition + i < mItemCount; - i += forward ? 1 : -1, addViews++) { - if (mAdapter.getIntrinsicAspectRatio(startPosition + i) >= 1f) { - // landscape - remaining -= LAND_UNITS; - } else { - // portrait - remaining -= PORT_UNITS; - if (remaining < 0) { - remaining += (mSmallColumnUnitCount - mLargeColumnUnitCount); - columnWidth = mSmallColumnWidth; - } - } - } - int nextTop = 0; - for (int i = 0; i < addViews; i++) { - int position = startPosition + (forward ? i : -i); - View child = obtainView(position, null); - if (child.getParent() != this) { - if (mInLayout) { - addViewInLayout(child, forward ? -1 : 0, child.getLayoutParams()); - } else { - addView(child, forward ? -1 : 0); - } - } - int heightSize = (int) (.5f + (mAdapter.getIntrinsicAspectRatio(position) >= 1f - ? columnWidth / ASPECT_RATIO - : columnWidth * ASPECT_RATIO)); - int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY); - int widthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY); - child.measure(widthSpec, heightSpec); - int childLeft = forward ? x : x - columnWidth; - child.layout(childLeft, nextTop, childLeft + columnWidth, nextTop + heightSize); - nextTop += heightSize; - } - return addViews; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - mVelocityTracker.addMovement(ev); - final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; - switch (action) { - case MotionEvent.ACTION_DOWN: - mVelocityTracker.clear(); - mScroller.abortAnimation(); - mLastTouchX = ev.getX(); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mTouchRemainderX = 0; - if (mTouchMode == TOUCH_MODE_FLINGING) { - // Catch! - mTouchMode = TOUCH_MODE_DRAGGING; - return true; - } - break; - - case MotionEvent.ACTION_MOVE: { - final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - if (index < 0) { - Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " + - mActivePointerId + " - did StaggeredGridView receive an inconsistent " + - "event stream?"); - return false; - } - final float x = MotionEventCompat.getX(ev, index); - final float dx = x - mLastTouchX + mTouchRemainderX; - final int deltaY = (int) dx; - mTouchRemainderX = dx - deltaY; - - if (Math.abs(dx) > mTouchSlop) { - mTouchMode = TOUCH_MODE_DRAGGING; - return true; - } - } - } - - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - mVelocityTracker.addMovement(ev); - final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; - switch (action) { - case MotionEvent.ACTION_DOWN: - mVelocityTracker.clear(); - mScroller.abortAnimation(); - mLastTouchX = ev.getX(); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mTouchRemainderX = 0; - break; - - case MotionEvent.ACTION_MOVE: { - final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - if (index < 0) { - Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " + - mActivePointerId + " - did StaggeredGridView receive an inconsistent " + - "event stream?"); - return false; - } - final float x = MotionEventCompat.getX(ev, index); - final float dx = x - mLastTouchX + mTouchRemainderX; - final int deltaX = (int) dx; - mTouchRemainderX = dx - deltaX; - - if (Math.abs(dx) > mTouchSlop) { - mTouchMode = TOUCH_MODE_DRAGGING; - } - - if (mTouchMode == TOUCH_MODE_DRAGGING) { - mLastTouchX = x; - - if (!trackMotionScroll(deltaX, true)) { - // Break fling velocity if we impacted an edge. - mVelocityTracker.clear(); - } - } - } break; - - case MotionEvent.ACTION_CANCEL: - mTouchMode = TOUCH_MODE_IDLE; - break; - - case MotionEvent.ACTION_UP: { - mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - final float velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker, - mActivePointerId); - if (Math.abs(velocity) > mFlingVelocity) { // TODO - mTouchMode = TOUCH_MODE_FLINGING; - mScroller.fling(0, 0, (int) velocity, 0, - Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0); - mLastTouchX = 0; - ViewCompat.postInvalidateOnAnimation(this); - } else { - mTouchMode = TOUCH_MODE_IDLE; - } - - } break; - } - return true; - } - - /** - * - * @param deltaX Pixels that content should move by - * @return true if the movement completed, false if it was stopped prematurely. - */ - private boolean trackMotionScroll(int deltaX, boolean allowOverScroll) { - final boolean contentFits = contentFits(); - final int allowOverhang = Math.abs(deltaX); - - final int overScrolledBy; - final int movedBy; - if (!contentFits) { - final int overhang; - final boolean up; - mPopulating = true; - if (deltaX > 0) { - overhang = fillLeft(mFirstPosition - 1, allowOverhang); - up = true; - } else { - overhang = fillRight(mFirstPosition + getChildCount(), allowOverhang); - up = false; - } - movedBy = Math.min(overhang, allowOverhang); - offsetChildren(up ? movedBy : -movedBy); - recycleOffscreenViews(); - mPopulating = false; - overScrolledBy = allowOverhang - overhang; - } else { - overScrolledBy = allowOverhang; - movedBy = 0; - } - - if (allowOverScroll) { - final int overScrollMode = ViewCompat.getOverScrollMode(this); - - if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || - (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits)) { - - if (overScrolledBy > 0) { - EdgeEffectCompat edge = deltaX > 0 ? mLeftEdge : mRightEdge; - edge.onPull((float) Math.abs(deltaX) / getWidth()); - ViewCompat.postInvalidateOnAnimation(this); - } - } - } - - return deltaX == 0 || movedBy != 0; - } - - /** - * Important: this method will leave offscreen views attached if they - * are required to maintain the invariant that child view with index i - * is always the view corresponding to position mFirstPosition + i. - */ - private void recycleOffscreenViews() { - final int height = getHeight(); - final int clearAbove = 0; - final int clearBelow = height; - for (int i = getChildCount() - 1; i >= 0; i--) { - final View child = getChildAt(i); - if (child.getTop() <= clearBelow) { - // There may be other offscreen views, but we need to maintain - // the invariant documented above. - break; - } - - if (mInLayout) { - removeViewsInLayout(i, 1); - } else { - removeViewAt(i); - } - - mRecycler.addScrap(child); - } - - while (getChildCount() > 0) { - final View child = getChildAt(0); - if (child.getBottom() >= clearAbove) { - // There may be other offscreen views, but we need to maintain - // the invariant documented above. - break; - } - - if (mInLayout) { - removeViewsInLayout(0, 1); - } else { - removeViewAt(0); - } - - mRecycler.addScrap(child); - mFirstPosition++; - } - } - - final void offsetChildren(int offset) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - child.layout(child.getLeft() + offset, child.getTop(), - child.getRight() + offset, child.getBottom()); - } - } - - private boolean contentFits() { - final int childCount = getChildCount(); - if (childCount == 0) return true; - if (childCount != mItemCount) return false; - - return getChildAt(0).getLeft() >= getPaddingLeft() && - getChildAt(childCount - 1).getRight() <= getWidth() - getPaddingRight(); - } - - private void recycleAllViews() { - for (int i = 0; i < getChildCount(); i++) { - mRecycler.addScrap(getChildAt(i)); - } - - if (mInLayout) { - removeAllViewsInLayout(); - } else { - removeAllViews(); - } - } - - private int fillRight(int pos, int overhang) { - int end = (getRight() - getLeft()) + overhang; - - int nextLeft = getChildCount() == 0 ? 0 : getChildAt(getChildCount() - 1).getRight(); - while (nextLeft < end && pos < mItemCount) { - pos += makeAndAddColumn(pos, nextLeft, true); - nextLeft = getChildAt(getChildCount() - 1).getRight(); - } - final int gridRight = getWidth() - getPaddingRight(); - return getChildAt(getChildCount() - 1).getRight() - gridRight; - } - - private int fillLeft(int pos, int overhang) { - int end = getPaddingLeft() - overhang; - - int nextRight = getChildAt(0).getLeft(); - while (nextRight > end && pos >= 0) { - pos -= makeAndAddColumn(pos, nextRight, false); - nextRight = getChildAt(0).getLeft(); - } - - mFirstPosition = pos + 1; - return getPaddingLeft() - getChildAt(0).getLeft(); - } - - @Override - public void computeScroll() { - if (mScroller.computeScrollOffset()) { - final int x = mScroller.getCurrX(); - final int dx = (int) (x - mLastTouchX); - mLastTouchX = x; - final boolean stopped = !trackMotionScroll(dx, false); - - if (!stopped && !mScroller.isFinished()) { - ViewCompat.postInvalidateOnAnimation(this); - } else { - if (stopped) { - final int overScrollMode = ViewCompat.getOverScrollMode(this); - if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) { - final EdgeEffectCompat edge; - if (dx > 0) { - edge = mLeftEdge; - } else { - edge = mRightEdge; - } - edge.onAbsorb(Math.abs((int) mScroller.getCurrVelocity())); - ViewCompat.postInvalidateOnAnimation(this); - } - mScroller.abortAnimation(); - } - mTouchMode = TOUCH_MODE_IDLE; - } - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - if (!mLeftEdge.isFinished()) { - final int restoreCount = canvas.save(); - final int height = getHeight() - getPaddingTop() - getPaddingBottom(); - - canvas.rotate(270); - canvas.translate(-height + getPaddingTop(), 0); - mLeftEdge.setSize(height, getWidth()); - if (mLeftEdge.draw(canvas)) { - postInvalidateOnAnimation(); - } - canvas.restoreToCount(restoreCount); - } - if (!mRightEdge.isFinished()) { - final int restoreCount = canvas.save(); - final int width = getWidth(); - final int height = getHeight() - getPaddingTop() - getPaddingBottom(); - - canvas.rotate(90); - canvas.translate(-getPaddingTop(), width); - mRightEdge.setSize(height, width); - if (mRightEdge.draw(canvas)) { - postInvalidateOnAnimation(); - } - canvas.restoreToCount(restoreCount); - } - } - - /** - * Obtain a populated view from the adapter. If optScrap is non-null and is not - * reused it will be placed in the recycle bin. - * - * @param position position to get view for - * @param optScrap Optional scrap view; will be reused if possible - * @return A new view, a recycled view from mRecycler, or optScrap - */ - private final View obtainView(int position, View optScrap) { - View view = mRecycler.getTransientStateView(position); - if (view != null) { - return view; - } - - // Reuse optScrap if it's of the right type (and not null) - final int optType = optScrap != null ? - ((LayoutParams) optScrap.getLayoutParams()).viewType : -1; - final int positionViewType = mAdapter.getItemViewType(position); - final View scrap = optType == positionViewType ? - optScrap : mRecycler.getScrapView(positionViewType); - - view = mAdapter.getView(position, scrap, this); - - if (view != scrap && scrap != null) { - // The adapter didn't use it; put it back. - mRecycler.addScrap(scrap); - } - - ViewGroup.LayoutParams lp = view.getLayoutParams(); - - if (view.getParent() != this) { - if (lp == null) { - lp = generateDefaultLayoutParams(); - } else if (!checkLayoutParams(lp)) { - lp = generateLayoutParams(lp); - } - view.setLayoutParams(lp); - } - - final LayoutParams sglp = (LayoutParams) lp; - sglp.position = position; - sglp.viewType = positionViewType; - - return view; - } - - public GalleryThumbnailAdapter getAdapter() { - return mAdapter; - } - - public void setAdapter(GalleryThumbnailAdapter adapter) { - if (mAdapter != null) { - mAdapter.unregisterDataSetObserver(mObserver); - } - // TODO: If the new adapter says that there are stable IDs, remove certain layout records - // and onscreen views if they have changed instead of removing all of the state here. - clearAllState(); - mAdapter = adapter; - mDataChanged = true; - mOldItemCount = mItemCount = adapter != null ? adapter.getCount() : 0; - if (adapter != null) { - adapter.registerDataSetObserver(mObserver); - mRecycler.setViewTypeCount(adapter.getViewTypeCount()); - mHasStableIds = adapter.hasStableIds(); - } else { - mHasStableIds = false; - } - populate(); - } - - /** - * Clear all state because the grid will be used for a completely different set of data. - */ - private void clearAllState() { - // Clear all layout records and views - removeAllViews(); - - // Reset to the top of the grid - mFirstPosition = 0; - - // Clear recycler because there could be different view types now - mRecycler.clear(); - } - - @Override - protected LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(LayoutParams.WRAP_CONTENT); - } - - @Override - protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { - return new LayoutParams(lp); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) { - return lp instanceof LayoutParams; - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - public static class LayoutParams extends ViewGroup.LayoutParams { - private static final int[] LAYOUT_ATTRS = new int[] { - android.R.attr.layout_span - }; - - private static final int SPAN_INDEX = 0; - - /** - * The number of columns this item should span - */ - public int span = 1; - - /** - * Item position this view represents - */ - int position; - - /** - * Type of this view as reported by the adapter - */ - int viewType; - - /** - * The column this view is occupying - */ - int column; - - /** - * The stable ID of the item this view displays - */ - long id = -1; - - public LayoutParams(int height) { - super(MATCH_PARENT, height); - - if (this.height == MATCH_PARENT) { - Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " + - "impossible! Falling back to WRAP_CONTENT"); - this.height = WRAP_CONTENT; - } - } - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - - if (this.width != MATCH_PARENT) { - Log.w(TAG, "Inflation setting LayoutParams width to " + this.width + - " - must be MATCH_PARENT"); - this.width = MATCH_PARENT; - } - if (this.height == MATCH_PARENT) { - Log.w(TAG, "Inflation setting LayoutParams height to MATCH_PARENT - " + - "impossible! Falling back to WRAP_CONTENT"); - this.height = WRAP_CONTENT; - } - - TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); - span = a.getInteger(SPAN_INDEX, 1); - a.recycle(); - } - - public LayoutParams(ViewGroup.LayoutParams other) { - super(other); - - if (this.width != MATCH_PARENT) { - Log.w(TAG, "Constructing LayoutParams with width " + this.width + - " - must be MATCH_PARENT"); - this.width = MATCH_PARENT; - } - if (this.height == MATCH_PARENT) { - Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " + - "impossible! Falling back to WRAP_CONTENT"); - this.height = WRAP_CONTENT; - } - } - } - - private class RecycleBin { - private ArrayList<View>[] mScrapViews; - private int mViewTypeCount; - private int mMaxScrap; - - private SparseArray<View> mTransientStateViews; - - public void setViewTypeCount(int viewTypeCount) { - if (viewTypeCount < 1) { - throw new IllegalArgumentException("Must have at least one view type (" + - viewTypeCount + " types reported)"); - } - if (viewTypeCount == mViewTypeCount) { - return; - } - - ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; - for (int i = 0; i < viewTypeCount; i++) { - scrapViews[i] = new ArrayList<View>(); - } - mViewTypeCount = viewTypeCount; - mScrapViews = scrapViews; - } - - public void clear() { - final int typeCount = mViewTypeCount; - for (int i = 0; i < typeCount; i++) { - mScrapViews[i].clear(); - } - if (mTransientStateViews != null) { - mTransientStateViews.clear(); - } - } - - public void clearTransientViews() { - if (mTransientStateViews != null) { - mTransientStateViews.clear(); - } - } - - public void addScrap(View v) { - final LayoutParams lp = (LayoutParams) v.getLayoutParams(); - if (ViewCompat.hasTransientState(v)) { - if (mTransientStateViews == null) { - mTransientStateViews = new SparseArray<View>(); - } - mTransientStateViews.put(lp.position, v); - return; - } - - final int childCount = getChildCount(); - if (childCount > mMaxScrap) { - mMaxScrap = childCount; - } - - ArrayList<View> scrap = mScrapViews[lp.viewType]; - if (scrap.size() < mMaxScrap) { - scrap.add(v); - } - } - - public View getTransientStateView(int position) { - if (mTransientStateViews == null) { - return null; - } - - final View result = mTransientStateViews.get(position); - if (result != null) { - mTransientStateViews.remove(position); - } - return result; - } - - public View getScrapView(int type) { - ArrayList<View> scrap = mScrapViews[type]; - if (scrap.isEmpty()) { - return null; - } - - final int index = scrap.size() - 1; - final View result = scrap.get(index); - scrap.remove(index); - return result; - } - } - - private class AdapterDataSetObserver extends DataSetObserver { - @Override - public void onChanged() { - mDataChanged = true; - mOldItemCount = mItemCount; - mItemCount = mAdapter.getCount(); - - // TODO: Consider matching these back up if we have stable IDs. - mRecycler.clearTransientViews(); - - if (!mHasStableIds) { - recycleAllViews(); - } - - // TODO: consider repopulating in a deferred runnable instead - // (so that successive changes may still be batched) - requestLayout(); - } - - @Override - public void onInvalidated() { - } - } -} diff --git a/src/com/android/photos/views/HeaderGridView.java b/src/com/android/photos/views/HeaderGridView.java deleted file mode 100644 index 45a5eaf73..000000000 --- a/src/com/android/photos/views/HeaderGridView.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.views; - -import android.content.Context; -import android.database.DataSetObservable; -import android.database.DataSetObserver; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.Filter; -import android.widget.Filterable; -import android.widget.FrameLayout; -import android.widget.GridView; -import android.widget.ListAdapter; -import android.widget.WrapperListAdapter; - -import java.util.ArrayList; - -/** - * A {@link GridView} that supports adding header rows in a - * very similar way to {@link ListView}. - * See {@link HeaderGridView#addHeaderView(View, Object, boolean)} - */ -public class HeaderGridView extends GridView { - private static final String TAG = "HeaderGridView"; - - /** - * A class that represents a fixed view in a list, for example a header at the top - * or a footer at the bottom. - */ - private static class FixedViewInfo { - /** The view to add to the grid */ - public View view; - public ViewGroup viewContainer; - /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */ - public Object data; - /** <code>true</code> if the fixed view should be selectable in the grid */ - public boolean isSelectable; - } - - private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<FixedViewInfo>(); - - private void initHeaderGridView() { - super.setClipChildren(false); - } - - public HeaderGridView(Context context) { - super(context); - initHeaderGridView(); - } - - public HeaderGridView(Context context, AttributeSet attrs) { - super(context, attrs); - initHeaderGridView(); - } - - public HeaderGridView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initHeaderGridView(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - ListAdapter adapter = getAdapter(); - if (adapter != null && adapter instanceof HeaderViewGridAdapter) { - ((HeaderViewGridAdapter) adapter).setNumColumns(getNumColumns()); - } - } - - @Override - public void setClipChildren(boolean clipChildren) { - // Ignore, since the header rows depend on not being clipped - } - - /** - * Add a fixed view to appear at the top of the grid. If addHeaderView is - * called more than once, the views will appear in the order they were - * added. Views added using this call can take focus if they want. - * <p> - * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap - * the supplied cursor with one that will also account for header views. - * - * @param v The view to add. - * @param data Data to associate with this view - * @param isSelectable whether the item is selectable - */ - public void addHeaderView(View v, Object data, boolean isSelectable) { - ListAdapter adapter = getAdapter(); - - if (adapter != null && ! (adapter instanceof HeaderViewGridAdapter)) { - throw new IllegalStateException( - "Cannot add header view to grid -- setAdapter has already been called."); - } - - FixedViewInfo info = new FixedViewInfo(); - FrameLayout fl = new FullWidthFixedViewLayout(getContext()); - fl.addView(v); - info.view = v; - info.viewContainer = fl; - info.data = data; - info.isSelectable = isSelectable; - mHeaderViewInfos.add(info); - - // in the case of re-adding a header view, or adding one later on, - // we need to notify the observer - if (adapter != null) { - ((HeaderViewGridAdapter) adapter).notifyDataSetChanged(); - } - } - - /** - * Add a fixed view to appear at the top of the grid. If addHeaderView is - * called more than once, the views will appear in the order they were - * added. Views added using this call can take focus if they want. - * <p> - * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap - * the supplied cursor with one that will also account for header views. - * - * @param v The view to add. - */ - public void addHeaderView(View v) { - addHeaderView(v, null, true); - } - - public int getHeaderViewCount() { - return mHeaderViewInfos.size(); - } - - /** - * Removes a previously-added header view. - * - * @param v The view to remove - * @return true if the view was removed, false if the view was not a header - * view - */ - public boolean removeHeaderView(View v) { - if (mHeaderViewInfos.size() > 0) { - boolean result = false; - ListAdapter adapter = getAdapter(); - if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) { - result = true; - } - removeFixedViewInfo(v, mHeaderViewInfos); - return result; - } - return false; - } - - private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) { - int len = where.size(); - for (int i = 0; i < len; ++i) { - FixedViewInfo info = where.get(i); - if (info.view == v) { - where.remove(i); - break; - } - } - } - - @Override - public void setAdapter(ListAdapter adapter) { - if (mHeaderViewInfos.size() > 0) { - HeaderViewGridAdapter hadapter = new HeaderViewGridAdapter(mHeaderViewInfos, adapter); - int numColumns = getNumColumns(); - if (numColumns > 1) { - hadapter.setNumColumns(numColumns); - } - super.setAdapter(hadapter); - } else { - super.setAdapter(adapter); - } - } - - private class FullWidthFixedViewLayout extends FrameLayout { - public FullWidthFixedViewLayout(Context context) { - super(context); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int targetWidth = HeaderGridView.this.getMeasuredWidth() - - HeaderGridView.this.getPaddingLeft() - - HeaderGridView.this.getPaddingRight(); - widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth, - MeasureSpec.getMode(widthMeasureSpec)); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - /** - * ListAdapter used when a HeaderGridView has header views. This ListAdapter - * wraps another one and also keeps track of the header views and their - * associated data objects. - *<p>This is intended as a base class; you will probably not need to - * use this class directly in your own code. - */ - private static class HeaderViewGridAdapter implements WrapperListAdapter, Filterable { - - // This is used to notify the container of updates relating to number of columns - // or headers changing, which changes the number of placeholders needed - private final DataSetObservable mDataSetObservable = new DataSetObservable(); - - private final ListAdapter mAdapter; - private int mNumColumns = 1; - - // This ArrayList is assumed to NOT be null. - ArrayList<FixedViewInfo> mHeaderViewInfos; - - boolean mAreAllFixedViewsSelectable; - - private final boolean mIsFilterable; - - public HeaderViewGridAdapter(ArrayList<FixedViewInfo> headerViewInfos, ListAdapter adapter) { - mAdapter = adapter; - mIsFilterable = adapter instanceof Filterable; - - if (headerViewInfos == null) { - throw new IllegalArgumentException("headerViewInfos cannot be null"); - } - mHeaderViewInfos = headerViewInfos; - - mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos); - } - - public int getHeadersCount() { - return mHeaderViewInfos.size(); - } - - @Override - public boolean isEmpty() { - return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0; - } - - public void setNumColumns(int numColumns) { - if (numColumns < 1) { - throw new IllegalArgumentException("Number of columns must be 1 or more"); - } - if (mNumColumns != numColumns) { - mNumColumns = numColumns; - notifyDataSetChanged(); - } - } - - private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) { - if (infos != null) { - for (FixedViewInfo info : infos) { - if (!info.isSelectable) { - return false; - } - } - } - return true; - } - - public boolean removeHeader(View v) { - for (int i = 0; i < mHeaderViewInfos.size(); i++) { - FixedViewInfo info = mHeaderViewInfos.get(i); - if (info.view == v) { - mHeaderViewInfos.remove(i); - - mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos); - - mDataSetObservable.notifyChanged(); - return true; - } - } - - return false; - } - - @Override - public int getCount() { - if (mAdapter != null) { - return getHeadersCount() * mNumColumns + mAdapter.getCount(); - } else { - return getHeadersCount() * mNumColumns; - } - } - - @Override - public boolean areAllItemsEnabled() { - if (mAdapter != null) { - return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled(); - } else { - return true; - } - } - - @Override - public boolean isEnabled(int position) { - // Header (negative positions will throw an ArrayIndexOutOfBoundsException) - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - if (position < numHeadersAndPlaceholders) { - return (position % mNumColumns == 0) - && mHeaderViewInfos.get(position / mNumColumns).isSelectable; - } - - // Adapter - final int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = 0; - if (mAdapter != null) { - adapterCount = mAdapter.getCount(); - if (adjPosition < adapterCount) { - return mAdapter.isEnabled(adjPosition); - } - } - - throw new ArrayIndexOutOfBoundsException(position); - } - - @Override - public Object getItem(int position) { - // Header (negative positions will throw an ArrayIndexOutOfBoundsException) - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - if (position < numHeadersAndPlaceholders) { - if (position % mNumColumns == 0) { - return mHeaderViewInfos.get(position / mNumColumns).data; - } - return null; - } - - // Adapter - final int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = 0; - if (mAdapter != null) { - adapterCount = mAdapter.getCount(); - if (adjPosition < adapterCount) { - return mAdapter.getItem(adjPosition); - } - } - - throw new ArrayIndexOutOfBoundsException(position); - } - - @Override - public long getItemId(int position) { - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - if (mAdapter != null && position >= numHeadersAndPlaceholders) { - int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = mAdapter.getCount(); - if (adjPosition < adapterCount) { - return mAdapter.getItemId(adjPosition); - } - } - return -1; - } - - @Override - public boolean hasStableIds() { - if (mAdapter != null) { - return mAdapter.hasStableIds(); - } - return false; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - // Header (negative positions will throw an ArrayIndexOutOfBoundsException) - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns ; - if (position < numHeadersAndPlaceholders) { - View headerViewContainer = mHeaderViewInfos - .get(position / mNumColumns).viewContainer; - if (position % mNumColumns == 0) { - return headerViewContainer; - } else { - if (convertView == null) { - convertView = new View(parent.getContext()); - } - // We need to do this because GridView uses the height of the last item - // in a row to determine the height for the entire row. - convertView.setVisibility(View.INVISIBLE); - convertView.setMinimumHeight(headerViewContainer.getHeight()); - return convertView; - } - } - - // Adapter - final int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = 0; - if (mAdapter != null) { - adapterCount = mAdapter.getCount(); - if (adjPosition < adapterCount) { - return mAdapter.getView(adjPosition, convertView, parent); - } - } - - throw new ArrayIndexOutOfBoundsException(position); - } - - @Override - public int getItemViewType(int position) { - int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns; - if (position < numHeadersAndPlaceholders && (position % mNumColumns != 0)) { - // Placeholders get the last view type number - return mAdapter != null ? mAdapter.getViewTypeCount() : 1; - } - if (mAdapter != null && position >= numHeadersAndPlaceholders) { - int adjPosition = position - numHeadersAndPlaceholders; - int adapterCount = mAdapter.getCount(); - if (adjPosition < adapterCount) { - return mAdapter.getItemViewType(adjPosition); - } - } - - return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; - } - - @Override - public int getViewTypeCount() { - if (mAdapter != null) { - return mAdapter.getViewTypeCount() + 1; - } - return 2; - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - mDataSetObservable.registerObserver(observer); - if (mAdapter != null) { - mAdapter.registerDataSetObserver(observer); - } - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - mDataSetObservable.unregisterObserver(observer); - if (mAdapter != null) { - mAdapter.unregisterDataSetObserver(observer); - } - } - - @Override - public Filter getFilter() { - if (mIsFilterable) { - return ((Filterable) mAdapter).getFilter(); - } - return null; - } - - @Override - public ListAdapter getWrappedAdapter() { - return mAdapter; - } - - public void notifyDataSetChanged() { - mDataSetObservable.notifyChanged(); - } - } -} diff --git a/src/com/android/photos/views/SquareImageView.java b/src/com/android/photos/views/SquareImageView.java deleted file mode 100644 index 14eff1077..000000000 --- a/src/com/android/photos/views/SquareImageView.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.views; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.ImageView; - - -public class SquareImageView extends ImageView { - - public SquareImageView(Context context) { - super(context); - } - - public SquareImageView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public SquareImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = width; - if (heightMode == MeasureSpec.AT_MOST) { - height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec)); - } - setMeasuredDimension(width, height); - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } -} diff --git a/src/com/android/photos/views/TiledImageRenderer.java b/src/com/android/photos/views/TiledImageRenderer.java deleted file mode 100644 index c4e493b34..000000000 --- a/src/com/android/photos/views/TiledImageRenderer.java +++ /dev/null @@ -1,825 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.views; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Rect; -import android.graphics.RectF; -import android.support.v4.util.LongSparseArray; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.Pools.Pool; -import android.util.Pools.SynchronizedPool; -import android.view.View; -import android.view.WindowManager; - -import com.android.gallery3d.common.Utils; -import com.android.gallery3d.glrenderer.BasicTexture; -import com.android.gallery3d.glrenderer.GLCanvas; -import com.android.gallery3d.glrenderer.UploadedTexture; - -/** - * Handles laying out, decoding, and drawing of tiles in GL - */ -public class TiledImageRenderer { - public static final int SIZE_UNKNOWN = -1; - - private static final String TAG = "TiledImageRenderer"; - private static final int UPLOAD_LIMIT = 1; - - /* - * This is the tile state in the CPU side. - * Life of a Tile: - * ACTIVATED (initial state) - * --> IN_QUEUE - by queueForDecode() - * --> RECYCLED - by recycleTile() - * IN_QUEUE --> DECODING - by decodeTile() - * --> RECYCLED - by recycleTile) - * DECODING --> RECYCLING - by recycleTile() - * --> DECODED - by decodeTile() - * --> DECODE_FAIL - by decodeTile() - * RECYCLING --> RECYCLED - by decodeTile() - * DECODED --> ACTIVATED - (after the decoded bitmap is uploaded) - * DECODED --> RECYCLED - by recycleTile() - * DECODE_FAIL -> RECYCLED - by recycleTile() - * RECYCLED --> ACTIVATED - by obtainTile() - */ - private static final int STATE_ACTIVATED = 0x01; - private static final int STATE_IN_QUEUE = 0x02; - private static final int STATE_DECODING = 0x04; - private static final int STATE_DECODED = 0x08; - private static final int STATE_DECODE_FAIL = 0x10; - private static final int STATE_RECYCLING = 0x20; - private static final int STATE_RECYCLED = 0x40; - - private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64); - - // TILE_SIZE must be 2^N - private int mTileSize; - - private TileSource mModel; - private BasicTexture mPreview; - protected int mLevelCount; // cache the value of mScaledBitmaps.length - - // The mLevel variable indicates which level of bitmap we should use. - // Level 0 means the original full-sized bitmap, and a larger value means - // a smaller scaled bitmap (The width and height of each scaled bitmap is - // half size of the previous one). If the value is in [0, mLevelCount), we - // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value - // is mLevelCount - private int mLevel = 0; - - private int mOffsetX; - private int mOffsetY; - - private int mUploadQuota; - private boolean mRenderComplete; - - private final RectF mSourceRect = new RectF(); - private final RectF mTargetRect = new RectF(); - - private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>(); - - // The following three queue are guarded by mQueueLock - private final Object mQueueLock = new Object(); - private final TileQueue mRecycledQueue = new TileQueue(); - private final TileQueue mUploadQueue = new TileQueue(); - private final TileQueue mDecodeQueue = new TileQueue(); - - // The width and height of the full-sized bitmap - protected int mImageWidth = SIZE_UNKNOWN; - protected int mImageHeight = SIZE_UNKNOWN; - - protected int mCenterX; - protected int mCenterY; - protected float mScale; - protected int mRotation; - - private boolean mLayoutTiles; - - // Temp variables to avoid memory allocation - private final Rect mTileRange = new Rect(); - private final Rect mActiveRange[] = {new Rect(), new Rect()}; - - private TileDecoder mTileDecoder; - private boolean mBackgroundTileUploaded; - - private int mViewWidth, mViewHeight; - private View mParent; - - /** - * Interface for providing tiles to a {@link TiledImageRenderer} - */ - public static interface TileSource { - - /** - * If the source does not care about the tile size, it should use - * {@link TiledImageRenderer#suggestedTileSize(Context)} - */ - public int getTileSize(); - public int getImageWidth(); - public int getImageHeight(); - public int getRotation(); - - /** - * Return a Preview image if available. This will be used as the base layer - * if higher res tiles are not yet available - */ - public BasicTexture getPreview(); - - /** - * The tile returned by this method can be specified this way: Assuming - * the image size is (width, height), first take the intersection of (0, - * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If - * in extending the region, we found some part of the region is outside - * the image, those pixels are filled with black. - * - * If level > 0, it does the same operation on a down-scaled version of - * the original image (down-scaled by a factor of 2^level), but (x, y) - * still refers to the coordinate on the original image. - * - * The method would be called by the decoder thread. - */ - public Bitmap getTile(int level, int x, int y, Bitmap reuse); - } - - public static int suggestedTileSize(Context context) { - return isHighResolution(context) ? 512 : 256; - } - - private static boolean isHighResolution(Context context) { - DisplayMetrics metrics = new DisplayMetrics(); - WindowManager wm = (WindowManager) - context.getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getMetrics(metrics); - return metrics.heightPixels > 2048 || metrics.widthPixels > 2048; - } - - public TiledImageRenderer(View parent) { - mParent = parent; - mTileDecoder = new TileDecoder(); - mTileDecoder.start(); - } - - public int getViewWidth() { - return mViewWidth; - } - - public int getViewHeight() { - return mViewHeight; - } - - private void invalidate() { - mParent.postInvalidate(); - } - - public void setModel(TileSource model, int rotation) { - if (mModel != model) { - mModel = model; - notifyModelInvalidated(); - } - if (mRotation != rotation) { - mRotation = rotation; - mLayoutTiles = true; - } - } - - private void calculateLevelCount() { - if (mPreview != null) { - mLevelCount = Math.max(0, Utils.ceilLog2( - mImageWidth / (float) mPreview.getWidth())); - } else { - int levels = 1; - int maxDim = Math.max(mImageWidth, mImageHeight); - int t = mTileSize; - while (t < maxDim) { - t <<= 1; - levels++; - } - mLevelCount = levels; - } - } - - public void notifyModelInvalidated() { - invalidateTiles(); - if (mModel == null) { - mImageWidth = 0; - mImageHeight = 0; - mLevelCount = 0; - mPreview = null; - } else { - mImageWidth = mModel.getImageWidth(); - mImageHeight = mModel.getImageHeight(); - mPreview = mModel.getPreview(); - mTileSize = mModel.getTileSize(); - calculateLevelCount(); - } - mLayoutTiles = true; - } - - public void setViewSize(int width, int height) { - mViewWidth = width; - mViewHeight = height; - } - - public void setPosition(int centerX, int centerY, float scale) { - if (mCenterX == centerX && mCenterY == centerY - && mScale == scale) { - return; - } - mCenterX = centerX; - mCenterY = centerY; - mScale = scale; - mLayoutTiles = true; - } - - // Prepare the tiles we want to use for display. - // - // 1. Decide the tile level we want to use for display. - // 2. Decide the tile levels we want to keep as texture (in addition to - // the one we use for display). - // 3. Recycle unused tiles. - // 4. Activate the tiles we want. - private void layoutTiles() { - if (mViewWidth == 0 || mViewHeight == 0 || !mLayoutTiles) { - return; - } - mLayoutTiles = false; - - // The tile levels we want to keep as texture is in the range - // [fromLevel, endLevel). - int fromLevel; - int endLevel; - - // We want to use a texture larger than or equal to the display size. - mLevel = Utils.clamp(Utils.floorLog2(1f / mScale), 0, mLevelCount); - - // We want to keep one more tile level as texture in addition to what - // we use for display. So it can be faster when the scale moves to the - // next level. We choose the level closest to the current scale. - if (mLevel != mLevelCount) { - Rect range = mTileRange; - getRange(range, mCenterX, mCenterY, mLevel, mScale, mRotation); - mOffsetX = Math.round(mViewWidth / 2f + (range.left - mCenterX) * mScale); - mOffsetY = Math.round(mViewHeight / 2f + (range.top - mCenterY) * mScale); - fromLevel = mScale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel; - } else { - // Activate the tiles of the smallest two levels. - fromLevel = mLevel - 2; - mOffsetX = Math.round(mViewWidth / 2f - mCenterX * mScale); - mOffsetY = Math.round(mViewHeight / 2f - mCenterY * mScale); - } - - fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2)); - endLevel = Math.min(fromLevel + 2, mLevelCount); - - Rect range[] = mActiveRange; - for (int i = fromLevel; i < endLevel; ++i) { - getRange(range[i - fromLevel], mCenterX, mCenterY, i, mRotation); - } - - // If rotation is transient, don't update the tile. - if (mRotation % 90 != 0) { - return; - } - - synchronized (mQueueLock) { - mDecodeQueue.clean(); - mUploadQueue.clean(); - mBackgroundTileUploaded = false; - - // Recycle unused tiles: if the level of the active tile is outside the - // range [fromLevel, endLevel) or not in the visible range. - int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - Tile tile = mActiveTiles.valueAt(i); - int level = tile.mTileLevel; - if (level < fromLevel || level >= endLevel - || !range[level - fromLevel].contains(tile.mX, tile.mY)) { - mActiveTiles.removeAt(i); - i--; - n--; - recycleTile(tile); - } - } - } - - for (int i = fromLevel; i < endLevel; ++i) { - int size = mTileSize << i; - Rect r = range[i - fromLevel]; - for (int y = r.top, bottom = r.bottom; y < bottom; y += size) { - for (int x = r.left, right = r.right; x < right; x += size) { - activateTile(x, y, i); - } - } - } - invalidate(); - } - - private void invalidateTiles() { - synchronized (mQueueLock) { - mDecodeQueue.clean(); - mUploadQueue.clean(); - - // TODO(xx): disable decoder - int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - Tile tile = mActiveTiles.valueAt(i); - recycleTile(tile); - } - mActiveTiles.clear(); - } - } - - private void getRange(Rect out, int cX, int cY, int level, int rotation) { - getRange(out, cX, cY, level, 1f / (1 << (level + 1)), rotation); - } - - // If the bitmap is scaled by the given factor "scale", return the - // rectangle containing visible range. The left-top coordinate returned is - // aligned to the tile boundary. - // - // (cX, cY) is the point on the original bitmap which will be put in the - // center of the ImageViewer. - private void getRange(Rect out, - int cX, int cY, int level, float scale, int rotation) { - - double radians = Math.toRadians(-rotation); - double w = mViewWidth; - double h = mViewHeight; - - double cos = Math.cos(radians); - double sin = Math.sin(radians); - int width = (int) Math.ceil(Math.max( - Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h))); - int height = (int) Math.ceil(Math.max( - Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h))); - - int left = (int) Math.floor(cX - width / (2f * scale)); - int top = (int) Math.floor(cY - height / (2f * scale)); - int right = (int) Math.ceil(left + width / scale); - int bottom = (int) Math.ceil(top + height / scale); - - // align the rectangle to tile boundary - int size = mTileSize << level; - left = Math.max(0, size * (left / size)); - top = Math.max(0, size * (top / size)); - right = Math.min(mImageWidth, right); - bottom = Math.min(mImageHeight, bottom); - - out.set(left, top, right, bottom); - } - - public void freeTextures() { - mLayoutTiles = true; - - mTileDecoder.finishAndWait(); - synchronized (mQueueLock) { - mUploadQueue.clean(); - mDecodeQueue.clean(); - Tile tile = mRecycledQueue.pop(); - while (tile != null) { - tile.recycle(); - tile = mRecycledQueue.pop(); - } - } - - int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - Tile texture = mActiveTiles.valueAt(i); - texture.recycle(); - } - mActiveTiles.clear(); - mTileRange.set(0, 0, 0, 0); - - while (sTilePool.acquire() != null) {} - } - - public boolean draw(GLCanvas canvas) { - layoutTiles(); - uploadTiles(canvas); - - mUploadQuota = UPLOAD_LIMIT; - mRenderComplete = true; - - int level = mLevel; - int rotation = mRotation; - int flags = 0; - if (rotation != 0) { - flags |= GLCanvas.SAVE_FLAG_MATRIX; - } - - if (flags != 0) { - canvas.save(flags); - if (rotation != 0) { - int centerX = mViewWidth / 2, centerY = mViewHeight / 2; - canvas.translate(centerX, centerY); - canvas.rotate(rotation, 0, 0, 1); - canvas.translate(-centerX, -centerY); - } - } - try { - if (level != mLevelCount) { - int size = (mTileSize << level); - float length = size * mScale; - Rect r = mTileRange; - - for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) { - float y = mOffsetY + i * length; - for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) { - float x = mOffsetX + j * length; - drawTile(canvas, tx, ty, level, x, y, length); - } - } - } else if (mPreview != null) { - mPreview.draw(canvas, mOffsetX, mOffsetY, - Math.round(mImageWidth * mScale), - Math.round(mImageHeight * mScale)); - } - } finally { - if (flags != 0) { - canvas.restore(); - } - } - - if (mRenderComplete) { - if (!mBackgroundTileUploaded) { - uploadBackgroundTiles(canvas); - } - } else { - invalidate(); - } - return mRenderComplete || mPreview != null; - } - - private void uploadBackgroundTiles(GLCanvas canvas) { - mBackgroundTileUploaded = true; - int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - Tile tile = mActiveTiles.valueAt(i); - if (!tile.isContentValid()) { - queueForDecode(tile); - } - } - } - - private void queueForDecode(Tile tile) { - synchronized (mQueueLock) { - if (tile.mTileState == STATE_ACTIVATED) { - tile.mTileState = STATE_IN_QUEUE; - if (mDecodeQueue.push(tile)) { - mQueueLock.notifyAll(); - } - } - } - } - - private void decodeTile(Tile tile) { - synchronized (mQueueLock) { - if (tile.mTileState != STATE_IN_QUEUE) { - return; - } - tile.mTileState = STATE_DECODING; - } - boolean decodeComplete = tile.decode(); - synchronized (mQueueLock) { - if (tile.mTileState == STATE_RECYCLING) { - tile.mTileState = STATE_RECYCLED; - if (tile.mDecodedTile != null) { - sTilePool.release(tile.mDecodedTile); - tile.mDecodedTile = null; - } - mRecycledQueue.push(tile); - return; - } - tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL; - if (!decodeComplete) { - return; - } - mUploadQueue.push(tile); - } - invalidate(); - } - - private Tile obtainTile(int x, int y, int level) { - synchronized (mQueueLock) { - Tile tile = mRecycledQueue.pop(); - if (tile != null) { - tile.mTileState = STATE_ACTIVATED; - tile.update(x, y, level); - return tile; - } - return new Tile(x, y, level); - } - } - - private void recycleTile(Tile tile) { - synchronized (mQueueLock) { - if (tile.mTileState == STATE_DECODING) { - tile.mTileState = STATE_RECYCLING; - return; - } - tile.mTileState = STATE_RECYCLED; - if (tile.mDecodedTile != null) { - sTilePool.release(tile.mDecodedTile); - tile.mDecodedTile = null; - } - mRecycledQueue.push(tile); - } - } - - private void activateTile(int x, int y, int level) { - long key = makeTileKey(x, y, level); - Tile tile = mActiveTiles.get(key); - if (tile != null) { - if (tile.mTileState == STATE_IN_QUEUE) { - tile.mTileState = STATE_ACTIVATED; - } - return; - } - tile = obtainTile(x, y, level); - mActiveTiles.put(key, tile); - } - - private Tile getTile(int x, int y, int level) { - return mActiveTiles.get(makeTileKey(x, y, level)); - } - - private static long makeTileKey(int x, int y, int level) { - long result = x; - result = (result << 16) | y; - result = (result << 16) | level; - return result; - } - - private void uploadTiles(GLCanvas canvas) { - int quota = UPLOAD_LIMIT; - Tile tile = null; - while (quota > 0) { - synchronized (mQueueLock) { - tile = mUploadQueue.pop(); - } - if (tile == null) { - break; - } - if (!tile.isContentValid()) { - if (tile.mTileState == STATE_DECODED) { - tile.updateContent(canvas); - --quota; - } else { - Log.w(TAG, "Tile in upload queue has invalid state: " + tile.mTileState); - } - } - } - if (tile != null) { - invalidate(); - } - } - - // Draw the tile to a square at canvas that locates at (x, y) and - // has a side length of length. - private void drawTile(GLCanvas canvas, - int tx, int ty, int level, float x, float y, float length) { - RectF source = mSourceRect; - RectF target = mTargetRect; - target.set(x, y, x + length, y + length); - source.set(0, 0, mTileSize, mTileSize); - - Tile tile = getTile(tx, ty, level); - if (tile != null) { - if (!tile.isContentValid()) { - if (tile.mTileState == STATE_DECODED) { - if (mUploadQuota > 0) { - --mUploadQuota; - tile.updateContent(canvas); - } else { - mRenderComplete = false; - } - } else if (tile.mTileState != STATE_DECODE_FAIL){ - mRenderComplete = false; - queueForDecode(tile); - } - } - if (drawTile(tile, canvas, source, target)) { - return; - } - } - if (mPreview != null) { - int size = mTileSize << level; - float scaleX = (float) mPreview.getWidth() / mImageWidth; - float scaleY = (float) mPreview.getHeight() / mImageHeight; - source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX, - (ty + size) * scaleY); - canvas.drawTexture(mPreview, source, target); - } - } - - private boolean drawTile( - Tile tile, GLCanvas canvas, RectF source, RectF target) { - while (true) { - if (tile.isContentValid()) { - canvas.drawTexture(tile, source, target); - return true; - } - - // Parent can be divided to four quads and tile is one of the four. - Tile parent = tile.getParentTile(); - if (parent == null) { - return false; - } - if (tile.mX == parent.mX) { - source.left /= 2f; - source.right /= 2f; - } else { - source.left = (mTileSize + source.left) / 2f; - source.right = (mTileSize + source.right) / 2f; - } - if (tile.mY == parent.mY) { - source.top /= 2f; - source.bottom /= 2f; - } else { - source.top = (mTileSize + source.top) / 2f; - source.bottom = (mTileSize + source.bottom) / 2f; - } - tile = parent; - } - } - - private class Tile extends UploadedTexture { - public int mX; - public int mY; - public int mTileLevel; - public Tile mNext; - public Bitmap mDecodedTile; - public volatile int mTileState = STATE_ACTIVATED; - - public Tile(int x, int y, int level) { - mX = x; - mY = y; - mTileLevel = level; - } - - @Override - protected void onFreeBitmap(Bitmap bitmap) { - sTilePool.release(bitmap); - } - - boolean decode() { - // Get a tile from the original image. The tile is down-scaled - // by (1 << mTilelevel) from a region in the original image. - try { - Bitmap reuse = sTilePool.acquire(); - if (reuse != null && reuse.getWidth() != mTileSize) { - reuse = null; - } - mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse); - } catch (Throwable t) { - Log.w(TAG, "fail to decode tile", t); - } - return mDecodedTile != null; - } - - @Override - protected Bitmap onGetBitmap() { - Utils.assertTrue(mTileState == STATE_DECODED); - - // We need to override the width and height, so that we won't - // draw beyond the boundaries. - int rightEdge = ((mImageWidth - mX) >> mTileLevel); - int bottomEdge = ((mImageHeight - mY) >> mTileLevel); - setSize(Math.min(mTileSize, rightEdge), Math.min(mTileSize, bottomEdge)); - - Bitmap bitmap = mDecodedTile; - mDecodedTile = null; - mTileState = STATE_ACTIVATED; - return bitmap; - } - - // We override getTextureWidth() and getTextureHeight() here, so the - // texture can be re-used for different tiles regardless of the actual - // size of the tile (which may be small because it is a tile at the - // boundary). - @Override - public int getTextureWidth() { - return mTileSize; - } - - @Override - public int getTextureHeight() { - return mTileSize; - } - - public void update(int x, int y, int level) { - mX = x; - mY = y; - mTileLevel = level; - invalidateContent(); - } - - public Tile getParentTile() { - if (mTileLevel + 1 == mLevelCount) { - return null; - } - int size = mTileSize << (mTileLevel + 1); - int x = size * (mX / size); - int y = size * (mY / size); - return getTile(x, y, mTileLevel + 1); - } - - @Override - public String toString() { - return String.format("tile(%s, %s, %s / %s)", - mX / mTileSize, mY / mTileSize, mLevel, mLevelCount); - } - } - - private static class TileQueue { - private Tile mHead; - - public Tile pop() { - Tile tile = mHead; - if (tile != null) { - mHead = tile.mNext; - } - return tile; - } - - public boolean push(Tile tile) { - if (contains(tile)) { - Log.w(TAG, "Attempting to add a tile already in the queue!"); - return false; - } - boolean wasEmpty = mHead == null; - tile.mNext = mHead; - mHead = tile; - return wasEmpty; - } - - private boolean contains(Tile tile) { - Tile other = mHead; - while (other != null) { - if (other == tile) { - return true; - } - other = other.mNext; - } - return false; - } - - public void clean() { - mHead = null; - } - } - - private class TileDecoder extends Thread { - - public void finishAndWait() { - interrupt(); - try { - join(); - } catch (InterruptedException e) { - Log.w(TAG, "Interrupted while waiting for TileDecoder thread to finish!"); - } - } - - private Tile waitForTile() throws InterruptedException { - synchronized (mQueueLock) { - while (true) { - Tile tile = mDecodeQueue.pop(); - if (tile != null) { - return tile; - } - mQueueLock.wait(); - } - } - } - - @Override - public void run() { - try { - while (!isInterrupted()) { - Tile tile = waitForTile(); - decodeTile(tile); - } - } catch (InterruptedException ex) { - // We were finished - } - } - - } -} diff --git a/src/com/android/photos/views/TiledImageView.java b/src/com/android/photos/views/TiledImageView.java deleted file mode 100644 index 8bc07c051..000000000 --- a/src/com/android/photos/views/TiledImageView.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) 2013 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.android.photos.views; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.RectF; -import android.opengl.GLSurfaceView; -import android.opengl.GLSurfaceView.Renderer; -import android.os.Build; -import android.util.AttributeSet; -import android.view.Choreographer; -import android.view.Choreographer.FrameCallback; -import android.view.View; -import android.widget.FrameLayout; - -import com.android.gallery3d.glrenderer.BasicTexture; -import com.android.gallery3d.glrenderer.GLES20Canvas; -import com.android.photos.views.TiledImageRenderer.TileSource; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - -/** - * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView} - * or {@link BlockingGLTextureView}. - */ -public class TiledImageView extends FrameLayout { - - private static final boolean USE_TEXTURE_VIEW = false; - private static final boolean IS_SUPPORTED = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - private static final boolean USE_CHOREOGRAPHER = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - - private BlockingGLTextureView mTextureView; - private GLSurfaceView mGLSurfaceView; - private boolean mInvalPending = false; - private FrameCallback mFrameCallback; - - private static class ImageRendererWrapper { - // Guarded by locks - float scale; - int centerX, centerY; - int rotation; - TileSource source; - Runnable isReadyCallback; - - // GL thread only - TiledImageRenderer image; - } - - private float[] mValues = new float[9]; - - // ------------------------- - // Guarded by mLock - // ------------------------- - private Object mLock = new Object(); - private ImageRendererWrapper mRenderer; - - public TiledImageView(Context context) { - this(context, null); - } - - public TiledImageView(Context context, AttributeSet attrs) { - super(context, attrs); - if (!IS_SUPPORTED) { - return; - } - - mRenderer = new ImageRendererWrapper(); - mRenderer.image = new TiledImageRenderer(this); - View view; - if (USE_TEXTURE_VIEW) { - mTextureView = new BlockingGLTextureView(context); - mTextureView.setRenderer(new TileRenderer()); - view = mTextureView; - } else { - mGLSurfaceView = new GLSurfaceView(context); - mGLSurfaceView.setEGLContextClientVersion(2); - mGLSurfaceView.setRenderer(new TileRenderer()); - mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); - view = mGLSurfaceView; - } - addView(view, new LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - //setTileSource(new ColoredTiles()); - } - - public void destroy() { - if (!IS_SUPPORTED) { - return; - } - if (USE_TEXTURE_VIEW) { - mTextureView.destroy(); - } else { - mGLSurfaceView.queueEvent(mFreeTextures); - } - } - - private Runnable mFreeTextures = new Runnable() { - - @Override - public void run() { - mRenderer.image.freeTextures(); - } - }; - - public void onPause() { - if (!IS_SUPPORTED) { - return; - } - if (!USE_TEXTURE_VIEW) { - mGLSurfaceView.onPause(); - } - } - - public void onResume() { - if (!IS_SUPPORTED) { - return; - } - if (!USE_TEXTURE_VIEW) { - mGLSurfaceView.onResume(); - } - } - - public void setTileSource(TileSource source, Runnable isReadyCallback) { - if (!IS_SUPPORTED) { - return; - } - synchronized (mLock) { - mRenderer.source = source; - mRenderer.isReadyCallback = isReadyCallback; - mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0; - mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0; - mRenderer.rotation = source != null ? source.getRotation() : 0; - mRenderer.scale = 0; - updateScaleIfNecessaryLocked(mRenderer); - } - invalidate(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, - int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (!IS_SUPPORTED) { - return; - } - synchronized (mLock) { - updateScaleIfNecessaryLocked(mRenderer); - } - } - - private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) { - if (renderer == null || renderer.source == null - || renderer.scale > 0 || getWidth() == 0) { - return; - } - renderer.scale = Math.min( - (float) getWidth() / (float) renderer.source.getImageWidth(), - (float) getHeight() / (float) renderer.source.getImageHeight()); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - if (!IS_SUPPORTED) { - return; - } - if (USE_TEXTURE_VIEW) { - mTextureView.render(); - } - super.dispatchDraw(canvas); - } - - @SuppressLint("NewApi") - @Override - public void setTranslationX(float translationX) { - if (!IS_SUPPORTED) { - return; - } - super.setTranslationX(translationX); - } - - @Override - public void invalidate() { - if (!IS_SUPPORTED) { - return; - } - if (USE_TEXTURE_VIEW) { - super.invalidate(); - mTextureView.invalidate(); - } else { - if (USE_CHOREOGRAPHER) { - invalOnVsync(); - } else { - mGLSurfaceView.requestRender(); - } - } - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private void invalOnVsync() { - if (!mInvalPending) { - mInvalPending = true; - if (mFrameCallback == null) { - mFrameCallback = new FrameCallback() { - @Override - public void doFrame(long frameTimeNanos) { - mInvalPending = false; - mGLSurfaceView.requestRender(); - } - }; - } - Choreographer.getInstance().postFrameCallback(mFrameCallback); - } - } - - private RectF mTempRectF = new RectF(); - public void positionFromMatrix(Matrix matrix) { - if (!IS_SUPPORTED) { - return; - } - if (mRenderer.source != null) { - final int rotation = mRenderer.source.getRotation(); - final boolean swap = !(rotation % 180 == 0); - final int width = swap ? mRenderer.source.getImageHeight() - : mRenderer.source.getImageWidth(); - final int height = swap ? mRenderer.source.getImageWidth() - : mRenderer.source.getImageHeight(); - mTempRectF.set(0, 0, width, height); - matrix.mapRect(mTempRectF); - matrix.getValues(mValues); - int cx = width / 2; - int cy = height / 2; - float scale = mValues[Matrix.MSCALE_X]; - int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale); - int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale); - if (rotation == 90 || rotation == 180) { - cx += (mTempRectF.left / scale) - xoffset; - } else { - cx -= (mTempRectF.left / scale) - xoffset; - } - if (rotation == 180 || rotation == 270) { - cy += (mTempRectF.top / scale) - yoffset; - } else { - cy -= (mTempRectF.top / scale) - yoffset; - } - mRenderer.scale = scale; - mRenderer.centerX = swap ? cy : cx; - mRenderer.centerY = swap ? cx : cy; - invalidate(); - } - } - - private class TileRenderer implements Renderer { - - private GLES20Canvas mCanvas; - - @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - mCanvas = new GLES20Canvas(); - BasicTexture.invalidateAllTextures(); - mRenderer.image.setModel(mRenderer.source, mRenderer.rotation); - } - - @Override - public void onSurfaceChanged(GL10 gl, int width, int height) { - mCanvas.setSize(width, height); - mRenderer.image.setViewSize(width, height); - } - - @Override - public void onDrawFrame(GL10 gl) { - mCanvas.clearBuffer(); - Runnable readyCallback; - synchronized (mLock) { - readyCallback = mRenderer.isReadyCallback; - mRenderer.image.setModel(mRenderer.source, mRenderer.rotation); - mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY, - mRenderer.scale); - } - boolean complete = mRenderer.image.draw(mCanvas); - if (complete && readyCallback != null) { - synchronized (mLock) { - // Make sure we don't trample on a newly set callback/source - // if it changed while we were rendering - if (mRenderer.isReadyCallback == readyCallback) { - mRenderer.isReadyCallback = null; - } - } - if (readyCallback != null) { - post(readyCallback); - } - } - } - - } - - @SuppressWarnings("unused") - private static class ColoredTiles implements TileSource { - private static final int[] COLORS = new int[] { - Color.RED, - Color.BLUE, - Color.YELLOW, - Color.GREEN, - Color.CYAN, - Color.MAGENTA, - Color.WHITE, - }; - - private Paint mPaint = new Paint(); - private Canvas mCanvas = new Canvas(); - - @Override - public int getTileSize() { - return 256; - } - - @Override - public int getImageWidth() { - return 16384; - } - - @Override - public int getImageHeight() { - return 8192; - } - - @Override - public int getRotation() { - return 0; - } - - @Override - public Bitmap getTile(int level, int x, int y, Bitmap bitmap) { - int tileSize = getTileSize(); - if (bitmap == null) { - bitmap = Bitmap.createBitmap(tileSize, tileSize, - Bitmap.Config.ARGB_8888); - } - mCanvas.setBitmap(bitmap); - mCanvas.drawColor(COLORS[level]); - mPaint.setColor(Color.BLACK); - mPaint.setTextSize(20); - mPaint.setTextAlign(Align.CENTER); - mCanvas.drawText(x + "x" + y, 128, 128, mPaint); - tileSize <<= level; - x /= tileSize; - y /= tileSize; - mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint); - mCanvas.setBitmap(null); - return bitmap; - } - - @Override - public BasicTexture getPreview() { - return null; - } - } -} |