aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/ui/media
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/media')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaAddFragment.java281
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaBrowserActivity.java583
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaEditFragment.java385
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryActivity.java186
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryAdapter.java140
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryEditFragment.java191
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryPickerActivity.java275
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaGallerySettingsFragment.java370
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridAdapter.java519
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridFragment.java836
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaImageLoader.java42
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaItemFragment.java380
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaPickerActivity.java538
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaSourceWPImages.java264
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/MediaSourceWPVideos.java209
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/WordPressMediaUtils.java379
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaDeleteService.java121
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaEvents.java51
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaUploadService.java247
19 files changed, 5997 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaAddFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaAddFragment.java
new file mode 100644
index 000000000..ea8d06f47
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaAddFragment.java
@@ -0,0 +1,281 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.support.v4.content.CursorLoader;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import org.wordpress.android.BuildConfig;
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.models.MediaUploadState;
+import org.wordpress.android.ui.RequestCodes;
+import org.wordpress.android.ui.media.WordPressMediaUtils.LaunchCameraCallback;
+import org.wordpress.android.ui.media.services.MediaEvents.MediaChanged;
+import org.wordpress.android.ui.media.services.MediaUploadService;
+import org.wordpress.android.util.MediaUtils;
+import org.wordpress.android.util.NetworkUtils;
+import org.wordpress.android.util.helpers.MediaFile;
+
+import java.io.File;
+import java.util.List;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * An invisible fragment in charge of launching the right intents to camera, video, and image library.
+ * Also queues up media for upload and listens to notifications from the upload service.
+ */
+public class MediaAddFragment extends Fragment implements LaunchCameraCallback {
+ private static final String BUNDLE_MEDIA_CAPTURE_PATH = "mediaCapturePath";
+ private String mMediaCapturePath = "";
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // This view doesn't really matter as this fragment is invisible
+
+ if (savedInstanceState != null && savedInstanceState.getString(BUNDLE_MEDIA_CAPTURE_PATH) != null)
+ mMediaCapturePath = savedInstanceState.getString(BUNDLE_MEDIA_CAPTURE_PATH);
+
+ return inflater.inflate(R.layout.actionbar_add_media_cell, container, false);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mMediaCapturePath != null && !mMediaCapturePath.equals(""))
+ outState.putString(BUNDLE_MEDIA_CAPTURE_PATH, mMediaCapturePath);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // register context for change in connection status
+ getActivity().registerReceiver(mReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+ }
+
+ @Override
+ public void onStop() {
+ getActivity().unregisterReceiver(mReceiver);
+ super.onStop();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ resumeMediaUploadService();
+ }
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
+ // Coming from zero connection. Re-register upload intent.
+ resumeMediaUploadService();
+ }
+ }
+ };
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (data != null || requestCode == RequestCodes.TAKE_PHOTO ||
+ requestCode == RequestCodes.TAKE_VIDEO) {
+ String path;
+
+ switch (requestCode) {
+ case RequestCodes.PICTURE_LIBRARY:
+ case RequestCodes.VIDEO_LIBRARY:
+ Uri imageUri = data.getData();
+ fetchMedia(imageUri);
+ break;
+ case RequestCodes.TAKE_PHOTO:
+ if (resultCode == Activity.RESULT_OK) {
+ path = mMediaCapturePath;
+ mMediaCapturePath = null;
+ queueFileForUpload(path);
+ }
+ break;
+ case RequestCodes.TAKE_VIDEO:
+ if (resultCode == Activity.RESULT_OK) {
+ path = getRealPathFromURI(MediaUtils.getLastRecordedVideoUri(getActivity()));
+ queueFileForUpload(path);
+ }
+ break;
+ }
+ }
+ }
+
+ private void fetchMedia(Uri mediaUri) {
+ if (!MediaUtils.isInMediaStore(mediaUri)) {
+ // Create an AsyncTask to download the file
+ new DownloadMediaTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mediaUri);
+ } else {
+ // It is a regular local media file
+ String path = getRealPathFromURI(mediaUri);
+ queueFileForUpload(path);
+ }
+ }
+
+ private String getRealPathFromURI(Uri uri) {
+ String path;
+ if ("content".equals(uri.getScheme())) {
+ path = getRealPathFromContentURI(uri);
+ } else if ("file".equals(uri.getScheme())) {
+ path = uri.getPath();
+ } else {
+ path = uri.toString();
+ }
+ return path;
+ }
+
+ private String getRealPathFromContentURI(Uri contentUri) {
+ if (contentUri == null)
+ return null;
+
+ String[] proj = { MediaStore.Images.Media.DATA };
+ CursorLoader loader = new CursorLoader(getActivity(), contentUri, proj, null, null, null);
+ Cursor cursor = loader.loadInBackground();
+
+ if (cursor == null)
+ return null;
+
+ int column_index = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
+ if (column_index == -1) {
+ cursor.close();
+ return null;
+ }
+
+ String path;
+ if (cursor.moveToFirst()) {
+ path = cursor.getString(column_index);
+ } else {
+ path = null;
+ }
+
+ cursor.close();
+ return path;
+ }
+
+ private void queueFileForUpload(String path) {
+ if (path == null || path.equals("")) {
+ Toast.makeText(getActivity(), "Error opening file", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Blog blog = WordPress.getCurrentBlog();
+
+ File file = new File(path);
+ if (!file.exists()) {
+ return;
+ }
+
+ String mimeType = MediaUtils.getMediaFileMimeType(file);
+ String fileName = MediaUtils.getMediaFileName(file, mimeType);
+
+ MediaFile mediaFile = new MediaFile();
+ mediaFile.setBlogId(String.valueOf(blog.getLocalTableBlogId()));
+ mediaFile.setFileName(fileName);
+ mediaFile.setFilePath(path);
+ mediaFile.setUploadState("queued");
+ mediaFile.setDateCreatedGMT(System.currentTimeMillis());
+ mediaFile.setMediaId(String.valueOf(System.currentTimeMillis()));
+ if (mimeType != null && mimeType.startsWith("image")) {
+ // get width and height
+ BitmapFactory.Options bfo = new BitmapFactory.Options();
+ bfo.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(path, bfo);
+ mediaFile.setWidth(bfo.outWidth);
+ mediaFile.setHeight(bfo.outHeight);
+ }
+
+ if (!TextUtils.isEmpty(mimeType)) {
+ mediaFile.setMimeType(mimeType);
+ }
+ WordPress.wpDB.saveMediaFile(mediaFile);
+ EventBus.getDefault().post(new MediaChanged(String.valueOf(blog.getLocalTableBlogId()), mediaFile.getMediaId()));
+ startMediaUploadService();
+ }
+
+ private void startMediaUploadService() {
+ if (NetworkUtils.isNetworkAvailable(getActivity())) {
+ getActivity().startService(new Intent(getActivity(), MediaUploadService.class));
+ }
+ }
+
+ private void resumeMediaUploadService() {
+ startMediaUploadService();
+ }
+
+ @Override
+ public void onMediaCapturePathReady(String mediaCapturePath) {
+ mMediaCapturePath = mediaCapturePath;
+ }
+
+ public void launchCamera() {
+ WordPressMediaUtils.launchCamera(this, BuildConfig.APPLICATION_ID, this);
+ }
+
+ public void launchVideoCamera() {
+ WordPressMediaUtils.launchVideoCamera(this);
+ }
+
+ public void launchVideoLibrary() {
+ WordPressMediaUtils.launchVideoLibrary(this);
+ }
+
+ public void launchPictureLibrary() {
+ WordPressMediaUtils.launchPictureLibrary(this);
+ }
+
+ public void addToQueue(String mediaId) {
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ WordPress.wpDB.updateMediaUploadState(blogId, mediaId, MediaUploadState.QUEUED);
+ startMediaUploadService();
+ }
+
+ public void uploadList(List<Uri> uriList) {
+ for (Uri uri : uriList) {
+ fetchMedia(uri);
+ }
+ }
+
+ private class DownloadMediaTask extends AsyncTask<Uri, Integer, Uri> {
+ @Override
+ protected Uri doInBackground(Uri... uris) {
+ Uri imageUri = uris[0];
+ return MediaUtils.downloadExternalMedia(getActivity(), imageUri);
+ }
+
+ protected void onPostExecute(Uri newUri) {
+ if (getActivity() == null)
+ return;
+
+ if (newUri != null) {
+ String path = getRealPathFromURI(newUri);
+ queueFileForUpload(path);
+ }
+ else
+ Toast.makeText(getActivity(), getString(R.string.error_downloading_image), Toast.LENGTH_SHORT).show();
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaBrowserActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaBrowserActivity.java
new file mode 100644
index 000000000..2e85481fb
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaBrowserActivity.java
@@ -0,0 +1,583 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.ColorDrawable;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v4.view.MenuItemCompat.OnActionExpandListener;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.SearchView;
+import android.support.v7.widget.SearchView.OnQueryTextListener;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.Toast;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.FeatureSet;
+import org.wordpress.android.ui.ActivityId;
+import org.wordpress.android.ui.media.MediaEditFragment.MediaEditFragmentCallback;
+import org.wordpress.android.ui.media.MediaGridFragment.Filter;
+import org.wordpress.android.ui.media.MediaGridFragment.MediaGridListener;
+import org.wordpress.android.ui.media.MediaItemFragment.MediaItemFragmentCallback;
+import org.wordpress.android.ui.media.services.MediaDeleteService;
+import org.wordpress.android.ui.media.services.MediaEvents;
+import org.wordpress.android.util.ActivityUtils;
+import org.wordpress.android.util.NetworkUtils;
+import org.wordpress.android.util.PermissionUtils;
+import org.wordpress.android.util.ToastUtils;
+import org.xmlrpc.android.ApiHelper;
+import org.xmlrpc.android.ApiHelper.GetFeatures.Callback;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * The main activity in which the user can browse their media.
+ */
+public class MediaBrowserActivity extends AppCompatActivity implements MediaGridListener,
+ MediaItemFragmentCallback, OnQueryTextListener, OnActionExpandListener,
+ MediaEditFragmentCallback {
+ private static final String SAVED_QUERY = "SAVED_QUERY";
+ public static final int MEDIA_PERMISSION_REQUEST_CODE = 1;
+
+ private MediaGridFragment mMediaGridFragment;
+ private MediaItemFragment mMediaItemFragment;
+ private MediaEditFragment mMediaEditFragment;
+ private MediaAddFragment mMediaAddFragment;
+ private PopupWindow mAddMediaPopup;
+
+ private SearchView mSearchView;
+ private MenuItem mSearchMenuItem;
+ private Menu mMenu;
+ private FeatureSet mFeatureSet;
+ private String mQuery;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
+ // Coming from zero connection. Continue what's pending for delete
+ int blogId = WordPress.getCurrentLocalTableBlogId();
+ if (blogId != -1 && WordPress.wpDB.hasMediaDeleteQueueItems(blogId)) {
+ startMediaDeleteService();
+ }
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // This should be removed when #2734 is fixed
+ if (WordPress.getCurrentBlog() == null) {
+ ToastUtils.showToast(this, R.string.blog_not_found, ToastUtils.Duration.SHORT);
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.media_browser_activity);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayShowTitleEnabled(true);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle(R.string.media);
+
+ FragmentManager fm = getFragmentManager();
+ fm.addOnBackStackChangedListener(mOnBackStackChangedListener);
+ FragmentTransaction ft = fm.beginTransaction();
+
+ mMediaAddFragment = (MediaAddFragment) fm.findFragmentById(R.id.mediaAddFragment);
+ mMediaGridFragment = (MediaGridFragment) fm.findFragmentById(R.id.mediaGridFragment);
+
+ mMediaItemFragment = (MediaItemFragment) fm.findFragmentByTag(MediaItemFragment.TAG);
+ if (mMediaItemFragment != null)
+ ft.hide(mMediaGridFragment);
+
+ mMediaEditFragment = (MediaEditFragment) fm.findFragmentByTag(MediaEditFragment.TAG);
+ if (mMediaEditFragment != null && !mMediaEditFragment.isInLayout())
+ ft.hide(mMediaItemFragment);
+
+ ft.commitAllowingStateLoss();
+
+ setupAddMenuPopup();
+
+ String action = getIntent().getAction();
+ if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+ // We arrived here from a share action
+ uploadSharedFiles();
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ registerReceiver(mReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+ EventBus.getDefault().register(this);
+ }
+
+ @Override
+ public void onStop() {
+ EventBus.getDefault().unregister(this);
+ unregisterReceiver(mReceiver);
+ super.onStop();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(SAVED_QUERY, mQuery);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mQuery = savedInstanceState.getString(SAVED_QUERY);
+ }
+
+ private void uploadSharedFiles() {
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ final List<Uri> multi_stream;
+ if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+ multi_stream = intent.getParcelableArrayListExtra((Intent.EXTRA_STREAM));
+ } else {
+ multi_stream = new ArrayList<>();
+ multi_stream.add((Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM));
+ }
+ mMediaAddFragment.uploadList(multi_stream);
+
+ // clear the intent's action, so that in case the user rotates, we don't re-upload the same
+ // files
+ getIntent().setAction(null);
+ }
+
+ private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
+ public void onBackStackChanged() {
+ FragmentManager manager = getFragmentManager();
+ MediaGridFragment mediaGridFragment = (MediaGridFragment)manager.findFragmentById(R.id.mediaGridFragment);
+ if (mediaGridFragment.isVisible()) {
+ mediaGridFragment.refreshSpinnerAdapter();
+ }
+ ActivityUtils.hideKeyboard(MediaBrowserActivity.this);
+ }
+ };
+
+ /** Setup the popup that allows you to add new media from camera, video camera or local files **/
+ private void setupAddMenuPopup() {
+ String capturePhoto = getResources().getString(R.string.media_add_popup_capture_photo);
+ String captureVideo = getResources().getString(R.string.media_add_popup_capture_video);
+ String pickPhotoFromGallery = getResources().getString(R.string.select_photo);
+ String pickVideoFromGallery = getResources().getString(R.string.select_video);
+ final ArrayAdapter<String> adapter = new ArrayAdapter<>(MediaBrowserActivity.this,
+ R.layout.actionbar_add_media_cell,
+ new String[] {
+ capturePhoto, captureVideo, pickPhotoFromGallery, pickVideoFromGallery
+ });
+
+ View layoutView = getLayoutInflater().inflate(R.layout.actionbar_add_media, null, false);
+ ListView listView = (ListView) layoutView.findViewById(R.id.actionbar_add_media_listview);
+ listView.setAdapter(adapter);
+ listView.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ adapter.notifyDataSetChanged();
+
+ if (position == 0) {
+ mMediaAddFragment.launchCamera();
+ } else if (position == 1) {
+ mMediaAddFragment.launchVideoCamera();
+ } else if (position == 2) {
+ mMediaAddFragment.launchPictureLibrary();
+ } else if (position == 3) {
+ mMediaAddFragment.launchVideoLibrary();
+ }
+
+ mAddMediaPopup.dismiss();
+ }
+ });
+
+ int width = getResources().getDimensionPixelSize(R.dimen.action_bar_spinner_width);
+
+ mAddMediaPopup = new PopupWindow(layoutView, width, ViewGroup.LayoutParams.WRAP_CONTENT, true);
+ mAddMediaPopup.setBackgroundDrawable(new ColorDrawable());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ startMediaDeleteService();
+ getFeatureSet();
+ ActivityId.trackLastActivity(ActivityId.MEDIA);
+ }
+
+ /** Get the feature set for a wordpress.com hosted blog **/
+ private void getFeatureSet() {
+ if (WordPress.getCurrentBlog() == null || !WordPress.getCurrentBlog().isDotcomFlag())
+ return;
+
+ ApiHelper.GetFeatures task = new ApiHelper.GetFeatures(new Callback() {
+ @Override
+ public void onResult(FeatureSet featureSet) {
+ mFeatureSet = featureSet;
+ }
+
+ });
+
+ List<Object> apiArgs = new ArrayList<>();
+ apiArgs.add(WordPress.getCurrentBlog());
+ task.execute(apiArgs);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ if (mSearchMenuItem != null) {
+ String tempQuery = mQuery;
+ MenuItemCompat.collapseActionView(mSearchMenuItem);
+ mQuery = tempQuery;
+ }
+ }
+
+ @Override
+ public void onMediaItemSelected(String mediaId) {
+ String tempQuery = mQuery;
+ if (mSearchView != null) {
+ mSearchView.clearFocus();
+ }
+
+ if (mSearchMenuItem != null) {
+ MenuItemCompat.collapseActionView(mSearchMenuItem);
+ }
+
+ FragmentManager fm = getFragmentManager();
+ if (fm.getBackStackEntryCount() == 0) {
+ FragmentTransaction ft = fm.beginTransaction();
+ ft.hide(mMediaGridFragment);
+ mMediaGridFragment.clearSelectedItems();
+ mMediaItemFragment = MediaItemFragment.newInstance(mediaId);
+ ft.add(R.id.media_browser_container, mMediaItemFragment, MediaItemFragment.TAG);
+ ft.addToBackStack(null);
+ ft.commitAllowingStateLoss();
+ mQuery = tempQuery;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ mMenu = menu;
+ getMenuInflater().inflate(R.menu.media, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ mSearchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
+ mSearchView.setOnQueryTextListener(this);
+
+ mSearchMenuItem = menu.findItem(R.id.menu_search);
+ MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, this);
+
+ //open search bar if we were searching for something before
+ if (!TextUtils.isEmpty(mQuery) && mMediaGridFragment != null && mMediaGridFragment.isVisible()) {
+ String tempQuery = mQuery; //temporary hold onto query
+ MenuItemCompat.expandActionView(mSearchMenuItem); //this will reset mQuery
+ onQueryTextSubmit(tempQuery);
+ mSearchView.setQuery(mQuery, true);
+ }
+
+ return super.onPrepareOptionsMenu(menu);
+
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
+ @NonNull int[] grantResults) {
+ switch (requestCode) {
+ case MEDIA_PERMISSION_REQUEST_CODE:
+ for (int grantResult : grantResults) {
+ if (grantResult == PackageManager.PERMISSION_DENIED) {
+ ToastUtils.showToast(this, getString(R.string.add_media_permission_required));
+ return;
+ }
+ }
+ showNewMediaMenu();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int i = item.getItemId();
+ if (i == android.R.id.home) {
+ onBackPressed();
+ return true;
+ } else if (i == R.id.menu_new_media) {
+ if (PermissionUtils.checkAndRequestCameraAndStoragePermissions(this, MEDIA_PERMISSION_REQUEST_CODE)) {
+ showNewMediaMenu();
+ }
+ return true;
+ } else if (i == R.id.menu_search) {
+ mSearchMenuItem = item;
+ MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, this);
+ MenuItemCompat.expandActionView(mSearchMenuItem);
+
+ mSearchView = (SearchView) item.getActionView();
+ mSearchView.setOnQueryTextListener(this);
+
+ // load last saved query
+ if (!TextUtils.isEmpty(mQuery)) {
+ onQueryTextSubmit(mQuery);
+ mSearchView.setQuery(mQuery, true);
+ }
+ return true;
+ } else if (i == R.id.menu_edit_media) {
+ String mediaId = mMediaItemFragment.getMediaId();
+ FragmentManager fm = getFragmentManager();
+
+ if (mMediaEditFragment == null || !mMediaEditFragment.isInLayout()) {
+ // phone layout: hide item details, show and update edit fragment
+ FragmentTransaction ft = fm.beginTransaction();
+
+ if (mMediaItemFragment.isVisible())
+ ft.hide(mMediaItemFragment);
+
+ mMediaEditFragment = MediaEditFragment.newInstance(mediaId);
+ ft.add(R.id.media_browser_container, mMediaEditFragment, MediaEditFragment.TAG);
+ ft.addToBackStack(null);
+ ft.commitAllowingStateLoss();
+ } else {
+ // tablet layout: update edit fragment
+ mMediaEditFragment.loadMedia(mediaId);
+ }
+
+ if (mSearchView != null) {
+ mSearchView.clearFocus();
+ }
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onMediaItemListDownloaded() {
+ if (mMediaItemFragment != null) {
+ mMediaGridFragment.setRefreshing(false);
+ if (mMediaItemFragment.isInLayout()) {
+ mMediaItemFragment.loadDefaultMedia();
+ }
+ }
+ }
+
+ @Override
+ public void onMediaItemListDownloadStart() {
+ mMediaGridFragment.setRefreshing(true);
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ if (mMediaGridFragment != null) {
+ mMediaGridFragment.search(query);
+ }
+ mQuery = query;
+ mSearchView.clearFocus();
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ if (mMediaGridFragment != null) {
+ mMediaGridFragment.search(newText);
+ }
+ mQuery = newText;
+ return true;
+ }
+
+ @Override
+ public void onResume(Fragment fragment) {
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onPause(Fragment fragment) {
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ // currently we don't support searching from within a filter, so hide it
+ if (mMediaGridFragment != null) {
+ mMediaGridFragment.setFilterVisibility(View.GONE);
+ mMediaGridFragment.setFilter(Filter.ALL);
+ }
+
+ // load last search query
+ if (!TextUtils.isEmpty(mQuery))
+ onQueryTextChange(mQuery);
+ mMenu.findItem(R.id.menu_new_media).setVisible(false);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ if (mMediaGridFragment != null) {
+ mMediaGridFragment.setFilterVisibility(View.VISIBLE);
+ mMediaGridFragment.setFilter(Filter.ALL);
+ }
+ mMenu.findItem(R.id.menu_new_media).setVisible(true);
+ return true;
+ }
+
+ public void onSavedEdit(String mediaId, boolean result) {
+ if (mMediaEditFragment != null && mMediaEditFragment.isVisible() && result) {
+ FragmentManager fm = getFragmentManager();
+ fm.popBackStack();
+
+ // refresh media item details (phone-only)
+ if (mMediaItemFragment != null)
+ mMediaItemFragment.loadMedia(mediaId);
+
+ // refresh grid
+ mMediaGridFragment.refreshMediaFromDB();
+ }
+ }
+
+ private void startMediaDeleteService() {
+ if (NetworkUtils.isNetworkAvailable(this)) {
+ startService(new Intent(this, MediaDeleteService.class));
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ FragmentManager fm = getFragmentManager();
+ if (fm.getBackStackEntryCount() > 0) {
+ fm.popBackStack();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public void onEventMainThread(MediaEvents.MediaChanged event) {
+ updateOnMediaChanged(event.mLocalBlogId, event.mMediaId);
+ }
+
+ @SuppressWarnings("unused")
+ public void onEventMainThread(MediaEvents.MediaUploadSucceeded event) {
+ updateOnMediaChanged(event.mLocalBlogId, event.mLocalMediaId);
+ }
+
+ @SuppressWarnings("unused")
+ public void onEventMainThread(MediaEvents.MediaUploadFailed event) {
+ ToastUtils.showToast(this, event.mErrorMessage, ToastUtils.Duration.LONG);
+ }
+
+ public void updateOnMediaChanged(String blogId, String mediaId) {
+ if (mediaId == null) {
+ return;
+ }
+
+ // If the media was deleted, remove it from multi select (if it was selected) and hide it from the the detail
+ // view (if it was the one displayed)
+ if (!WordPress.wpDB.mediaFileExists(blogId, mediaId)) {
+ mMediaGridFragment.removeFromMultiSelect(mediaId);
+ if (mMediaEditFragment != null && mMediaEditFragment.isVisible()
+ && mediaId.equals(mMediaEditFragment.getMediaId())) {
+ if (mMediaEditFragment.isInLayout()) {
+ mMediaEditFragment.loadMedia(null);
+ } else {
+ getFragmentManager().popBackStack();
+ }
+ }
+ }
+
+ // Update Grid view
+ mMediaGridFragment.refreshMediaFromDB();
+
+ // Update Spinner views
+ mMediaGridFragment.updateFilterText();
+ mMediaGridFragment.updateSpinnerAdapter();
+ }
+
+ @Override
+ public void onRetryUpload(String mediaId) {
+ mMediaAddFragment.addToQueue(mediaId);
+ }
+
+ public void deleteMedia(final ArrayList<String> ids) {
+ final String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ Set<String> sanitizedIds = new HashSet<>(ids.size());
+
+ // phone layout: pop the item fragment if it's visible
+ getFragmentManager().popBackStack();
+
+ // Make sure there are no media in "uploading"
+ for (String currentID : ids) {
+ if (WordPressMediaUtils.canDeleteMedia(blogId, currentID)) {
+ sanitizedIds.add(currentID);
+ }
+ }
+
+ if (sanitizedIds.size() != ids.size()) {
+ if (ids.size() == 1) {
+ Toast.makeText(this, R.string.wait_until_upload_completes, Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(this, R.string.cannot_delete_multi_media_items, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ // mark items for delete without actually deleting items yet,
+ // and then refresh the grid
+ WordPress.wpDB.setMediaFilesMarkedForDelete(blogId, sanitizedIds);
+ startMediaDeleteService();
+ if (mMediaGridFragment != null) {
+ mMediaGridFragment.clearSelectedItems();
+ mMediaGridFragment.refreshMediaFromDB();
+ }
+ }
+
+ private void showNewMediaMenu() {
+ View view = findViewById(R.id.menu_new_media);
+ if (view != null) {
+ int y_offset = getResources().getDimensionPixelSize(R.dimen.action_bar_spinner_y_offset);
+ int[] loc = new int[2];
+ view.getLocationOnScreen(loc);
+ mAddMediaPopup.showAtLocation(view, Gravity.TOP | Gravity.LEFT, loc[0],
+ loc[1] + view.getHeight() + y_offset);
+ } else {
+ // In case menu button is not on screen (declared showAsAction="ifRoom"), center the popup in the view.
+ View gridView = findViewById(R.id.media_gridview);
+ mAddMediaPopup.showAtLocation(gridView, Gravity.CENTER, 0, 0);
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaEditFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaEditFragment.java
new file mode 100644
index 000000000..6b9dbb0e8
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaEditFragment.java
@@ -0,0 +1,385 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ScrollView;
+import android.widget.Toast;
+
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.NetworkImageView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.WordPressDB;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.util.ActivityUtils;
+import org.wordpress.android.util.ImageUtils.BitmapWorkerCallback;
+import org.wordpress.android.util.ImageUtils.BitmapWorkerTask;
+import org.wordpress.android.util.MediaUtils;
+import org.xmlrpc.android.ApiHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fragment for editing media on the Media tab
+ */
+public class MediaEditFragment extends Fragment {
+ private static final String ARGS_MEDIA_ID = "media_id";
+ // also appears in the layouts, from the strings.xml
+ public static final String TAG = "MediaEditFragment";
+
+ private NetworkImageView mNetworkImageView;
+ private ImageView mLocalImageView;
+ private EditText mTitleView;
+ private EditText mCaptionView;
+ private EditText mDescriptionView;
+ private Button mSaveButton;
+
+ private MediaEditFragmentCallback mCallback;
+
+ private boolean mIsMediaUpdating = false;
+
+ private String mMediaId;
+ private ScrollView mScrollView;
+ private View mLinearLayout;
+ private ImageLoader mImageLoader;
+
+ public interface MediaEditFragmentCallback {
+ void onResume(Fragment fragment);
+ void onPause(Fragment fragment);
+ void onSavedEdit(String mediaId, boolean result);
+ }
+
+ public static MediaEditFragment newInstance(String mediaId) {
+ MediaEditFragment fragment = new MediaEditFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARGS_MEDIA_ID, mediaId);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ mImageLoader = MediaImageLoader.getInstance();
+
+ // retain this fragment across configuration changes
+ setRetainInstance(true);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ try {
+ mCallback = (MediaEditFragmentCallback) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement "
+ + MediaEditFragmentCallback.class.getSimpleName());
+ }
+ }
+
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ // set callback to null so we don't accidentally leak the activity instance
+ mCallback = null;
+ }
+
+ private boolean hasCallback() {
+ return (mCallback != null);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (hasCallback()) {
+ mCallback.onResume(this);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (hasCallback()) {
+ mCallback.onPause(this);
+ }
+ }
+
+ public String getMediaId() {
+ if (mMediaId != null) {
+ return mMediaId;
+ } else if (getArguments() != null) {
+ mMediaId = getArguments().getString(ARGS_MEDIA_ID);
+ return mMediaId;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ mScrollView = (ScrollView) inflater.inflate(R.layout.media_edit_fragment, container, false);
+
+ mLinearLayout = mScrollView.findViewById(R.id.media_edit_linear_layout);
+ mTitleView = (EditText) mScrollView.findViewById(R.id.media_edit_fragment_title);
+ mCaptionView = (EditText) mScrollView.findViewById(R.id.media_edit_fragment_caption);
+ mDescriptionView = (EditText) mScrollView.findViewById(R.id.media_edit_fragment_description);
+ mLocalImageView = (ImageView) mScrollView.findViewById(R.id.media_edit_fragment_image_local);
+ mNetworkImageView = (NetworkImageView) mScrollView.findViewById(R.id.media_edit_fragment_image_network);
+ mSaveButton = (Button) mScrollView.findViewById(R.id.media_edit_save_button);
+ mSaveButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ editMedia();
+ }
+ });
+
+ disableEditingOnOldVersion();
+
+ loadMedia(getMediaId());
+
+ return mScrollView;
+ }
+
+ private void disableEditingOnOldVersion() {
+ if (WordPressMediaUtils.isWordPressVersionWithMediaEditingCapabilities()) {
+ return;
+ }
+
+ mSaveButton.setEnabled(false);
+ mTitleView.setEnabled(false);
+ mCaptionView.setEnabled(false);
+ mDescriptionView.setEnabled(false);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ }
+
+ public void loadMedia(String mediaId) {
+ mMediaId = mediaId;
+ Blog blog = WordPress.getCurrentBlog();
+
+ if (blog != null && getActivity() != null) {
+ String blogId = String.valueOf(blog.getLocalTableBlogId());
+
+ if (mMediaId != null) {
+ Cursor cursor = WordPress.wpDB.getMediaFile(blogId, mMediaId);
+ refreshViews(cursor);
+ cursor.close();
+ } else {
+ refreshViews(null);
+ }
+ }
+ }
+
+ void editMedia() {
+ ActivityUtils.hideKeyboard(getActivity());
+ final String mediaId = this.getMediaId();
+ final String title = mTitleView.getText().toString();
+ final String description = mDescriptionView.getText().toString();
+ final Blog currentBlog = WordPress.getCurrentBlog();
+ final String caption = mCaptionView.getText().toString();
+
+ ApiHelper.EditMediaItemTask task = new ApiHelper.EditMediaItemTask(mediaId, title, description, caption,
+ new ApiHelper.GenericCallback() {
+ @Override
+ public void onSuccess() {
+ String blogId = String.valueOf(currentBlog.getLocalTableBlogId());
+ WordPress.wpDB.updateMediaFile(blogId, mediaId, title, description, caption);
+ if (getActivity() != null) {
+ Toast.makeText(getActivity(), R.string.media_edit_success, Toast.LENGTH_LONG).show();
+ }
+ setMediaUpdating(false);
+ if (hasCallback()) {
+ mCallback.onSavedEdit(mediaId, true);
+ }
+ }
+
+ @Override
+ public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) {
+ if (getActivity() != null) {
+ Toast.makeText(getActivity(), R.string.media_edit_failure, Toast.LENGTH_LONG).show();
+ getActivity().invalidateOptionsMenu();
+ }
+ setMediaUpdating(false);
+ if (hasCallback()) {
+ mCallback.onSavedEdit(mediaId, false);
+ }
+ }
+ }
+ );
+
+ List<Object> apiArgs = new ArrayList<Object>();
+ apiArgs.add(currentBlog);
+
+ if (!isMediaUpdating()) {
+ setMediaUpdating(true);
+ task.execute(apiArgs);
+ }
+ }
+
+ private void setMediaUpdating(boolean isUpdating) {
+ mIsMediaUpdating = isUpdating;
+ mSaveButton.setEnabled(!isUpdating);
+
+ if (isUpdating) {
+ mSaveButton.setText(R.string.saving);
+ } else {
+ mSaveButton.setText(R.string.save);
+ }
+ }
+
+ private boolean isMediaUpdating() {
+ return mIsMediaUpdating;
+ }
+
+ private void refreshImageView(Cursor cursor, boolean isLocal) {
+ final String imageUri;
+ if (isLocal) {
+ imageUri = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_PATH));
+ } else {
+ imageUri = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_URL));
+ }
+ if (MediaUtils.isValidImage(imageUri)) {
+ int width = cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_WIDTH));
+ int height = cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_HEIGHT));
+
+ // differentiating between tablet and phone
+ float screenWidth;
+ if (this.isInLayout()) {
+ screenWidth = mLinearLayout.getMeasuredWidth();
+ } else {
+ screenWidth = getActivity().getResources().getDisplayMetrics().widthPixels;
+ }
+ float screenHeight = getActivity().getResources().getDisplayMetrics().heightPixels;
+
+ if (width > screenWidth) {
+ height = (int) (height / (width / screenWidth));
+ } else if (height > screenHeight) {
+ width = (int) (width / (height / screenHeight));
+ }
+
+ if (isLocal) {
+ loadLocalImage(mLocalImageView, imageUri, width, height);
+ mLocalImageView.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, height));
+ } else {
+ mNetworkImageView.setImageUrl(imageUri + "?w=" + screenWidth, mImageLoader);
+ mNetworkImageView.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, height));
+ }
+ } else {
+ mNetworkImageView.setVisibility(View.GONE);
+ mLocalImageView.setVisibility(View.GONE);
+ }
+ }
+
+ private void refreshViews(Cursor cursor) {
+ if (cursor == null || !cursor.moveToFirst() || cursor.getCount() == 0) {
+ mLinearLayout.setVisibility(View.GONE);
+ return;
+ }
+
+ mLinearLayout.setVisibility(View.VISIBLE);
+
+ mScrollView.scrollTo(0, 0);
+
+ String state = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_UPLOAD_STATE));
+ boolean isLocal = MediaUtils.isLocalFile(state);
+ if (isLocal) {
+ mNetworkImageView.setVisibility(View.GONE);
+ mLocalImageView.setVisibility(View.VISIBLE);
+ } else {
+ mNetworkImageView.setVisibility(View.VISIBLE);
+ mLocalImageView.setVisibility(View.GONE);
+ }
+
+ // user can't edit local files
+ mSaveButton.setEnabled(!isLocal);
+ mTitleView.setEnabled(!isLocal);
+ mCaptionView.setEnabled(!isLocal);
+ mDescriptionView.setEnabled(!isLocal);
+
+ mMediaId = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MEDIA_ID));
+ mTitleView.setText(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_TITLE)));
+ mTitleView.requestFocus();
+ mTitleView.setSelection(mTitleView.getText().length());
+ mCaptionView.setText(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_CAPTION)));
+ mDescriptionView.setText(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_DESCRIPTION)));
+
+ refreshImageView(cursor, isLocal);
+ disableEditingOnOldVersion();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ if (!isInLayout()) {
+ inflater.inflate(R.menu.media_edit, menu);
+ }
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ if (!isInLayout()) {
+ menu.findItem(R.id.menu_new_media).setVisible(false);
+ menu.findItem(R.id.menu_search).setVisible(false);
+
+ if (!WordPressMediaUtils.isWordPressVersionWithMediaEditingCapabilities()) {
+ menu.findItem(R.id.menu_save_media).setVisible(false);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.menu_save_media) {
+ item.setActionView(R.layout.progressbar);
+ editMedia();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private synchronized void loadLocalImage(ImageView imageView, String filePath, int width, int height) {
+ if (MediaUtils.isValidImage(filePath)) {
+ imageView.setTag(filePath);
+
+ Bitmap bitmap = WordPress.getBitmapCache().get(filePath);
+ if (bitmap != null) {
+ imageView.setImageBitmap(bitmap);
+ } else {
+ BitmapWorkerTask task = new BitmapWorkerTask(imageView, width, height, new BitmapWorkerCallback() {
+ @Override
+ public void onBitmapReady(String path, ImageView imageView, Bitmap bitmap) {
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ }
+ WordPress.getBitmapCache().put(path, bitmap);
+ }
+ });
+ task.execute(filePath);
+ }
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryActivity.java
new file mode 100644
index 000000000..e3168ed45
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryActivity.java
@@ -0,0 +1,186 @@
+package org.wordpress.android.ui.media;
+
+import android.app.FragmentManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Toast;
+
+import com.sothree.slidinguppanel.SlidingUpPanelLayout;
+import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelSlideListener;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.util.helpers.MediaGallery;
+import org.wordpress.android.ui.media.MediaGallerySettingsFragment.MediaGallerySettingsCallback;
+import org.wordpress.android.util.DisplayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * An activity where the user can manage a media gallery
+ */
+public class MediaGalleryActivity extends AppCompatActivity implements MediaGallerySettingsCallback {
+ public static final int REQUEST_CODE = 3000;
+
+ // params for the gallery
+ public static final String PARAMS_MEDIA_GALLERY = "PARAMS_MEDIA_GALLERY";
+
+ // launches media picker in onCreate() if set
+ public static final String PARAMS_LAUNCH_PICKER = "PARAMS_LAUNCH_PICKER";
+
+ // result of the gallery
+ public static final String RESULT_MEDIA_GALLERY = "RESULT_MEDIA_GALLERY";
+
+ private MediaGalleryEditFragment mMediaGalleryEditFragment;
+ private MediaGallerySettingsFragment mMediaGallerySettingsFragment;
+
+ private SlidingUpPanelLayout mSlidingPanelLayout;
+ private boolean mIsPanelCollapsed = true;
+
+ private MediaGallery mMediaGallery;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (WordPress.wpDB == null) {
+ Toast.makeText(this, R.string.fatal_db_error, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ setTitle(R.string.media_gallery_edit);
+
+ setContentView(R.layout.media_gallery_activity);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
+
+ FragmentManager fm = getFragmentManager();
+
+ mMediaGallery = (MediaGallery) getIntent().getSerializableExtra(PARAMS_MEDIA_GALLERY);
+ if (mMediaGallery == null) {
+ mMediaGallery = new MediaGallery();
+ }
+
+ mMediaGalleryEditFragment = (MediaGalleryEditFragment) fm.findFragmentById(R.id.mediaGalleryEditFragment);
+ mMediaGallerySettingsFragment = (MediaGallerySettingsFragment) fm.findFragmentById(
+ R.id.mediaGallerySettingsFragment);
+ if (savedInstanceState == null) {
+ // if not null, the fragments will remember its state
+ mMediaGallerySettingsFragment.setRandom(mMediaGallery.isRandom());
+ mMediaGallerySettingsFragment.setNumColumns(mMediaGallery.getNumColumns());
+ mMediaGallerySettingsFragment.setType(mMediaGallery.getType());
+ mMediaGalleryEditFragment.setMediaIds(mMediaGallery.getIds());
+ }
+
+ mSlidingPanelLayout = (SlidingUpPanelLayout) findViewById(R.id.media_gallery_root);
+ if (mSlidingPanelLayout != null) {
+ // sliding panel layout is on phone only
+
+ mSlidingPanelLayout.setDragView(mMediaGallerySettingsFragment.getDragView());
+ mSlidingPanelLayout.setPanelHeight(DisplayUtils.dpToPx(this, 48));
+ mSlidingPanelLayout.setPanelSlideListener(new PanelSlideListener() {
+ @Override
+ public void onPanelSlide(View panel, float slideOffset) {
+ }
+
+ @Override
+ public void onPanelExpanded(View panel) {
+ mMediaGallerySettingsFragment.onPanelExpanded();
+ mIsPanelCollapsed = false;
+ }
+
+ @Override
+ public void onPanelCollapsed(View panel) {
+ mMediaGallerySettingsFragment.onPanelCollapsed();
+ mIsPanelCollapsed = true;
+ }
+ });
+ }
+
+ if (getIntent().hasExtra(PARAMS_LAUNCH_PICKER)) {
+ handleAddMedia();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.media_gallery, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.menu_add_media) {
+ handleAddMedia();
+ return true;
+ } else if (item.getItemId() == R.id.menu_save) {
+ handleSaveMedia();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == MediaGalleryPickerActivity.REQUEST_CODE) {
+ if (resultCode == RESULT_OK) {
+ ArrayList<String> ids = data.getStringArrayListExtra(MediaGalleryPickerActivity.RESULT_IDS);
+ mMediaGalleryEditFragment.setMediaIds(ids);
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ private void handleAddMedia() {
+ // need to make MediaGalleryAdd into an activity rather than a fragment because I can't add this fragment
+ // on top of the slidingpanel layout (since it needs to be the root layout)
+
+ ArrayList<String> mediaIds = mMediaGalleryEditFragment.getMediaIds();
+
+ Intent intent = new Intent(this, MediaGalleryPickerActivity.class);
+ intent.putExtra(MediaGalleryPickerActivity.PARAM_SELECTED_IDS, mediaIds);
+ startActivityForResult(intent, MediaGalleryPickerActivity.REQUEST_CODE);
+ }
+
+ private void handleSaveMedia() {
+ Intent intent = new Intent();
+ ArrayList<String> ids = mMediaGalleryEditFragment.getMediaIds();
+ boolean isRandom = mMediaGallerySettingsFragment.isRandom();
+ int numColumns = mMediaGallerySettingsFragment.getNumColumns();
+ String type = mMediaGallerySettingsFragment.getType();
+
+ mMediaGallery.setIds(ids);
+ mMediaGallery.setRandom(isRandom);
+ mMediaGallery.setNumColumns(numColumns);
+ mMediaGallery.setType(type);
+
+ intent.putExtra(RESULT_MEDIA_GALLERY, mMediaGallery);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mSlidingPanelLayout != null && !mIsPanelCollapsed) {
+ mSlidingPanelLayout.collapsePane();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onReverseClicked() {
+ mMediaGalleryEditFragment.reverseIds();
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryAdapter.java
new file mode 100644
index 000000000..93a24ab2f
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryAdapter.java
@@ -0,0 +1,140 @@
+package org.wordpress.android.ui.media;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.NetworkImageView;
+import com.mobeta.android.dslv.ResourceDragSortCursorAdapter;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.WordPressDB;
+import org.wordpress.android.util.MediaUtils;
+import org.wordpress.android.util.StringUtils;
+
+/**
+ * Adapter for a drag-sort listview where the user can drag media items to sort their order
+ * for a media gallery
+ */
+class MediaGalleryAdapter extends ResourceDragSortCursorAdapter {
+ private ImageLoader mImageLoader;
+
+ public MediaGalleryAdapter(Context context, int layout, Cursor c, boolean autoRequery, ImageLoader imageLoader) {
+ super(context, layout, c, autoRequery);
+ setImageLoader(imageLoader);
+ }
+
+ void setImageLoader(ImageLoader imageLoader) {
+ if (imageLoader != null) {
+ mImageLoader = imageLoader;
+ } else {
+ mImageLoader = WordPress.imageLoader;
+ }
+ }
+
+ private static class GridViewHolder {
+ private final TextView filenameView;
+ private final TextView titleView;
+ private final TextView uploadDateView;
+ private final ImageView imageView;
+ private final TextView fileTypeView;
+ private final TextView dimensionView;
+
+ GridViewHolder(View view) {
+ filenameView = (TextView) view.findViewById(R.id.media_grid_item_filename);
+ titleView = (TextView) view.findViewById(R.id.media_grid_item_name);
+ uploadDateView = (TextView) view.findViewById(R.id.media_grid_item_upload_date);
+ imageView = (ImageView) view.findViewById(R.id.media_grid_item_image);
+ fileTypeView = (TextView) view.findViewById(R.id.media_grid_item_filetype);
+ dimensionView = (TextView) view.findViewById(R.id.media_grid_item_dimension);
+ }
+ }
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ final GridViewHolder holder;
+ if (view.getTag() instanceof GridViewHolder) {
+ holder = (GridViewHolder) view.getTag();
+ } else {
+ holder = new GridViewHolder(view);
+ view.setTag(holder);
+ }
+
+ String state = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_UPLOAD_STATE));
+ boolean isLocalFile = MediaUtils.isLocalFile(state);
+
+ // file name
+ String fileName = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_NAME));
+ if (holder.filenameView != null) {
+ holder.filenameView.setText(String.format(context.getString(R.string.media_file_name), fileName));
+ }
+
+ // title of media
+ String title = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_TITLE));
+ if (title == null || title.equals(""))
+ title = fileName;
+ holder.titleView.setText(title);
+
+ // upload date
+ if (holder.uploadDateView != null) {
+ String date = MediaUtils.getDate(cursor.getLong(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_DATE_CREATED_GMT)));
+ holder.uploadDateView.setText(String.format(context.getString(R.string.media_uploaded_on), date));
+ }
+
+ // load image
+ if (isLocalFile) {
+ // should not be local file
+ } else {
+ loadNetworkImage(cursor, (NetworkImageView) holder.imageView);
+ }
+
+ // get the file extension from the fileURL
+ String filePath = StringUtils.notNullStr(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_PATH)));
+ if (filePath.isEmpty())
+ filePath = StringUtils.notNullStr(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_URL)));
+
+ // file type
+ String fileExtension = filePath.replaceAll(".*\\.(\\w+)$", "$1").toUpperCase();
+ if (holder.fileTypeView != null) {
+ holder.fileTypeView.setText(String.format(context.getString(R.string.media_file_type), fileExtension));
+ }
+
+ // dimensions
+ if (holder.dimensionView != null) {
+ if( MediaUtils.isValidImage(filePath)) {
+ int width = cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_WIDTH));
+ int height = cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_HEIGHT));
+
+ if (width > 0 && height > 0) {
+ String dimensions = width + "x" + height;
+ holder.dimensionView.setText(String.format(context.getString(R.string.media_dimensions),
+ dimensions));
+ holder.dimensionView.setVisibility(View.VISIBLE);
+ }
+ } else {
+ holder.dimensionView.setVisibility(View.GONE);
+ }
+ }
+
+ }
+
+ private void loadNetworkImage(Cursor cursor, NetworkImageView imageView) {
+ String thumbnailURL = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_THUMBNAIL_URL));
+ if (thumbnailURL == null) {
+ imageView.setImageUrl(null, null);
+ return;
+ }
+
+ Uri uri = Uri.parse(thumbnailURL);
+ if (uri != null && MediaUtils.isValidImage(uri.getLastPathSegment())) {
+ imageView.setTag(thumbnailURL);
+ imageView.setImageUrl(thumbnailURL, mImageLoader);
+ } else {
+ imageView.setImageUrl(null, null);
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryEditFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryEditFragment.java
new file mode 100644
index 000000000..e99418720
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryEditFragment.java
@@ -0,0 +1,191 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Fragment;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.os.Bundle;
+import android.util.SparseIntArray;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+
+import com.mobeta.android.dslv.DragSortListView;
+import com.mobeta.android.dslv.DragSortListView.DropListener;
+import com.mobeta.android.dslv.DragSortListView.RemoveListener;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Fragment where containing a drag-sort listview where the user can drag items
+ * to change their position in a media gallery
+ */
+public class MediaGalleryEditFragment extends Fragment implements DropListener, RemoveListener {
+ private static final String SAVED_MEDIA_IDS = "SAVED_MEDIA_IDS";
+ private MediaGalleryAdapter mGridAdapter;
+ private ArrayList<String> mIds;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ mIds = new ArrayList<String>();
+ if (savedInstanceState != null) {
+ mIds = savedInstanceState.getStringArrayList(SAVED_MEDIA_IDS);
+ }
+
+ mGridAdapter = new MediaGalleryAdapter(getActivity(), R.layout.media_gallery_item, null, true,
+ MediaImageLoader.getInstance());
+
+ View view = inflater.inflate(R.layout.media_gallery_edit_fragment, container, false);
+
+ DragSortListView gridView = (DragSortListView) view.findViewById(R.id.edit_media_gallery_gridview);
+ gridView.setAdapter(mGridAdapter);
+ gridView.setOnCreateContextMenuListener(this);
+ gridView.setDropListener(this);
+ gridView.setRemoveListener(this);
+ refreshGridView();
+
+ return view;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putStringArrayList(SAVED_MEDIA_IDS, mIds);
+ }
+
+ private void refreshGridView() {
+ if (WordPress.getCurrentBlog() == null) {
+ return;
+ }
+
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ Cursor cursor = WordPress.wpDB.getMediaFiles(blogId, mIds);
+ if (cursor == null) {
+ mGridAdapter.changeCursor(null);
+ return;
+ }
+ SparseIntArray positions = mapIdsToCursorPositions(cursor);
+ mGridAdapter.swapCursor(new OrderedCursor(cursor, positions));
+ }
+
+ private SparseIntArray mapIdsToCursorPositions(Cursor cursor) {
+ SparseIntArray positions = new SparseIntArray();
+ int size = mIds.size();
+ for (int i = 0; i < size; i++) {
+ while (cursor.moveToNext()) {
+ String mediaId = cursor.getString(cursor.getColumnIndex("mediaId"));
+ if (mediaId.equals(mIds.get(i))) {
+ positions.put(i, cursor.getPosition());
+ cursor.moveToPosition(-1);
+ break;
+ }
+ }
+ }
+ return positions;
+ }
+
+ public void setMediaIds(ArrayList<String> ids) {
+ mIds = ids;
+ refreshGridView();
+ }
+
+ public ArrayList<String> getMediaIds() {
+ return mIds;
+ }
+
+ public void reverseIds() {
+ Collections.reverse(mIds);
+ refreshGridView();
+ }
+
+ private class OrderedCursor extends CursorWrapper {
+ final int mPos;
+ private final int mCount;
+
+ // a map of custom position to cursor position
+ private final SparseIntArray mPositions;
+
+ /**
+ * A wrapper to allow for a custom order of items in a cursor *
+ */
+ public OrderedCursor(Cursor cursor, SparseIntArray positions) {
+ super(cursor);
+ cursor.moveToPosition(-1);
+ mPos = 0;
+ mCount = cursor.getCount();
+ mPositions = positions;
+ }
+
+ @Override
+ public boolean move(int offset) {
+ return this.moveToPosition(this.mPos + offset);
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return this.moveToPosition(this.mPos + 1);
+ }
+
+ @Override
+ public boolean moveToPrevious() {
+ return this.moveToPosition(this.mPos - 1);
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ return this.moveToPosition(0);
+ }
+
+ @Override
+ public boolean moveToLast() {
+ return this.moveToPosition(this.mCount - 1);
+ }
+
+ @Override
+ public boolean moveToPosition(int position) {
+ return super.moveToPosition(mPositions.get(position));
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ Cursor cursor = mGridAdapter.getCursor();
+ if (cursor == null) {
+ return;
+ }
+ cursor.moveToPosition(info.position);
+ String mediaId = cursor.getString(cursor.getColumnIndex("mediaId"));
+
+ menu.add(ContextMenu.NONE, mIds.indexOf(mediaId), ContextMenu.NONE, R.string.delete);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ int index = item.getItemId();
+ mIds.remove(index);
+ refreshGridView();
+ return true;
+ }
+
+ @Override
+ public void drop(int from, int to) {
+ String id = mIds.get(from);
+ mIds.remove(id);
+ mIds.add(to, id);
+ refreshGridView();
+ }
+
+ @Override
+ public void remove(int position) {
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryPickerActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryPickerActivity.java
new file mode 100644
index 000000000..128ac806a
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGalleryPickerActivity.java
@@ -0,0 +1,275 @@
+package org.wordpress.android.ui.media;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.AdapterView;
+import android.widget.GridView;
+import android.widget.Toast;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.util.ToastUtils;
+import org.xmlrpc.android.ApiHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An activity where the user can add new images to their media gallery or where the user
+ * can choose a single image to embed into their post.
+ */
+public class MediaGalleryPickerActivity extends AppCompatActivity
+ implements MultiChoiceModeListener, ActionMode.Callback, MediaGridAdapter.MediaGridAdapterCallback,
+ AdapterView.OnItemClickListener {
+ private GridView mGridView;
+ private MediaGridAdapter mGridAdapter;
+ private ActionMode mActionMode;
+
+ private ArrayList<String> mFilteredItems;
+ private boolean mIsSelectOneItem;
+ private boolean mIsRefreshing;
+ private boolean mHasRetrievedAllMedia;
+
+ private static final String STATE_FILTERED_ITEMS = "STATE_FILTERED_ITEMS";
+ private static final String STATE_SELECTED_ITEMS = "STATE_SELECTED_ITEMS";
+ private static final String STATE_IS_SELECT_ONE_ITEM = "STATE_IS_SELECT_ONE_ITEM";
+
+ public static final int REQUEST_CODE = 4000;
+ public static final String PARAM_SELECT_ONE_ITEM = "PARAM_SELECT_ONE_ITEM";
+ private static final String PARAM_FILTERED_IDS = "PARAM_FILTERED_IDS";
+ public static final String PARAM_SELECTED_IDS = "PARAM_SELECTED_IDS";
+ public static final String RESULT_IDS = "RESULT_IDS";
+ public static final String TAG = MediaGalleryPickerActivity.class.getSimpleName();
+
+ private int mOldMediaSyncOffset = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ArrayList<String> selectedItems = new ArrayList<String>();
+ mFilteredItems = getIntent().getStringArrayListExtra(PARAM_FILTERED_IDS);
+ mIsSelectOneItem = getIntent().getBooleanExtra(PARAM_SELECT_ONE_ITEM, false);
+
+ ArrayList<String> prevSelectedItems = getIntent().getStringArrayListExtra(PARAM_SELECTED_IDS);
+ if (prevSelectedItems != null) {
+ selectedItems.addAll(prevSelectedItems);
+ }
+
+ if (savedInstanceState != null) {
+ selectedItems.addAll(savedInstanceState.getStringArrayList(STATE_SELECTED_ITEMS));
+ mFilteredItems = savedInstanceState.getStringArrayList(STATE_FILTERED_ITEMS);
+ mIsSelectOneItem = savedInstanceState.getBoolean(STATE_IS_SELECT_ONE_ITEM, mIsSelectOneItem);
+ }
+
+ setContentView(R.layout.media_gallery_picker_layout);
+ mGridView = (GridView) findViewById(R.id.media_gallery_picker_gridview);
+ mGridView.setMultiChoiceModeListener(this);
+ mGridView.setOnItemClickListener(this);
+ mGridAdapter = new MediaGridAdapter(this, null, 0, MediaImageLoader.getInstance());
+ mGridAdapter.setSelectedItems(selectedItems);
+ mGridAdapter.setCallback(this);
+ mGridView.setAdapter(mGridAdapter);
+ if (mIsSelectOneItem) {
+ setTitle(R.string.select_from_media_library);
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ } else {
+ mActionMode = startActionMode(this);
+ mActionMode.setTitle(String.format(getString(R.string.cab_selected),
+ mGridAdapter.getSelectedItems().size()));
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshViews();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putStringArrayList(STATE_SELECTED_ITEMS, mGridAdapter.getSelectedItems());
+ outState.putStringArrayList(STATE_FILTERED_ITEMS, mFilteredItems);
+ outState.putBoolean(STATE_IS_SELECT_ONE_ITEM, mIsSelectOneItem);
+ }
+
+ private void refreshViews() {
+ if (WordPress.getCurrentBlog() == null)
+ return;
+ final String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ Cursor cursor = WordPress.wpDB.getMediaImagesForBlog(blogId, mFilteredItems);
+ if (cursor.getCount() == 0) {
+ refreshMediaFromServer(0);
+ } else {
+ mGridAdapter.swapCursor(cursor);
+ }
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ setResult(RESULT_CANCELED, new Intent());
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (mIsSelectOneItem) {
+ // Single select, just finish the activity once an item is selected
+ mGridAdapter.setItemSelected(position, true);
+ Intent intent = new Intent();
+ intent.putStringArrayListExtra(RESULT_IDS, mGridAdapter.getSelectedItems());
+ setResult(RESULT_OK, intent);
+ finish();
+ } else {
+ mGridAdapter.toggleItemSelected(position);
+ mActionMode.setTitle(String.format(getString(R.string.cab_selected),
+ mGridAdapter.getSelectedItems().size()));
+ }
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ mGridAdapter.setItemSelected(position, checked);
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ Intent intent = new Intent();
+ intent.putStringArrayListExtra(RESULT_IDS, mGridAdapter.getSelectedItems());
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public void fetchMoreData(int offset) {
+ if (!mHasRetrievedAllMedia) {
+ refreshMediaFromServer(offset);
+ }
+ }
+
+ @Override
+ public void onRetryUpload(String mediaId) {
+ }
+
+ @Override
+ public boolean isInMultiSelect() {
+ return false;
+ }
+
+ private void noMediaFinish() {
+ ToastUtils.showToast(this, R.string.media_empty_list, ToastUtils.Duration.LONG);
+ // Delay activity finish
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ }, 1500);
+ }
+
+ void refreshMediaFromServer(int offset) {
+ if (offset == 0 || !mIsRefreshing) {
+ if (offset == mOldMediaSyncOffset) {
+ // we're pulling the same data again for some reason. Pull from the beginning.
+ offset = 0;
+ }
+ mOldMediaSyncOffset = offset;
+ mIsRefreshing = true;
+ mGridAdapter.setRefreshing(true);
+
+ List<Object> apiArgs = new ArrayList<Object>();
+ apiArgs.add(WordPress.getCurrentBlog());
+
+ ApiHelper.SyncMediaLibraryTask.Callback callback = new ApiHelper.SyncMediaLibraryTask.Callback() {
+ // refersh db from server. If returned count is 0, we've retrieved all the media.
+ // stop retrieving until the user manually refreshes
+
+ @Override
+ public void onSuccess(int count) {
+ MediaGridAdapter adapter = (MediaGridAdapter) mGridView.getAdapter();
+ mHasRetrievedAllMedia = (count == 0);
+ adapter.setHasRetrievedAll(mHasRetrievedAllMedia);
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ if (WordPress.wpDB.getMediaCountAll(blogId) == 0 && count == 0) {
+ // There is no media at all
+ noMediaFinish();
+ }
+ mIsRefreshing = false;
+
+ // the activity may be gone by the time this finishes, so check for it
+ if (!isFinishing()) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ //mListener.onMediaItemListDownloaded();
+ mGridAdapter.setRefreshing(false);
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ Cursor cursor = WordPress.wpDB.getMediaImagesForBlog(blogId, mFilteredItems);
+ mGridAdapter.swapCursor(cursor);
+
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) {
+ if (errorType != ApiHelper.ErrorType.NO_ERROR) {
+ String message = errorType == ApiHelper.ErrorType.NO_UPLOAD_FILES_CAP
+ ? getString(R.string.media_error_no_permission)
+ : getString(R.string.error_refresh_media);
+ Toast.makeText(MediaGalleryPickerActivity.this, message, Toast.LENGTH_SHORT).show();
+ MediaGridAdapter adapter = (MediaGridAdapter) mGridView.getAdapter();
+ mHasRetrievedAllMedia = true;
+ adapter.setHasRetrievedAll(mHasRetrievedAllMedia);
+ }
+
+ // the activity may be cone by the time we get this, so check for it
+ if (!isFinishing()) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mIsRefreshing = false;
+ mGridAdapter.setRefreshing(false);
+ }
+ });
+ }
+
+ }
+ };
+
+ ApiHelper.SyncMediaLibraryTask getMediaTask = new ApiHelper.SyncMediaLibraryTask(offset, MediaGridFragment.Filter.ALL, callback);
+ getMediaTask.execute(apiArgs);
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGallerySettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGallerySettingsFragment.java
new file mode 100644
index 000000000..e17074499
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGallerySettingsFragment.java
@@ -0,0 +1,370 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.ui.ExpandableHeightGridView;
+
+import java.util.ArrayList;
+
+/**
+ * The fragment containing the settings for the media gallery
+ */
+public class MediaGallerySettingsFragment extends Fragment implements OnCheckedChangeListener {
+ private static final int DEFAULT_THUMBNAIL_COLUMN_COUNT = 3;
+
+ private static final String STATE_NUM_COLUMNS = "STATE_NUM_COLUMNS";
+ private static final String STATE_GALLERY_TYPE_ORD = "GALLERY_TYPE_ORD";
+ private static final String STATE_RANDOM_ORDER = "STATE_RANDOM_ORDER";
+
+ private CheckBox mThumbnailCheckbox;
+ private CheckBox mSquaresCheckbox;
+ private CheckBox mTiledCheckbox;
+ private CheckBox mCirclesCheckbox;
+ private CheckBox mSlideshowCheckbox;
+
+ private GalleryType mType;
+ private int mNumColumns;
+ private boolean mIsRandomOrder;
+
+ private View mNumColumnsContainer;
+ private View mHeader;
+
+ private CheckBox mRandomOrderCheckbox;
+
+ private boolean mAllowCheckChanged;
+
+ private TextView mTitleView;
+
+ private ScrollView mScrollView;
+
+ private CustomGridAdapter mGridAdapter;
+
+ private MediaGallerySettingsCallback mCallback;
+
+
+ private enum GalleryType {
+ DEFAULT(""),
+ TILED("rectangular"),
+ SQUARES("square"),
+ CIRCLES("circle"),
+ SLIDESHOW("slideshow");
+
+ private final String mTag;
+
+ private GalleryType(String tag) {
+ mTag = tag;
+ }
+
+ public String getTag() {
+ return mTag;
+ }
+
+ public static GalleryType getTypeFromTag(String tag) {
+ if (tag.equals("rectangular"))
+ return TILED;
+ else if (tag.equals("square"))
+ return SQUARES;
+ else if (tag.equals("circle"))
+ return CIRCLES;
+ else if (tag.equals("slideshow"))
+ return SLIDESHOW;
+ else
+ return DEFAULT;
+ }
+
+ }
+
+ public interface MediaGallerySettingsCallback {
+ public void onReverseClicked();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ try {
+ mCallback = (MediaGallerySettingsCallback) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement MediaGallerySettingsCallback");
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ mAllowCheckChanged = true;
+ mType = GalleryType.DEFAULT;
+ mNumColumns = DEFAULT_THUMBNAIL_COLUMN_COUNT;
+ mIsRandomOrder = false;
+
+ restoreState(savedInstanceState);
+
+ View view = inflater.inflate(R.layout.media_gallery_settings_fragment, container, false);
+
+ mHeader = view.findViewById(R.id.media_gallery_settings_header);
+ mScrollView = (ScrollView) view.findViewById(R.id.media_gallery_settings_content_container);
+ mTitleView = (TextView) view.findViewById(R.id.media_gallery_settings_title);
+
+ mNumColumnsContainer = view.findViewById(R.id.media_gallery_settings_num_columns_container);
+ int visible = (mType == GalleryType.DEFAULT) ? View.VISIBLE : View.GONE;
+ mNumColumnsContainer.setVisibility(visible);
+
+ ExpandableHeightGridView numColumnsGrid = (ExpandableHeightGridView) view.findViewById(R.id.media_gallery_num_columns_grid);
+ numColumnsGrid.setExpanded(true);
+ ArrayList<String> list = new ArrayList<String>(9);
+ for (int i = 1; i <= 9; i++) {
+ list.add(i + "");
+ }
+
+ mGridAdapter = new CustomGridAdapter(mNumColumns);
+ numColumnsGrid.setAdapter(mGridAdapter);
+
+ mThumbnailCheckbox = (CheckBox) view.findViewById(R.id.media_gallery_type_thumbnail_grid);
+ mTiledCheckbox = (CheckBox) view.findViewById(R.id.media_gallery_type_tiled);
+ mSquaresCheckbox = (CheckBox) view.findViewById(R.id.media_gallery_type_squares);
+ mCirclesCheckbox = (CheckBox) view.findViewById(R.id.media_gallery_type_circles);
+ mSlideshowCheckbox = (CheckBox) view.findViewById(R.id.media_gallery_type_slideshow);
+
+ setType(mType.getTag());
+
+ mThumbnailCheckbox.setOnCheckedChangeListener(this);
+ mTiledCheckbox.setOnCheckedChangeListener(this);
+ mSquaresCheckbox.setOnCheckedChangeListener(this);
+ mCirclesCheckbox.setOnCheckedChangeListener(this);
+
+ mSlideshowCheckbox.setOnCheckedChangeListener(this);
+
+ mRandomOrderCheckbox = (CheckBox) view.findViewById(R.id.media_gallery_random_checkbox);
+ mRandomOrderCheckbox.setChecked(mIsRandomOrder);
+ mRandomOrderCheckbox.setOnCheckedChangeListener(this);
+
+ Button reverseButton = (Button) view.findViewById(R.id.media_gallery_settings_reverse_button);
+ reverseButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCallback.onReverseClicked();
+ }
+ });
+
+ return view;
+ }
+
+ private void restoreState(Bundle savedInstanceState) {
+ if (savedInstanceState == null)
+ return;
+
+ mNumColumns = savedInstanceState.getInt(STATE_NUM_COLUMNS);
+ int galleryTypeOrdinal = savedInstanceState.getInt(STATE_GALLERY_TYPE_ORD);
+ mType = GalleryType.values()[galleryTypeOrdinal];
+ mIsRandomOrder = savedInstanceState.getBoolean(STATE_RANDOM_ORDER);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_NUM_COLUMNS, mNumColumns);
+ outState.putBoolean(STATE_RANDOM_ORDER, mIsRandomOrder);
+ outState.putInt(STATE_GALLERY_TYPE_ORD, mType.ordinal());
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton button, boolean checked) {
+ if (!mAllowCheckChanged)
+ return;
+
+ // the checkboxes for types are mutually exclusive, so when one is set,
+ // the others must be unset. Disable the checkChange listener during this time,
+ // and re-enable it once done.
+ mAllowCheckChanged = false;
+
+ int numColumnsContainerVisible = View.GONE;
+ int i = button.getId();
+ if (i == R.id.media_gallery_type_thumbnail_grid) {
+ numColumnsContainerVisible = View.VISIBLE;
+
+ mType = GalleryType.DEFAULT;
+ mThumbnailCheckbox.setChecked(true);
+ mSquaresCheckbox.setChecked(false);
+ mTiledCheckbox.setChecked(false);
+ mCirclesCheckbox.setChecked(false);
+ mSlideshowCheckbox.setChecked(false);
+ } else if (i == R.id.media_gallery_type_tiled) {
+ mType = GalleryType.TILED;
+ mThumbnailCheckbox.setChecked(false);
+ mTiledCheckbox.setChecked(true);
+ mSquaresCheckbox.setChecked(false);
+ mCirclesCheckbox.setChecked(false);
+ mSlideshowCheckbox.setChecked(false);
+ } else if (i == R.id.media_gallery_type_squares) {
+ mType = GalleryType.SQUARES;
+ mThumbnailCheckbox.setChecked(false);
+ mTiledCheckbox.setChecked(false);
+ mSquaresCheckbox.setChecked(true);
+ mCirclesCheckbox.setChecked(false);
+ mSlideshowCheckbox.setChecked(false);
+ } else if (i == R.id.media_gallery_type_circles) {
+ mType = GalleryType.CIRCLES;
+ mThumbnailCheckbox.setChecked(false);
+ mSquaresCheckbox.setChecked(false);
+ mTiledCheckbox.setChecked(false);
+ mCirclesCheckbox.setChecked(true);
+ mSlideshowCheckbox.setChecked(false);
+ } else if (i == R.id.media_gallery_type_slideshow) {
+ mType = GalleryType.SLIDESHOW;
+ mThumbnailCheckbox.setChecked(false);
+ mSquaresCheckbox.setChecked(false);
+ mTiledCheckbox.setChecked(false);
+ mCirclesCheckbox.setChecked(false);
+ mSlideshowCheckbox.setChecked(true);
+ } else if (i == R.id.media_gallery_random_checkbox) {
+ numColumnsContainerVisible = mNumColumnsContainer.getVisibility();
+ mIsRandomOrder = checked;
+ }
+
+ mNumColumnsContainer.setVisibility(numColumnsContainerVisible);
+
+ mAllowCheckChanged = true;
+ }
+
+ private class CustomGridAdapter extends BaseAdapter implements OnCheckedChangeListener {
+ private boolean mAllowCheckChanged;
+ private final SparseBooleanArray mCheckedPositions;
+
+ public CustomGridAdapter(int selection) {
+ mAllowCheckChanged = true;
+ mCheckedPositions = new SparseBooleanArray(9);
+ setSelection(selection);
+ }
+
+ // when a number of columns is checked, the numbers less than
+ // the one chose are also set to checked on the ui.
+ // e.g. when 3 is checked, 1 and 2 are as well.
+ private void setSelection(int selection) {
+ for (int i = 0; i < 9; i++){
+ if (i + 1 <= selection)
+ mCheckedPositions.put(i, true);
+ else
+ mCheckedPositions.put(i, false);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return 9;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return position;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ CheckBox checkbox = (CheckBox) inflater.inflate(R.layout.media_gallery_column_checkbox, parent, false);
+ checkbox.setChecked(mCheckedPositions.get(position));
+ checkbox.setTag(position);
+ checkbox.setText(String.valueOf(position + 1));
+ checkbox.setOnCheckedChangeListener(this);
+
+ return checkbox;
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton button, boolean checked) {
+ if (mAllowCheckChanged) {
+ mAllowCheckChanged = false;
+
+ int position = (Integer) button.getTag();
+ mNumColumns = position + 1;
+
+ int count = mCheckedPositions.size();
+ for (int i = 0; i < count; i++) {
+ if (i <= position)
+ mCheckedPositions.put(i, true);
+ else
+ mCheckedPositions.put(i, false);
+ }
+ notifyDataSetChanged();
+ mAllowCheckChanged = true;
+ }
+ }
+
+ }
+
+ public View getDragView() {
+ return mHeader;
+ }
+
+ public void onPanelExpanded() {
+ mTitleView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.gallery_arrow_dropdown_open, 0);
+ mScrollView.scrollTo(0, 0);
+ }
+
+ public void onPanelCollapsed() {
+ mTitleView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.gallery_arrow_dropdown_closed, 0);
+ }
+
+ public void setRandom(boolean random) {
+ mIsRandomOrder = random;
+ mRandomOrderCheckbox.setChecked(mIsRandomOrder);
+ }
+
+ public boolean isRandom() {
+ return mIsRandomOrder;
+ }
+
+ public void setType(String type) {
+ mType = GalleryType.getTypeFromTag(type);
+ switch (mType) {
+ case CIRCLES:
+ mCirclesCheckbox.setChecked(true);
+ break;
+ case DEFAULT:
+ mThumbnailCheckbox.setChecked(true);
+ break;
+ case SLIDESHOW:
+ mSlideshowCheckbox.setChecked(true);
+ break;
+ case SQUARES:
+ mSquaresCheckbox.setChecked(true);
+ break;
+ case TILED:
+ mTiledCheckbox.setChecked(true);
+ break;
+ }
+ }
+
+ public String getType() {
+ return mType.getTag();
+ }
+
+ public void setNumColumns(int numColumns) {
+ mNumColumns = numColumns;
+ mGridAdapter.setSelection(numColumns);
+ mGridAdapter.notifyDataSetChanged();
+ }
+
+ public int getNumColumns() {
+ return mNumColumns;
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridAdapter.java
new file mode 100644
index 000000000..61192a4a1
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridAdapter.java
@@ -0,0 +1,519 @@
+package org.wordpress.android.ui.media;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.widget.CursorAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.NetworkImageView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.WordPressDB;
+import org.wordpress.android.ui.CheckableFrameLayout;
+import org.wordpress.android.util.DisplayUtils;
+import org.wordpress.android.util.ImageUtils.BitmapWorkerCallback;
+import org.wordpress.android.util.ImageUtils.BitmapWorkerTask;
+import org.wordpress.android.util.MediaUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An adapter for the media gallery listViews.
+ */
+public class MediaGridAdapter extends CursorAdapter {
+ private MediaGridAdapterCallback mCallback;
+ private boolean mHasRetrievedAll;
+ private boolean mIsRefreshing;
+ private int mCursorDataCount;
+ private int mGridItemWidth;
+ private final Map<String, List<BitmapReadyCallback>> mFilePathToCallbackMap;
+ private final Handler mHandler;
+ private final int mLocalImageWidth;
+ private final LayoutInflater mInflater;
+ private ImageLoader mImageLoader;
+ private Context mContext;
+ // Must be an ArrayList (order is important for galleries)
+ private ArrayList<String> mSelectedItems;
+
+ public interface MediaGridAdapterCallback {
+ public void fetchMoreData(int offset);
+ public void onRetryUpload(String mediaId);
+ public boolean isInMultiSelect();
+ }
+
+ interface BitmapReadyCallback {
+ void onBitmapReady(Bitmap bitmap);
+ }
+
+ private static enum ViewTypes {
+ LOCAL, NETWORK, PROGRESS, SPACER
+ }
+
+ public MediaGridAdapter(Context context, Cursor c, int flags, ImageLoader imageLoader) {
+ super(context, c, flags);
+ mContext = context;
+ mSelectedItems = new ArrayList<String>();
+ mLocalImageWidth = context.getResources().getDimensionPixelSize(R.dimen.media_grid_local_image_width);
+ mInflater = LayoutInflater.from(context);
+ mFilePathToCallbackMap = new HashMap<String, List<BitmapReadyCallback>>();
+ mHandler = new Handler();
+ setImageLoader(imageLoader);
+ }
+
+ void setImageLoader(ImageLoader imageLoader) {
+ if (imageLoader != null) {
+ mImageLoader = imageLoader;
+ } else {
+ mImageLoader = WordPress.imageLoader;
+ }
+ }
+
+ public ArrayList<String> getSelectedItems() {
+ return mSelectedItems;
+ }
+
+ private static class GridViewHolder {
+ private final TextView filenameView;
+ private final TextView titleView;
+ private final TextView uploadDateView;
+ private final ImageView imageView;
+ private final TextView fileTypeView;
+ private final TextView dimensionView;
+ private final CheckableFrameLayout frameLayout;
+
+ private final TextView stateTextView;
+ private final ProgressBar progressUpload;
+ private final RelativeLayout uploadStateView;
+
+ GridViewHolder(View view) {
+ filenameView = (TextView) view.findViewById(R.id.media_grid_item_filename);
+ titleView = (TextView) view.findViewById(R.id.media_grid_item_name);
+ uploadDateView = (TextView) view.findViewById(R.id.media_grid_item_upload_date);
+ imageView = (ImageView) view.findViewById(R.id.media_grid_item_image);
+ fileTypeView = (TextView) view.findViewById(R.id.media_grid_item_filetype);
+ dimensionView = (TextView) view.findViewById(R.id.media_grid_item_dimension);
+ frameLayout = (CheckableFrameLayout) view.findViewById(R.id.media_grid_frame_layout);
+
+ stateTextView = (TextView) view.findViewById(R.id.media_grid_item_upload_state);
+ progressUpload = (ProgressBar) view.findViewById(R.id.media_grid_item_upload_progress);
+ uploadStateView = (RelativeLayout) view.findViewById(R.id.media_grid_item_upload_state_container);
+ }
+ }
+
+ @SuppressLint("DefaultLocale")
+ @Override
+ public void bindView(final View view, Context context, Cursor cursor) {
+ int itemViewType = getItemViewType(cursor.getPosition());
+
+ if (itemViewType == ViewTypes.PROGRESS.ordinal()) {
+ if (mIsRefreshing) {
+ int height = mContext.getResources().getDimensionPixelSize(R.dimen.media_grid_progress_height);
+ view.setLayoutParams(new GridView.LayoutParams(GridView.LayoutParams.MATCH_PARENT, height));
+ view.setVisibility(View.VISIBLE);
+ } else {
+ view.setLayoutParams(new GridView.LayoutParams(0, 0));
+ view.setVisibility(View.GONE);
+ }
+ return;
+ } else if (itemViewType == ViewTypes.SPACER.ordinal()) {
+ CheckableFrameLayout frameLayout = (CheckableFrameLayout) view.findViewById(R.id.media_grid_frame_layout);
+ updateGridWidth(context, frameLayout);
+ view.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ final GridViewHolder holder;
+ if (view.getTag() instanceof GridViewHolder) {
+ holder = (GridViewHolder) view.getTag();
+ } else {
+ holder = new GridViewHolder(view);
+ view.setTag(holder);
+ }
+
+ final String mediaId = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MEDIA_ID));
+
+ String state = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_UPLOAD_STATE));
+ boolean isLocalFile = MediaUtils.isLocalFile(state);
+
+ // file name
+ String fileName = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_NAME));
+ if (holder.filenameView != null) {
+ holder.filenameView.setText(fileName);
+ }
+
+ // title of media
+ String title = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_TITLE));
+ if (title == null || title.equals(""))
+ title = fileName;
+ holder.titleView.setText(title);
+
+ // upload date
+ if (holder.uploadDateView != null) {
+ String date = MediaUtils.getDate(cursor.getLong(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_DATE_CREATED_GMT)));
+ holder.uploadDateView.setText(date);
+ }
+
+ // load image
+ if (isLocalFile) {
+ loadLocalImage(cursor, holder.imageView);
+ } else {
+ String thumbUrl = WordPressMediaUtils.getNetworkThumbnailUrl(cursor, mGridItemWidth);
+ WordPressMediaUtils.loadNetworkImage(thumbUrl, (NetworkImageView) holder.imageView, mImageLoader);
+ }
+
+ // get the file extension from the fileURL
+ String mimeType = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MIME_TYPE));
+ String fileExtension = MediaUtils.getExtensionForMimeType(mimeType);
+ fileExtension = fileExtension.toUpperCase();
+ // file type
+ if (DisplayUtils.isXLarge(context) && !TextUtils.isEmpty(fileExtension)) {
+ holder.fileTypeView.setText(String.format(context.getString(R.string.media_file_type), fileExtension));
+ } else {
+ holder.fileTypeView.setText(fileExtension);
+ }
+
+ // dimensions
+ String filePath = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_URL));
+ TextView dimensionView = (TextView) view.findViewById(R.id.media_grid_item_dimension);
+ if (dimensionView != null) {
+ if( MediaUtils.isValidImage(filePath)) {
+ int width = cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_WIDTH));
+ int height = cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_HEIGHT));
+
+ if (width > 0 && height > 0) {
+ String dimensions = width + "x" + height;
+ holder.dimensionView.setText(dimensions);
+ holder.dimensionView.setVisibility(View.VISIBLE);
+ }
+ } else {
+ holder.dimensionView.setVisibility(View.GONE);
+ }
+ }
+
+ holder.frameLayout.setTag(mediaId);
+ holder.frameLayout.setChecked(mSelectedItems.contains(mediaId));
+
+ // resizing layout to fit nicely into grid view
+ updateGridWidth(context, holder.frameLayout);
+
+ // show upload state
+ if (holder.stateTextView != null) {
+ if (state != null && state.length() > 0) {
+ // show the progressbar only when the state is uploading
+ if (state.equals("uploading")) {
+ holder.progressUpload.setVisibility(View.VISIBLE);
+ } else {
+ holder.progressUpload.setVisibility(View.GONE);
+ if (state.equals("uploaded")) {
+ holder.stateTextView.setVisibility(View.GONE);
+ }
+ }
+
+ // add onclick to retry failed uploads
+ if (state.equals("failed")) {
+ state = "retry";
+ holder.stateTextView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!inMultiSelect()) {
+ ((TextView) v).setText(R.string.upload_queued);
+ v.setOnClickListener(null);
+ mCallback.onRetryUpload(mediaId);
+ }
+ }
+
+ });
+ }
+
+ holder.stateTextView.setText(state);
+ holder.uploadStateView.setVisibility(View.VISIBLE);
+ } else {
+ holder.uploadStateView.setVisibility(View.GONE);
+ }
+ }
+
+ // if we are near the end, make a call to fetch more
+ int position = cursor.getPosition();
+ if (position == mCursorDataCount - 1 && !mHasRetrievedAll) {
+ if (mCallback != null) {
+ mCallback.fetchMoreData(mCursorDataCount);
+ }
+ }
+ }
+
+ private boolean inMultiSelect() {
+ return mCallback.isInMultiSelect();
+ }
+
+ private synchronized void loadLocalImage(Cursor cursor, final ImageView imageView) {
+ final String filePath = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_PATH));
+
+ if (MediaUtils.isValidImage(filePath)) {
+ imageView.setTag(filePath);
+
+ Bitmap bitmap = WordPress.getBitmapCache().get(filePath);
+ if (bitmap != null) {
+ imageView.setImageBitmap(bitmap);
+ } else {
+ imageView.setImageBitmap(null);
+
+ boolean shouldFetch = false;
+
+ List<BitmapReadyCallback> list;
+ if (mFilePathToCallbackMap.containsKey(filePath)) {
+ list = mFilePathToCallbackMap.get(filePath);
+ } else {
+ list = new ArrayList<MediaGridAdapter.BitmapReadyCallback>();
+ shouldFetch = true;
+ mFilePathToCallbackMap.put(filePath, list);
+ }
+ list.add(new BitmapReadyCallback() {
+ @Override
+ public void onBitmapReady(Bitmap bitmap) {
+ if (imageView.getTag() instanceof String && imageView.getTag().equals(filePath))
+ imageView.setImageBitmap(bitmap);
+ }
+ });
+
+
+ if (shouldFetch) {
+ fetchBitmap(filePath);
+ }
+ }
+ } else {
+ // if not image, for now show no image.
+ imageView.setImageBitmap(null);
+ }
+ }
+
+ private void fetchBitmap(final String filePath) {
+ BitmapWorkerTask task = new BitmapWorkerTask(null, mLocalImageWidth, mLocalImageWidth, new BitmapWorkerCallback() {
+ @Override
+ public void onBitmapReady(final String path, ImageView imageView, final Bitmap bitmap) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ List<BitmapReadyCallback> callbacks = mFilePathToCallbackMap.get(path);
+ for (BitmapReadyCallback callback : callbacks) {
+ callback.onBitmapReady(bitmap);
+ }
+
+ WordPress.getBitmapCache().put(path, bitmap);
+ callbacks.clear();
+ mFilePathToCallbackMap.remove(path);
+ }
+ });
+ }
+ });
+ task.execute(filePath);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup root) {
+ int itemViewType = getItemViewType(cursor.getPosition());
+
+ // spacer and progress spinner views
+ if (itemViewType == ViewTypes.PROGRESS.ordinal()) {
+ return mInflater.inflate(R.layout.media_grid_progress, root, false);
+ } else if (itemViewType == ViewTypes.SPACER.ordinal()) {
+ return mInflater.inflate(R.layout.media_grid_item, root, false);
+ }
+
+ View view = mInflater.inflate(R.layout.media_grid_item, root, false);
+ ViewStub imageStub = (ViewStub) view.findViewById(R.id.media_grid_image_stub);
+
+ // We need to use ViewStubs to inflate the image to either:
+ // - a regular ImageView (for local images)
+ // - a FadeInNetworkImageView (for network images)
+ // This is because the NetworkImageView can't load local images.
+ // The other option would be to inflate multiple layouts, but that would lead
+ // to extra near-duplicate xml files that would need to be maintained.
+ if (itemViewType == ViewTypes.LOCAL.ordinal()) {
+ imageStub.setLayoutResource(R.layout.media_grid_image_local);
+ } else {
+ imageStub.setLayoutResource(R.layout.media_grid_image_network);
+ }
+
+ imageStub.inflate();
+
+ view.setTag(new GridViewHolder(view));
+
+ return view;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return ViewTypes.values().length;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ Cursor cursor = getCursor();
+ cursor.moveToPosition(position);
+
+ // spacer / progress cells
+ int _id = cursor.getInt(cursor.getColumnIndex("_id"));
+ if (_id < 0) {
+ if (_id == Integer.MIN_VALUE)
+ return ViewTypes.PROGRESS.ordinal();
+ else
+ return ViewTypes.SPACER.ordinal();
+ }
+
+ // regular cells
+ String state = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_UPLOAD_STATE));
+ if (MediaUtils.isLocalFile(state))
+ return ViewTypes.LOCAL.ordinal();
+ else
+ return ViewTypes.NETWORK.ordinal();
+ }
+
+ /** Updates the width of a cell to max out the space available, for phones **/
+ private void updateGridWidth(Context context, View view) {
+ setGridItemWidth();
+ int columnCount = getColumnCount(context);
+
+ if (columnCount > 1) {
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mGridItemWidth, mGridItemWidth);
+ view.setLayoutParams(params);
+ }
+ }
+
+ @Override
+ public Cursor swapCursor(Cursor newCursor) {
+ if (newCursor == null) {
+ mCursorDataCount = 0;
+ return super.swapCursor(newCursor);
+ }
+
+ mCursorDataCount = newCursor.getCount();
+
+ // to mimic the infinite the notification's infinite scroll ui
+ // (with a progress spinner on the bottom of the list), we'll need to add
+ // extra cells in the gridview:
+ // - spacer cells as fillers to place the progress spinner on the first cell (_id < 0)
+ // - progress spinner cell (_id = Integer.MIN_VALUE)
+
+ // use a matrix cursor to create the extra rows
+ MatrixCursor matrixCursor = new MatrixCursor(new String[] { "_id" });
+
+ // add spacer cells
+ int columnCount = getColumnCount(mContext);
+ int remainder = newCursor.getCount() % columnCount;
+ if (remainder > 0) {
+ int spaceCount = columnCount - remainder;
+ for (int i = 0; i < spaceCount; i++ ) {
+ int id = i - spaceCount;
+ matrixCursor.addRow(new Object[] {id + ""});
+ }
+ }
+
+ // add progress spinner cell
+ matrixCursor.addRow(new Object[] { Integer.MIN_VALUE });
+
+ // use a merge cursor to place merge the extra rows at the bottom of the newly swapped cursor
+ MergeCursor mergeCursor = new MergeCursor(new Cursor[] { newCursor, matrixCursor });
+ return super.swapCursor(mergeCursor);
+ }
+
+ /** Return the number of columns in the media grid **/
+ private int getColumnCount(Context context) {
+ return context.getResources().getInteger(R.integer.media_grid_num_columns);
+ }
+
+ public void setCallback(MediaGridAdapterCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setHasRetrievedAll(boolean b) {
+ mHasRetrievedAll = b;
+ }
+
+ public void setRefreshing(boolean refreshing) {
+ mIsRefreshing = refreshing;
+ notifyDataSetChanged();
+ }
+
+ public int getDataCount() {
+ return mCursorDataCount;
+ }
+
+ private void setGridItemWidth() {
+ int maxWidth = mContext.getResources().getDisplayMetrics().widthPixels;
+ int columnCount = getColumnCount(mContext);
+ if (columnCount > 0) {
+ int dp8 = DisplayUtils.dpToPx(mContext, 8);
+ int padding = (columnCount + 1) * dp8;
+ mGridItemWidth = (maxWidth - padding) / columnCount;
+ }
+ }
+
+ public void clearSelection() {
+ mSelectedItems.clear();
+ }
+
+ public boolean isItemSelected(String mediaId) {
+ return mSelectedItems.contains(mediaId);
+ }
+
+ public void setItemSelected(int position, boolean selected) {
+ Cursor cursor = (Cursor) getItem(position);
+ if (cursor == null) {
+ return;
+ }
+ int columnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MEDIA_ID);
+ if (columnIndex != -1) {
+ String mediaId = cursor.getString(columnIndex);
+ setItemSelected(mediaId, selected);
+ }
+ }
+
+ public void setItemSelected(String mediaId, boolean selected) {
+ if (selected) {
+ mSelectedItems.add(mediaId);
+ } else {
+ mSelectedItems.remove(mediaId);
+ }
+ notifyDataSetChanged();
+ }
+
+ public void toggleItemSelected(int position) {
+ Cursor cursor = (Cursor) getItem(position);
+ int columnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MEDIA_ID);
+ if (columnIndex != -1) {
+ String mediaId = cursor.getString(columnIndex);
+ if (mSelectedItems.contains(mediaId)) {
+ mSelectedItems.remove(mediaId);
+ } else {
+ mSelectedItems.add(mediaId);
+ }
+ notifyDataSetChanged();
+ }
+ }
+
+ public void setSelectedItems(ArrayList<String> selectedItems) {
+ mSelectedItems = selectedItems;
+ notifyDataSetChanged();
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridFragment.java
new file mode 100644
index 000000000..793a8e647
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridFragment.java
@@ -0,0 +1,836 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AbsListView.RecyclerListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.DatePicker;
+import android.widget.GridView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.ImageLoader.ImageContainer;
+import com.android.volley.toolbox.ImageLoader.ImageListener;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.ui.CheckableFrameLayout;
+import org.wordpress.android.ui.CustomSpinner;
+import org.wordpress.android.ui.EmptyViewMessageType;
+import org.wordpress.android.ui.media.MediaGridAdapter.MediaGridAdapterCallback;
+import org.wordpress.android.ui.posts.EditPostActivity;
+import org.wordpress.android.util.NetworkUtils;
+import org.wordpress.android.util.ToastUtils;
+import org.wordpress.android.util.ToastUtils.Duration;
+import org.wordpress.android.util.WPActivityUtils;
+import org.wordpress.android.util.helpers.SwipeToRefreshHelper;
+import org.wordpress.android.util.helpers.SwipeToRefreshHelper.RefreshListener;
+import org.wordpress.android.util.widgets.CustomSwipeRefreshLayout;
+import org.xmlrpc.android.ApiHelper;
+import org.xmlrpc.android.ApiHelper.SyncMediaLibraryTask.Callback;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+/**
+ * The grid displaying the media items.
+ */
+public class MediaGridFragment extends Fragment
+ implements OnItemClickListener, MediaGridAdapterCallback, RecyclerListener {
+ private static final String BUNDLE_SELECTED_STATES = "BUNDLE_SELECTED_STATES";
+ private static final String BUNDLE_IN_MULTI_SELECT_MODE = "BUNDLE_IN_MULTI_SELECT_MODE";
+ private static final String BUNDLE_SCROLL_POSITION = "BUNDLE_SCROLL_POSITION";
+ private static final String BUNDLE_HAS_RETREIEVED_ALL_MEDIA = "BUNDLE_HAS_RETREIEVED_ALL_MEDIA";
+ private static final String BUNDLE_FILTER = "BUNDLE_FILTER";
+ private static final String BUNDLE_EMPTY_VIEW_MESSAGE = "BUNDLE_EMPTY_VIEW_MESSAGE";
+
+ private static final String BUNDLE_DATE_FILTER_SET = "BUNDLE_DATE_FILTER_SET";
+ private static final String BUNDLE_DATE_FILTER_VISIBLE = "BUNDLE_DATE_FILTER_VISIBLE";
+ private static final String BUNDLE_DATE_FILTER_START_YEAR = "BUNDLE_DATE_FILTER_START_YEAR";
+ private static final String BUNDLE_DATE_FILTER_START_MONTH = "BUNDLE_DATE_FILTER_START_MONTH";
+ private static final String BUNDLE_DATE_FILTER_START_DAY = "BUNDLE_DATE_FILTER_START_DAY";
+ private static final String BUNDLE_DATE_FILTER_END_YEAR = "BUNDLE_DATE_FILTER_END_YEAR";
+ private static final String BUNDLE_DATE_FILTER_END_MONTH = "BUNDLE_DATE_FILTER_END_MONTH";
+ private static final String BUNDLE_DATE_FILTER_END_DAY = "BUNDLE_DATE_FILTER_END_DAY";
+
+ private Filter mFilter = Filter.ALL;
+ private String[] mFiltersText;
+ private GridView mGridView;
+ private MediaGridAdapter mGridAdapter;
+ private MediaGridListener mListener;
+
+ private boolean mIsRefreshing;
+ private boolean mHasRetrievedAllMedia;
+ private boolean mIsMultiSelect;
+ private String mSearchTerm;
+
+ private View mSpinnerContainer;
+ private TextView mResultView;
+ private CustomSpinner mSpinner;
+ private SwipeToRefreshHelper mSwipeToRefreshHelper;
+
+ private LinearLayout mEmptyView;
+ private TextView mEmptyViewTitle;
+ private EmptyViewMessageType mEmptyViewMessageType = EmptyViewMessageType.NO_CONTENT;
+
+ private int mOldMediaSyncOffset = 0;
+
+ private boolean mIsDateFilterSet;
+ private boolean mSpinnerHasLaunched;
+
+ private int mStartYear, mStartMonth, mStartDay, mEndYear, mEndMonth, mEndDay;
+ private AlertDialog mDatePickerDialog;
+
+ public interface MediaGridListener {
+ public void onMediaItemListDownloadStart();
+ public void onMediaItemListDownloaded();
+ public void onMediaItemSelected(String mediaId);
+ public void onRetryUpload(String mediaId);
+ }
+
+ public enum Filter {
+ ALL, IMAGES, UNATTACHED, CUSTOM_DATE;
+
+ public static Filter getFilter(int filterPos) {
+ if (filterPos > Filter.values().length)
+ return ALL;
+ else
+ return Filter.values()[filterPos];
+ }
+ }
+
+ private final OnItemSelectedListener mFilterSelectedListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ // need this to stop the bug where onItemSelected is called during initialization, before user input
+ if (!mSpinnerHasLaunched) {
+ return;
+ }
+ if (position == Filter.CUSTOM_DATE.ordinal()) {
+ mIsDateFilterSet = true;
+ }
+ setFilter(Filter.getFilter(position));
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+ mFiltersText = new String[Filter.values().length];
+ mGridAdapter = new MediaGridAdapter(getActivity(), null, 0, MediaImageLoader.getInstance());
+ mGridAdapter.setCallback(this);
+
+ View view = inflater.inflate(R.layout.media_grid_fragment, container);
+
+ mGridView = (GridView) view.findViewById(R.id.media_gridview);
+ mGridView.setOnItemClickListener(this);
+ mGridView.setRecyclerListener(this);
+ mGridView.setMultiChoiceModeListener(new MultiChoiceModeListener());
+ mGridView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL);
+ mGridView.setAdapter(mGridAdapter);
+
+ mEmptyView = (LinearLayout) view.findViewById(R.id.empty_view);
+ mEmptyViewTitle = (TextView) view.findViewById(R.id.empty_view_title);
+
+ mResultView = (TextView) view.findViewById(R.id.media_filter_result_text);
+
+ mSpinner = (CustomSpinner) view.findViewById(R.id.media_filter_spinner);
+ mSpinner.setOnItemSelectedListener(mFilterSelectedListener);
+ mSpinner.setOnItemSelectedEvenIfUnchangedListener(mFilterSelectedListener);
+
+ mSpinnerContainer = view.findViewById(R.id.media_filter_spinner_container);
+ mSpinnerContainer.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!isInMultiSelect()) {
+ mSpinnerHasLaunched = true;
+ mSpinner.performClick();
+ }
+ }
+
+ });
+
+ // swipe to refresh setup
+ mSwipeToRefreshHelper = new SwipeToRefreshHelper(getActivity(),
+ (CustomSwipeRefreshLayout) view.findViewById(R.id.ptr_layout),
+ new RefreshListener() {
+ @Override
+ public void onRefreshStarted() {
+ if (!isAdded()) {
+ return;
+ }
+ if (!NetworkUtils.checkConnection(getActivity())) {
+ updateEmptyView(EmptyViewMessageType.NETWORK_ERROR);
+ mSwipeToRefreshHelper.setRefreshing(false);
+ return;
+ }
+ refreshMediaFromServer(0, false);
+ }
+ });
+ restoreState(savedInstanceState);
+ setupSpinnerAdapter();
+
+ return view;
+ }
+
+ private void restoreState(Bundle savedInstanceState) {
+ if (savedInstanceState == null)
+ return;
+
+ boolean isInMultiSelectMode = savedInstanceState.getBoolean(BUNDLE_IN_MULTI_SELECT_MODE);
+
+ if (savedInstanceState.containsKey(BUNDLE_SELECTED_STATES)) {
+ ArrayList selectedItems = savedInstanceState.getStringArrayList(BUNDLE_SELECTED_STATES);
+ mGridAdapter.setSelectedItems(selectedItems);
+ if (isInMultiSelectMode) {
+ setFilterSpinnerVisible(mGridAdapter.getSelectedItems().size() == 0);
+ mSwipeToRefreshHelper.setEnabled(false);
+ }
+ }
+
+ mGridView.setSelection(savedInstanceState.getInt(BUNDLE_SCROLL_POSITION, 0));
+ mHasRetrievedAllMedia = savedInstanceState.getBoolean(BUNDLE_HAS_RETREIEVED_ALL_MEDIA, false);
+ mFilter = Filter.getFilter(savedInstanceState.getInt(BUNDLE_FILTER));
+ mEmptyViewMessageType = EmptyViewMessageType.getEnumFromString(savedInstanceState.
+ getString(BUNDLE_EMPTY_VIEW_MESSAGE));
+
+ mIsDateFilterSet = savedInstanceState.getBoolean(BUNDLE_DATE_FILTER_SET, false);
+ mStartDay = savedInstanceState.getInt(BUNDLE_DATE_FILTER_START_DAY);
+ mStartMonth = savedInstanceState.getInt(BUNDLE_DATE_FILTER_START_MONTH);
+ mStartYear = savedInstanceState.getInt(BUNDLE_DATE_FILTER_START_YEAR);
+ mEndDay = savedInstanceState.getInt(BUNDLE_DATE_FILTER_END_DAY);
+ mEndMonth = savedInstanceState.getInt(BUNDLE_DATE_FILTER_END_MONTH);
+ mEndYear = savedInstanceState.getInt(BUNDLE_DATE_FILTER_END_YEAR);
+
+ boolean datePickerShowing = savedInstanceState.getBoolean(BUNDLE_DATE_FILTER_VISIBLE);
+ if (datePickerShowing)
+ showDatePicker();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ saveState(outState);
+ }
+
+ private void saveState(Bundle outState) {
+ outState.putStringArrayList(BUNDLE_SELECTED_STATES, mGridAdapter.getSelectedItems());
+ outState.putInt(BUNDLE_SCROLL_POSITION, mGridView.getFirstVisiblePosition());
+ outState.putBoolean(BUNDLE_HAS_RETREIEVED_ALL_MEDIA, mHasRetrievedAllMedia);
+ outState.putBoolean(BUNDLE_IN_MULTI_SELECT_MODE, isInMultiSelect());
+ outState.putInt(BUNDLE_FILTER, mFilter.ordinal());
+ outState.putString(BUNDLE_EMPTY_VIEW_MESSAGE, mEmptyViewMessageType.name());
+
+ outState.putBoolean(BUNDLE_DATE_FILTER_SET, mIsDateFilterSet);
+ outState.putBoolean(BUNDLE_DATE_FILTER_VISIBLE, (mDatePickerDialog != null && mDatePickerDialog.isShowing()));
+ outState.putInt(BUNDLE_DATE_FILTER_START_DAY, mStartDay);
+ outState.putInt(BUNDLE_DATE_FILTER_START_MONTH, mStartMonth);
+ outState.putInt(BUNDLE_DATE_FILTER_START_YEAR, mStartYear);
+ outState.putInt(BUNDLE_DATE_FILTER_END_DAY, mEndDay);
+ outState.putInt(BUNDLE_DATE_FILTER_END_MONTH, mEndMonth);
+ outState.putInt(BUNDLE_DATE_FILTER_END_YEAR, mEndYear);
+ }
+
+ private void setupSpinnerAdapter() {
+ if (getActivity() == null || WordPress.getCurrentBlog() == null) {
+ return;
+ }
+
+ updateFilterText();
+
+ Context context = WPActivityUtils.getThemedContext(getActivity());
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, R.layout.spinner_menu_dropdown_item, mFiltersText);
+ mSpinner.setAdapter(adapter);
+ mSpinner.setSelection(mFilter.ordinal());
+ }
+
+ public void refreshSpinnerAdapter() {
+ updateFilterText();
+ updateSpinnerAdapter();
+ setFilter(mFilter);
+ }
+
+ void resetSpinnerAdapter() {
+ setFiltersText(0, 0, 0);
+ updateSpinnerAdapter();
+ }
+
+ void updateFilterText() {
+ if (WordPress.currentBlog == null)
+ return;
+
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+
+ int countAll = WordPress.wpDB.getMediaCountAll(blogId);
+ int countImages = WordPress.wpDB.getMediaCountImages(blogId);
+ int countUnattached = WordPress.wpDB.getMediaCountUnattached(blogId);
+
+ setFiltersText(countAll, countImages, countUnattached);
+ }
+
+ private void setFiltersText(int countAll, int countImages, int countUnattached) {
+ mFiltersText[0] = getResources().getString(R.string.all) + " (" + countAll + ")";
+ mFiltersText[1] = getResources().getString(R.string.images) + " (" + countImages + ")";
+ mFiltersText[2] = getResources().getString(R.string.unattached) + " (" + countUnattached + ")";
+ mFiltersText[3] = getResources().getString(R.string.custom_date) + "...";
+ }
+
+ void updateSpinnerAdapter() {
+ ArrayAdapter<String> adapter = (ArrayAdapter<String>) mSpinner.getAdapter();
+ if (adapter != null) {
+ adapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ try {
+ mListener = (MediaGridListener) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement MediaGridListener");
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshSpinnerAdapter();
+ refreshMediaFromDB();
+ }
+
+ public void refreshMediaFromDB() {
+ setFilter(mFilter);
+ if (isAdded() && mGridAdapter.getDataCount() == 0) {
+ if (NetworkUtils.isNetworkAvailable(getActivity())) {
+ if (!mHasRetrievedAllMedia) {
+ refreshMediaFromServer(0, true);
+ }
+ } else {
+ updateEmptyView(EmptyViewMessageType.NETWORK_ERROR);
+ }
+ }
+ }
+
+ public void refreshMediaFromServer(int offset, final boolean auto) {
+ if (!NetworkUtils.isNetworkAvailable(getActivity())) {
+ updateEmptyView(EmptyViewMessageType.NETWORK_ERROR);
+ setRefreshing(false);
+ return;
+ }
+
+ // do not refresh if custom date filter is shown
+ if (WordPress.getCurrentBlog() == null || mFilter == Filter.CUSTOM_DATE) {
+ setRefreshing(false);
+ return;
+ }
+
+ // do not refresh if in search
+ if (mSearchTerm != null && mSearchTerm.length() > 0) {
+ setRefreshing(false);
+ return;
+ }
+
+ if (offset == 0 || !mIsRefreshing) {
+ if (offset == mOldMediaSyncOffset) {
+ // we're pulling the same data again for some reason. Pull from the beginning.
+ offset = 0;
+ }
+ mOldMediaSyncOffset = offset;
+
+ mIsRefreshing = true;
+ updateEmptyView(EmptyViewMessageType.LOADING);
+ mListener.onMediaItemListDownloadStart();
+ mGridAdapter.setRefreshing(true);
+
+ List<Object> apiArgs = new ArrayList<Object>();
+ apiArgs.add(WordPress.getCurrentBlog());
+
+ Callback callback = new Callback() {
+ // refresh db from server. If returned count is 0, we've retrieved all the media.
+ // stop retrieving until the user manually refreshes
+
+ @Override
+ public void onSuccess(int count) {
+ MediaGridAdapter adapter = (MediaGridAdapter) mGridView.getAdapter();
+ mHasRetrievedAllMedia = (count == 0);
+ adapter.setHasRetrievedAll(mHasRetrievedAllMedia);
+
+ mIsRefreshing = false;
+
+ // the activity may be gone by the time this finishes, so check for it
+ if (getActivity() != null && MediaGridFragment.this.isVisible()) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ refreshSpinnerAdapter();
+ updateEmptyView(EmptyViewMessageType.NO_CONTENT);
+ if (!auto) {
+ mGridView.setSelection(0);
+ }
+ mListener.onMediaItemListDownloaded();
+ mGridAdapter.setRefreshing(false);
+ mSwipeToRefreshHelper.setRefreshing(false);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onFailure(final ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) {
+ if (errorType != ApiHelper.ErrorType.NO_ERROR) {
+ if (getActivity() != null) {
+ if (errorType != ApiHelper.ErrorType.NO_UPLOAD_FILES_CAP) {
+ ToastUtils.showToast(getActivity(), getString(R.string.error_refresh_media),
+ Duration.LONG);
+ } else {
+ if (mEmptyView == null || mEmptyView.getVisibility() != View.VISIBLE) {
+ ToastUtils.showToast(getActivity(), getString(
+ R.string.media_error_no_permission));
+ }
+ }
+ }
+ MediaGridAdapter adapter = (MediaGridAdapter) mGridView.getAdapter();
+ mHasRetrievedAllMedia = true;
+ adapter.setHasRetrievedAll(mHasRetrievedAllMedia);
+ }
+
+ // the activity may be cone by the time we get this, so check for it
+ if (getActivity() != null && MediaGridFragment.this.isVisible()) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mIsRefreshing = false;
+ mListener.onMediaItemListDownloaded();
+ mGridAdapter.setRefreshing(false);
+ mSwipeToRefreshHelper.setRefreshing(false);
+ if (errorType == ApiHelper.ErrorType.NO_UPLOAD_FILES_CAP) {
+ updateEmptyView(EmptyViewMessageType.PERMISSION_ERROR);
+ } else {
+ updateEmptyView(EmptyViewMessageType.GENERIC_ERROR);
+ }
+ }
+ });
+ }
+ }
+ };
+
+ ApiHelper.SyncMediaLibraryTask getMediaTask = new ApiHelper.SyncMediaLibraryTask(offset, mFilter, callback);
+ getMediaTask.execute(apiArgs);
+ }
+ }
+
+ public void search(String searchTerm) {
+ mSearchTerm = searchTerm;
+ Blog blog = WordPress.getCurrentBlog();
+ if (blog != null) {
+ String blogId = String.valueOf(blog.getLocalTableBlogId());
+ Cursor cursor = WordPress.wpDB.getMediaFilesForBlog(blogId, searchTerm);
+ mGridAdapter.changeCursor(cursor);
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Cursor cursor = ((MediaGridAdapter) parent.getAdapter()).getCursor();
+ String mediaId = cursor.getString(cursor.getColumnIndex("mediaId"));
+ mListener.onMediaItemSelected(mediaId);
+ }
+
+ public void setFilterVisibility(int visibility) {
+ if (mSpinner != null) {
+ mSpinner.setVisibility(visibility);
+ }
+ }
+
+ private void updateEmptyView(EmptyViewMessageType emptyViewMessageType) {
+ if (mEmptyView != null) {
+ if (mGridAdapter.getDataCount() == 0) {
+ int stringId = 0;
+
+ switch (emptyViewMessageType) {
+ case LOADING:
+ stringId = R.string.media_fetching;
+ break;
+ case NO_CONTENT:
+ stringId = R.string.media_empty_list;
+ break;
+ case NETWORK_ERROR:
+ // Don't overwrite NO_CONTENT_CUSTOM_DATE message, since refresh is disabled with that filter on
+ if (mEmptyViewMessageType == EmptyViewMessageType.NO_CONTENT_CUSTOM_DATE) {
+ mEmptyView.setVisibility(View.VISIBLE);
+ return;
+ }
+ stringId = R.string.no_network_message;
+ break;
+ case PERMISSION_ERROR:
+ stringId = R.string.media_error_no_permission;
+ break;
+ case GENERIC_ERROR:
+ stringId = R.string.error_refresh_media;
+ break;
+ case NO_CONTENT_CUSTOM_DATE:
+ stringId = R.string.media_empty_list_custom_date;
+ break;
+ }
+
+ mEmptyViewTitle.setText(getText(stringId));
+ mEmptyViewMessageType = emptyViewMessageType;
+ mEmptyView.setVisibility(View.VISIBLE);
+ } else {
+ mEmptyView.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void hideEmptyView() {
+ if (mEmptyView != null) {
+ mEmptyView.setVisibility(View.GONE);
+ }
+ }
+
+ public void setFilter(Filter filter) {
+ mFilter = filter;
+ Cursor cursor = filterItems(mFilter);
+ if (filter != Filter.CUSTOM_DATE || cursor == null || cursor.getCount() == 0) {
+ mResultView.setVisibility(View.GONE);
+ }
+ if (cursor != null && cursor.getCount() != 0) {
+ mGridAdapter.swapCursor(cursor);
+ hideEmptyView();
+ } else {
+ // No data to display. Clear the GridView and display a message in the empty view
+ mGridAdapter.changeCursor(null);
+ }
+ if (filter != Filter.CUSTOM_DATE) {
+ // Overwrite the LOADING and NO_CONTENT_CUSTOM_DATE messages
+ if (mEmptyViewMessageType == EmptyViewMessageType.LOADING ||
+ mEmptyViewMessageType == EmptyViewMessageType.NO_CONTENT_CUSTOM_DATE) {
+ updateEmptyView(EmptyViewMessageType.NO_CONTENT);
+ } else {
+ updateEmptyView(mEmptyViewMessageType);
+ }
+ } else {
+ updateEmptyView(EmptyViewMessageType.NO_CONTENT_CUSTOM_DATE);
+ }
+ }
+
+ Cursor setDateFilter() {
+ Blog blog = WordPress.getCurrentBlog();
+
+ if (blog == null)
+ return null;
+
+ String blogId = String.valueOf(blog.getLocalTableBlogId());
+
+ GregorianCalendar startDate = new GregorianCalendar(mStartYear, mStartMonth, mStartDay);
+ GregorianCalendar endDate = new GregorianCalendar(mEndYear, mEndMonth, mEndDay);
+
+ long one_day = 24 * 60 * 60 * 1000;
+ Cursor cursor = WordPress.wpDB.getMediaFilesForBlog(blogId, startDate.getTimeInMillis(), endDate.getTimeInMillis() + one_day);
+ mGridAdapter.swapCursor(cursor);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ mResultView.setVisibility(View.VISIBLE);
+ hideEmptyView();
+ DateFormat format = DateFormat.getDateInstance();
+ String formattedStart = format.format(startDate.getTime());
+ String formattedEnd = format.format(endDate.getTime());
+ mResultView.setText(String.format(getString(R.string.media_gallery_date_range), formattedStart,
+ formattedEnd));
+ return cursor;
+ } else {
+ updateEmptyView(EmptyViewMessageType.NO_CONTENT_CUSTOM_DATE);
+ }
+ return null;
+ }
+
+ public void clearSelectedItems() {
+ mGridAdapter.clearSelection();
+ }
+
+ private Cursor filterItems(Filter filter) {
+ Blog blog = WordPress.getCurrentBlog();
+
+ if (blog == null)
+ return null;
+
+ String blogId = String.valueOf(blog.getLocalTableBlogId());
+
+ switch (filter) {
+ case ALL:
+ return WordPress.wpDB.getMediaFilesForBlog(blogId);
+ case IMAGES:
+ return WordPress.wpDB.getMediaImagesForBlog(blogId);
+ case UNATTACHED:
+ return WordPress.wpDB.getMediaUnattachedForBlog(blogId);
+ case CUSTOM_DATE:
+ // show date picker only when the user clicks on the spinner, not when we are doing syncing
+ if (mIsDateFilterSet) {
+ mIsDateFilterSet = false;
+ showDatePicker();
+ } else {
+ return setDateFilter();
+ }
+ break;
+ }
+ return null;
+ }
+
+ void showDatePicker() {
+ // Inflate your custom layout containing 2 DatePickers
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ View customView = inflater.inflate(R.layout.date_range_dialog, null);
+
+ // Define your date pickers
+ final DatePicker dpStartDate = (DatePicker) customView.findViewById(R.id.dpStartDate);
+ final DatePicker dpEndDate = (DatePicker) customView.findViewById(R.id.dpEndDate);
+
+ // Build the dialog
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setView(customView); // Set the view of the dialog to your custom layout
+ builder.setTitle("Select start and end date");
+ builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mStartYear = dpStartDate.getYear();
+ mStartMonth = dpStartDate.getMonth();
+ mStartDay = dpStartDate.getDayOfMonth();
+ mEndYear = dpEndDate.getYear();
+ mEndMonth = dpEndDate.getMonth();
+ mEndDay = dpEndDate.getDayOfMonth();
+ setDateFilter();
+
+ dialog.dismiss();
+ }
+ });
+
+ // Create and show the dialog
+ mDatePickerDialog = builder.create();
+ mDatePickerDialog.show();
+ }
+
+ @Override
+ public void fetchMoreData(int offset) {
+ if (!mHasRetrievedAllMedia) {
+ refreshMediaFromServer(offset, true);
+ }
+ }
+
+ @Override
+ public void onMovedToScrapHeap(View view) {
+ // cancel image fetch requests if the view has been moved to recycler.
+
+ View imageView = view.findViewById(R.id.media_grid_item_image);
+ if (imageView != null) {
+ // this tag is set in the MediaGridAdapter class
+ String tag = (String) imageView.getTag();
+ if (tag != null && tag.startsWith("http")) {
+ // need a listener to cancel request, even if the listener does nothing
+ ImageContainer container = WordPress.imageLoader.get(tag, new ImageListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) { }
+
+ @Override
+ public void onResponse(ImageContainer response, boolean isImmediate) { }
+
+ });
+ container.cancelRequest();
+ }
+ }
+
+ CheckableFrameLayout layout = (CheckableFrameLayout) view.findViewById(R.id.media_grid_frame_layout);
+ if (layout != null) {
+ layout.setOnCheckedChangeListener(null);
+ }
+ }
+
+ public void setFilterSpinnerVisible(boolean visible) {
+ if (visible) {
+ mSpinner.setEnabled(true);
+ mSpinnerContainer.setEnabled(true);
+ mSpinnerContainer.setVisibility(View.VISIBLE);
+ } else {
+ mSpinner.setEnabled(false);
+ mSpinnerContainer.setEnabled(false);
+ mSpinnerContainer.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onRetryUpload(String mediaId) {
+ mListener.onRetryUpload(mediaId);
+ }
+
+ public boolean hasRetrievedAllMediaFromServer() {
+ return mHasRetrievedAllMedia;
+ }
+
+ /*
+ * called by activity when blog is changed
+ */
+ protected void reset() {
+ mGridAdapter.clearSelection();
+ mGridView.setSelection(0);
+ mGridView.requestFocusFromTouch();
+ mGridView.setSelection(0);
+ mGridAdapter.setImageLoader(MediaImageLoader.getInstance());
+ mGridAdapter.changeCursor(null);
+ resetSpinnerAdapter();
+ mHasRetrievedAllMedia = false;
+ }
+
+ public void removeFromMultiSelect(String mediaId) {
+ if (isInMultiSelect() && mGridAdapter.isItemSelected(mediaId)) {
+ mGridAdapter.setItemSelected(mediaId, false);
+ setFilterSpinnerVisible(mGridAdapter.getSelectedItems().size() == 0);
+ }
+ }
+
+ public void setRefreshing(boolean refreshing) {
+ mSwipeToRefreshHelper.setRefreshing(refreshing);
+ }
+
+ public void setSwipeToRefreshEnabled(boolean enabled) {
+ mSwipeToRefreshHelper.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isInMultiSelect() {
+ return mIsMultiSelect;
+ }
+
+ public class MultiChoiceModeListener implements GridView.MultiChoiceModeListener {
+ private MenuItem mNewPostButton;
+ private MenuItem mNewGalleryButton;
+
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ int selectCount = mGridAdapter.getSelectedItems().size();
+ mode.setTitle(String.format(getString(R.string.cab_selected), selectCount));
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.media_multiselect, menu);
+ mNewPostButton = menu.findItem(R.id.media_multiselect_actionbar_post);
+ mNewGalleryButton = menu.findItem(R.id.media_multiselect_actionbar_gallery);
+ setSwipeToRefreshEnabled(false);
+ mIsMultiSelect = true;
+ updateActionButtons(selectCount);
+ return true;
+ }
+
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return true;
+ }
+
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ int i = item.getItemId();
+ if (i == R.id.media_multiselect_actionbar_post) {
+ handleNewPost();
+ return true;
+ } else if (i == R.id.media_multiselect_actionbar_gallery) {
+ handleMultiSelectPost();
+ return true;
+ } else if (i == R.id.media_multiselect_actionbar_trash) {
+ handleMultiSelectDelete();
+ return true;
+ }
+ return true;
+ }
+
+ public void onDestroyActionMode(ActionMode mode) {
+ mGridAdapter.clearSelection();
+ setSwipeToRefreshEnabled(true);
+ mIsMultiSelect = false;
+ setFilterSpinnerVisible(mGridAdapter.getSelectedItems().size() == 0);
+ }
+
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ mGridAdapter.setItemSelected(position, checked);
+ int selectCount = mGridAdapter.getSelectedItems().size();
+ setFilterSpinnerVisible(selectCount == 0);
+ mode.setTitle(String.format(getString(R.string.cab_selected), selectCount));
+ updateActionButtons(selectCount);
+ }
+
+ private void updateActionButtons(int selectCount) {
+ switch (selectCount) {
+ case 1:
+ mNewPostButton.setVisible(true);
+ mNewGalleryButton.setVisible(false);
+ break;
+ default:
+ mNewPostButton.setVisible(false);
+ mNewGalleryButton.setVisible(true);
+ break;
+ }
+ }
+
+ private void handleNewPost() {
+ if (!isAdded()) {
+ return;
+ }
+ ArrayList<String> ids = mGridAdapter.getSelectedItems();
+ Intent i = new Intent(getActivity(), EditPostActivity.class);
+ i.setAction(EditPostActivity.NEW_MEDIA_POST);
+ i.putExtra(EditPostActivity.NEW_MEDIA_POST_EXTRA, ids.iterator().next());
+ startActivity(i);
+ }
+
+ private void handleMultiSelectDelete() {
+ if (!isAdded()) {
+ return;
+ }
+ Builder builder = new AlertDialog.Builder(getActivity()).setMessage(R.string.confirm_delete_multi_media)
+ .setCancelable(true).setPositiveButton(
+ R.string.delete, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (getActivity() instanceof MediaBrowserActivity) {
+ ((MediaBrowserActivity) getActivity()).deleteMedia(
+ mGridAdapter.getSelectedItems());
+ }
+ refreshSpinnerAdapter();
+ }
+ }).setNegativeButton(R.string.cancel, null);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ private void handleMultiSelectPost() {
+ if (!isAdded()) {
+ return;
+ }
+ Intent i = new Intent(getActivity(), EditPostActivity.class);
+ i.setAction(EditPostActivity.NEW_MEDIA_GALLERY);
+ i.putStringArrayListExtra(EditPostActivity.NEW_MEDIA_GALLERY_EXTRA_IDS,
+ mGridAdapter.getSelectedItems());
+ startActivity(i);
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaImageLoader.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaImageLoader.java
new file mode 100644
index 000000000..d484d2fa5
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaImageLoader.java
@@ -0,0 +1,42 @@
+package org.wordpress.android.ui.media;
+
+import android.content.Context;
+
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.Volley;
+
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.VolleyUtils;
+
+/**
+ * provides the ImageLoader and backing RequestQueue for media image requests - necessary because
+ * images in protected blogs need to be authenticated, which requires a separate RequestQueue
+ */
+class MediaImageLoader {
+ private MediaImageLoader() {
+ throw new AssertionError();
+ }
+
+ static ImageLoader getInstance() {
+ return getInstance(WordPress.getCurrentBlog());
+ }
+
+ static ImageLoader getInstance(Blog blog) {
+ if (blog != null && VolleyUtils.isCustomHTTPClientStackNeeded(blog)) {
+ // use ImageLoader with authenticating request queue for protected blogs
+ AppLog.d(AppLog.T.MEDIA, "using custom imageLoader");
+ Context context = WordPress.getContext();
+ RequestQueue authRequestQueue = Volley.newRequestQueue(context, VolleyUtils.getHTTPClientStack(context, blog));
+ ImageLoader imageLoader = new ImageLoader(authRequestQueue, WordPress.getBitmapCache());
+ imageLoader.setBatchedResponseDelay(0);
+ return imageLoader;
+ } else {
+ // use default ImageLoader for all others
+ AppLog.d(AppLog.T.MEDIA, "using default imageLoader");
+ return WordPress.imageLoader;
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaItemFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaItemFragment.java
new file mode 100644
index 000000000..53f5be0eb
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaItemFragment.java
@@ -0,0 +1,380 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Fragment;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.WordPressDB;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.ui.reader.ReaderActivityLauncher;
+import org.wordpress.android.ui.reader.ReaderActivityLauncher.PhotoViewerOption;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.DisplayUtils;
+import org.wordpress.android.util.ImageUtils.BitmapWorkerCallback;
+import org.wordpress.android.util.ImageUtils.BitmapWorkerTask;
+import org.wordpress.android.util.MediaUtils;
+import org.wordpress.android.util.SqlUtils;
+import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.util.ToastUtils;
+import org.wordpress.android.util.UrlUtils;
+import org.wordpress.android.widgets.WPNetworkImageView;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+
+/**
+ * A fragment display a media item's details.
+ */
+public class MediaItemFragment extends Fragment {
+ private static final String ARGS_MEDIA_ID = "media_id";
+
+ public static final String TAG = MediaItemFragment.class.getName();
+
+ private WPNetworkImageView mImageView;
+ private TextView mCaptionView;
+ private TextView mDescriptionView;
+ private TextView mDateView;
+ private TextView mFileNameView;
+ private TextView mFileTypeView;
+ private MediaItemFragmentCallback mCallback;
+
+ private boolean mIsLocal;
+ private String mImageUri;
+
+ public interface MediaItemFragmentCallback {
+ void onResume(Fragment fragment);
+ void onPause(Fragment fragment);
+ }
+
+ public static MediaItemFragment newInstance(String mediaId) {
+ MediaItemFragment fragment = new MediaItemFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARGS_MEDIA_ID, mediaId);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ try {
+ mCallback = (MediaItemFragmentCallback) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement MediaItemFragmentCallback");
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mCallback.onResume(this);
+ loadMedia(getMediaId());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mCallback.onPause(this);
+ }
+
+ public String getMediaId() {
+ if (getArguments() != null) {
+ return getArguments().getString(ARGS_MEDIA_ID);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.media_listitem_details, container, false);
+
+ mCaptionView = (TextView) view.findViewById(R.id.media_listitem_details_caption);
+ mDescriptionView = (TextView) view.findViewById(R.id.media_listitem_details_description);
+ mDateView = (TextView) view.findViewById(R.id.media_listitem_details_date);
+ mFileNameView = (TextView) view.findViewById(R.id.media_listitem_details_file_name);
+ mFileTypeView = (TextView) view.findViewById(R.id.media_listitem_details_file_type);
+ mImageView = (WPNetworkImageView) view.findViewById(R.id.media_listitem_details_image);
+
+ return view;
+ }
+
+ /** Loads the first media item for the current blog from the database **/
+ public void loadDefaultMedia() {
+ loadMedia(null);
+ }
+
+ public void loadMedia(String mediaId) {
+ Blog blog = WordPress.getCurrentBlog();
+
+ if (blog != null) {
+ String blogId = String.valueOf(blog.getLocalTableBlogId());
+
+ Cursor cursor = null;
+ try {
+ // if the id is null, get the first media item in the database
+ if (mediaId == null) {
+ cursor = WordPress.wpDB.getFirstMediaFileForBlog(blogId);
+ } else {
+ cursor = WordPress.wpDB.getMediaFile(blogId, mediaId);
+ }
+ refreshViews(cursor);
+ } finally {
+ SqlUtils.closeCursor(cursor);
+ }
+ }
+ }
+
+ private void refreshViews(Cursor cursor) {
+ if (!isAdded() || !cursor.moveToFirst()) {
+ return;
+ }
+
+ // check whether or not to show the edit button
+ String state = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_UPLOAD_STATE));
+ mIsLocal = MediaUtils.isLocalFile(state);
+ if (mIsLocal && getActivity() != null) {
+ getActivity().invalidateOptionsMenu();
+ }
+
+ String caption = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_CAPTION));
+ if (TextUtils.isEmpty(caption)) {
+ mCaptionView.setVisibility(View.GONE);
+ } else {
+ mCaptionView.setText(caption);
+ mCaptionView.setVisibility(View.VISIBLE);
+ }
+
+ String desc = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_DESCRIPTION));
+ if (TextUtils.isEmpty(desc)) {
+ mDescriptionView.setVisibility(View.GONE);
+ } else {
+ mDescriptionView.setText(desc);
+ mDescriptionView.setVisibility(View.VISIBLE);
+ }
+
+ String date = MediaUtils.getDate(cursor.getLong(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_DATE_CREATED_GMT)));
+ mDateView.setText(date);
+ TextView txtDateLabel = (TextView) getView().findViewById(R.id.media_listitem_details_date_label);
+ txtDateLabel.setText(
+ mIsLocal ? R.string.media_details_label_date_added : R.string.media_details_label_date_uploaded);
+
+ String fileURL = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_URL));
+ String fileName = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_NAME));
+ mImageUri = TextUtils.isEmpty(fileURL)
+ ? cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_PATH))
+ : fileURL;
+ boolean isValidImage = MediaUtils.isValidImage(mImageUri);
+
+ mFileNameView.setText(fileName);
+
+ float mediaWidth = cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_WIDTH));
+ float mediaHeight = cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_HEIGHT));
+
+ // image and dimensions
+ if (isValidImage) {
+ int screenWidth = DisplayUtils.getDisplayPixelWidth(getActivity());
+ int screenHeight = DisplayUtils.getDisplayPixelHeight(getActivity());
+
+ // determine size for display
+ int imageWidth;
+ int imageHeight;
+ boolean isFullWidth;
+ if (mediaWidth == 0 || mediaHeight == 0) {
+ imageWidth = screenWidth;
+ imageHeight = screenHeight / 2;
+ isFullWidth = true;
+ } else if (mediaWidth > mediaHeight) {
+ float ratio = mediaHeight / mediaWidth;
+ imageWidth = Math.min(screenWidth, (int) mediaWidth);
+ imageHeight = (int) (imageWidth * ratio);
+ isFullWidth = (imageWidth == screenWidth);
+ } else {
+ float ratio = mediaWidth / mediaHeight;
+ imageHeight = Math.min(screenHeight / 2, (int) mediaHeight);
+ imageWidth = (int) (imageHeight * ratio);
+ isFullWidth = false;
+ }
+
+ // set the imageView's parent height to match the image so it takes up space while
+ // the image is loading
+ FrameLayout frameView = (FrameLayout) getView().findViewById(R.id.layout_image_frame);
+ frameView.setLayoutParams(
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, imageHeight));
+
+ // add padding to the frame if the image isn't full-width
+ if (!isFullWidth) {
+ int hpadding = getResources().getDimensionPixelSize(R.dimen.content_margin);
+ int vpadding = getResources().getDimensionPixelSize(R.dimen.margin_extra_large);
+ frameView.setPadding(hpadding, vpadding, hpadding, vpadding);
+ }
+
+ if (mIsLocal) {
+ final String filePath = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_PATH));
+ loadLocalImage(mImageView, filePath, imageWidth, imageHeight);
+ } else {
+ // Allow non-private wp.com and Jetpack blogs to use photon to get a higher res thumbnail
+ String thumbnailURL;
+ if (WordPress.getCurrentBlog() != null && WordPress.getCurrentBlog().isPhotonCapable()){
+ thumbnailURL = StringUtils.getPhotonUrl(mImageUri, imageWidth);
+ } else {
+ thumbnailURL = UrlUtils.removeQuery(mImageUri) + "?w=" + imageWidth;
+ }
+ mImageView.setImageUrl(thumbnailURL, WPNetworkImageView.ImageType.PHOTO);
+ }
+ } else {
+ // not an image so show placeholder icon
+ int placeholderResId = WordPressMediaUtils.getPlaceholder(mImageUri);
+ mImageView.setDefaultImageResId(placeholderResId);
+ mImageView.showDefaultImage();
+ }
+
+ // show dimens & file ext together
+ String dimens =
+ (mediaWidth > 0 && mediaHeight > 0) ? (int) mediaWidth + " x " + (int) mediaHeight : null;
+ String fileExt =
+ TextUtils.isEmpty(fileURL) ? null : fileURL.replaceAll(".*\\.(\\w+)$", "$1").toUpperCase();
+ boolean hasDimens = !TextUtils.isEmpty(dimens);
+ boolean hasExt = !TextUtils.isEmpty(fileExt);
+ if (hasDimens & hasExt) {
+ mFileTypeView.setText(fileExt + ", " + dimens);
+ mFileTypeView.setVisibility(View.VISIBLE);
+ } else if (hasExt) {
+ mFileTypeView.setText(fileExt);
+ mFileTypeView.setVisibility(View.VISIBLE);
+ } else {
+ mFileTypeView.setVisibility(View.GONE);
+ }
+
+ // enable fullscreen photo for non-local
+ if (!mIsLocal && isValidImage) {
+ mImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Blog blog = WordPress.getCurrentBlog();
+ boolean isPrivate = blog != null && blog.isPrivate();
+ EnumSet<PhotoViewerOption> imageOptions = EnumSet.noneOf(PhotoViewerOption.class);
+ if (isPrivate) {
+ imageOptions.add(PhotoViewerOption.IS_PRIVATE_IMAGE);
+ }
+ ReaderActivityLauncher.showReaderPhotoViewer(
+ v.getContext(), mImageUri, imageOptions);
+ }
+ });
+ }
+ }
+
+ private synchronized void loadLocalImage(ImageView imageView, String filePath, int width, int height) {
+ if (MediaUtils.isValidImage(filePath)) {
+ imageView.setTag(filePath);
+
+ Bitmap bitmap = WordPress.getBitmapCache().get(filePath);
+ if (bitmap != null) {
+ imageView.setImageBitmap(bitmap);
+ } else {
+ BitmapWorkerTask task = new BitmapWorkerTask(imageView, width, height, new BitmapWorkerCallback() {
+ @Override
+ public void onBitmapReady(String path, ImageView imageView, Bitmap bitmap) {
+ imageView.setImageBitmap(bitmap);
+ WordPress.getBitmapCache().put(path, bitmap);
+ }
+ });
+ task.execute(filePath);
+ }
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.media_details, menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.menu_new_media).setVisible(false);
+ menu.findItem(R.id.menu_search).setVisible(false);
+
+ menu.findItem(R.id.menu_edit_media).setVisible(
+ !mIsLocal && WordPressMediaUtils.isWordPressVersionWithMediaEditingCapabilities());
+
+ menu.findItem(R.id.menu_copy_media_url).setVisible(!mIsLocal && !TextUtils.isEmpty(mImageUri));
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int itemId = item.getItemId();
+
+ if (itemId == R.id.menu_delete) {
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ boolean canDeleteMedia = WordPressMediaUtils.canDeleteMedia(blogId, getMediaId());
+ if (!canDeleteMedia) {
+ Toast.makeText(getActivity(), R.string.wait_until_upload_completes, Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ Builder builder = new AlertDialog.Builder(getActivity()).setMessage(R.string.confirm_delete_media)
+ .setCancelable(true).setPositiveButton(
+ R.string.delete, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ArrayList<String> ids = new ArrayList<>(1);
+ ids.add(getMediaId());
+ if (getActivity() instanceof MediaBrowserActivity) {
+ ((MediaBrowserActivity) getActivity()).deleteMedia(ids);
+ }
+ }
+ }).setNegativeButton(R.string.cancel, null);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ return true;
+ } else if (itemId == R.id.menu_copy_media_url) {
+ copyUrlToClipboard();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void copyUrlToClipboard() {
+ try {
+ ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(ClipData.newPlainText(mImageUri, mImageUri));
+ ToastUtils.showToast(getActivity(), R.string.media_details_copy_url_toast);
+ } catch (Exception e) {
+ AppLog.e(AppLog.T.UTILS, e);
+ ToastUtils.showToast(getActivity(), R.string.error_copy_to_clipboard);
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaPickerActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaPickerActivity.java
new file mode 100644
index 000000000..ea4664f67
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaPickerActivity.java
@@ -0,0 +1,538 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.TabLayout;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Surface;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.LinearLayout;
+
+import com.android.volley.toolbox.ImageLoader;
+
+import org.wordpress.android.BuildConfig;
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.ui.RequestCodes;
+import org.wordpress.android.util.MediaUtils;
+import org.wordpress.android.widgets.WPViewPager;
+import org.wordpress.mediapicker.MediaItem;
+import org.wordpress.mediapicker.MediaPickerFragment;
+import org.wordpress.mediapicker.source.MediaSource;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Allows users to select a variety of videos and images from any source.
+ *
+ * Title can be set either by defining a string resource, R.string.media_picker_title, or passing
+ * a String extra in the {@link android.content.Intent} with the key ACTIVITY_TITLE_KEY.
+ *
+ * Accepts Image and Video sources as arguments and displays each in a tab.
+ * - Use DEVICE_IMAGE_MEDIA_SOURCES_KEY with a {@link java.util.List} of {@link org.wordpress.mediapicker.source.MediaSource}'s to pass image sources via the Intent
+ * - Use DEVICE_VIDEO_MEDIA_SOURCES_KEY with a {@link java.util.List} of {@link org.wordpress.mediapicker.source.MediaSource}'s to pass video sources via the Intent
+ */
+
+public class MediaPickerActivity extends AppCompatActivity
+ implements MediaPickerFragment.OnMediaSelected {
+ /**
+ * Request code for the {@link android.content.Intent} to start media selection.
+ */
+ public static final int ACTIVITY_REQUEST_CODE_MEDIA_SELECTION = 6000;
+ /**
+ * Result code signaling that media has been selected.
+ */
+ public static final int ACTIVITY_RESULT_CODE_MEDIA_SELECTED = 6001;
+ /**
+ * Result code signaling that a gallery should be created with the results.
+ */
+ public static final int ACTIVITY_RESULT_CODE_GALLERY_CREATED = 6002;
+
+ /**
+ * Pass a {@link String} with this key in the {@link android.content.Intent} to set the title.
+ */
+ public static final String ACTIVITY_TITLE_KEY = "activity-title";
+ /**
+ * Pass an {@link java.util.ArrayList} of {@link org.wordpress.mediapicker.source.MediaSource}'s
+ * in the {@link android.content.Intent} to set image sources for selection.
+ */
+ public static final String DEVICE_IMAGE_MEDIA_SOURCES_KEY = "device-image-media-sources";
+ /**
+ * Pass an {@link java.util.ArrayList} of {@link org.wordpress.mediapicker.source.MediaSource}'s
+ * in the {@link android.content.Intent} to set video sources for selection.
+ */
+ public static final String DEVICE_VIDEO_MEDIA_SOURCES_KEY = "device- video=media-sources";
+ public static final String BLOG_IMAGE_MEDIA_SOURCES_KEY = "blog-image-media-sources";
+ public static final String BLOG_VIDEO_MEDIA_SOURCES_KEY = "blog-video-media-sources";
+ /**
+ * Key to extract the {@link java.util.ArrayList} of {@link org.wordpress.mediapicker.MediaItem}'s
+ * that were selected by the user.
+ */
+ public static final String SELECTED_CONTENT_RESULTS_KEY = "selected-content";
+
+ private static final String CAPTURE_PATH_KEY = "capture-path";
+
+ private static final long TAB_ANIMATION_DURATION_MS = 250L;
+
+ private MediaPickerAdapter mMediaPickerAdapter;
+ private ArrayList<MediaSource>[] mMediaSources;
+ private TabLayout mTabLayout;
+ private WPViewPager mViewPager;
+ private ActionMode mActionMode;
+ private String mCapturePath;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ lockRotation();
+ addMediaSources();
+ setTitle();
+ initializeContentView();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.media_picker, menu);
+
+ return true;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putString(CAPTURE_PATH_KEY, mCapturePath);
+ }
+
+ @Override
+ public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ if (savedInstanceState.containsKey(CAPTURE_PATH_KEY)) {
+ mCapturePath = savedInstanceState.getString(CAPTURE_PATH_KEY);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ } else if (item.getItemId() == R.id.capture_image) {
+ WordPressMediaUtils.launchCamera(this, BuildConfig.APPLICATION_ID,
+ new WordPressMediaUtils.LaunchCameraCallback() {
+ @Override
+ public void onMediaCapturePathReady(String mediaCapturePath) {
+ mCapturePath = mediaCapturePath;
+ }
+ });
+ return true;
+ } else if (item.getItemId() == R.id.capture_video) {
+ WordPressMediaUtils.launchVideoCamera(this);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ switch (requestCode) {
+ case RequestCodes.TAKE_PHOTO:
+ File file = new File(mCapturePath);
+ Uri imageUri = Uri.fromFile(file);
+
+ if (file.exists() && MediaUtils.isValidImage(imageUri.toString())) {
+ // Notify MediaStore of new content
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, imageUri));
+
+ MediaItem newImage = new MediaItem();
+ newImage.setSource(imageUri);
+ newImage.setPreviewSource(imageUri);
+ ArrayList<MediaItem> imageResult = new ArrayList<>();
+ imageResult.add(newImage);
+ finishWithResults(imageResult, ACTIVITY_RESULT_CODE_MEDIA_SELECTED);
+ }
+ break;
+ case RequestCodes.TAKE_VIDEO:
+ Uri videoUri = data != null ? data.getData() : null;
+
+ if (videoUri != null) {
+ // Notify MediaStore of new content
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, videoUri));
+
+ MediaItem newVideo = new MediaItem();
+ newVideo.setSource(videoUri);
+ newVideo.setPreviewSource(videoUri);
+ ArrayList<MediaItem> videoResult = new ArrayList<>();
+ videoResult.add(newVideo);
+ finishWithResults(videoResult, ACTIVITY_RESULT_CODE_MEDIA_SELECTED);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mActionMode != null) {
+ mActionMode.finish();
+ } else {
+ finishWithResults(null, ACTIVITY_RESULT_CODE_MEDIA_SELECTED);
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ super.onActionModeStarted(mode);
+
+ mViewPager.setPagingEnabled(false);
+ mActionMode = mode;
+
+ animateTabGone();
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ super.onActionModeFinished(mode);
+
+ mViewPager.setPagingEnabled(true);
+ mActionMode = null;
+
+ animateTabAppear();
+ }
+
+ /*
+ OnMediaSelected interface
+ */
+
+ @Override
+ public void onMediaSelectionStarted() {
+ }
+
+ @Override
+ public void onMediaSelected(MediaItem mediaContent, boolean selected) {
+ }
+
+ @Override
+ public void onMediaSelectionConfirmed(ArrayList<MediaItem> mediaContent) {
+ if (mediaContent != null) {
+ finishWithResults(mediaContent, ACTIVITY_RESULT_CODE_MEDIA_SELECTED);
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ public void onMediaSelectionCancelled() {
+ }
+
+ @Override
+ public boolean onMenuItemSelected(MenuItem menuItem, ArrayList<MediaItem> selectedContent) {
+ if (menuItem.getItemId() == R.id.menu_media_content_selection_gallery) {
+ finishWithResults(selectedContent, ACTIVITY_RESULT_CODE_GALLERY_CREATED);
+ }
+
+ return false;
+ }
+
+ @Override
+ public ImageLoader.ImageCache getImageCache() {
+ return WordPress.getBitmapCache();
+ }
+
+ /**
+ * Finishes the activity after the user has confirmed media selection.
+ *
+ * @param results
+ * list of selected media items
+ */
+ private void finishWithResults(ArrayList<MediaItem> results, int resultCode) {
+ Intent result = new Intent();
+ result.putParcelableArrayListExtra(SELECTED_CONTENT_RESULTS_KEY, results);
+ setResult(resultCode, result);
+ finish();
+ }
+
+ /**
+ * Helper method; sets title to R.string.media_picker_title unless intent defines one
+ */
+ private void setTitle() {
+ final Intent intent = getIntent();
+
+ if (intent != null && intent.hasExtra(ACTIVITY_TITLE_KEY)) {
+ String activityTitle = intent.getStringExtra(ACTIVITY_TITLE_KEY);
+ setTitle(activityTitle);
+ } else {
+ setTitle(getString(R.string.media_picker_title));
+ }
+ }
+
+ /**
+ * Helper method; gathers {@link org.wordpress.mediapicker.source.MediaSource}'s from intent
+ */
+ private void addMediaSources() {
+ final Intent intent = getIntent();
+
+ if (intent != null) {
+ mMediaSources = new ArrayList[4];
+
+ List<MediaSource> mediaSources = intent.getParcelableArrayListExtra(DEVICE_IMAGE_MEDIA_SOURCES_KEY);
+ if (mediaSources != null) {
+ mMediaSources[0] = new ArrayList<>();
+ mMediaSources[0].addAll(mediaSources);
+ }
+
+ mediaSources = intent.getParcelableArrayListExtra(DEVICE_VIDEO_MEDIA_SOURCES_KEY);
+ if (mediaSources != null) {
+ mMediaSources[1] = new ArrayList<>();
+ mMediaSources[1].addAll(mediaSources);
+ }
+
+ mediaSources = intent.getParcelableArrayListExtra(BLOG_IMAGE_MEDIA_SOURCES_KEY);
+ if (mediaSources != null) {
+ mMediaSources[2] = new ArrayList<>();
+ mMediaSources[2].addAll(mediaSources);
+ }
+
+ mediaSources = intent.getParcelableArrayListExtra(BLOG_VIDEO_MEDIA_SOURCES_KEY);
+ if (mediaSources != null) {
+ mMediaSources[3] = new ArrayList<>();
+ mMediaSources[3].addAll(mediaSources);
+ }
+ }
+ }
+
+ /**
+ * Helper method; locks device orientation to its current state while media is being selected
+ */
+ private void lockRotation() {
+ switch (getWindowManager().getDefaultDisplay().getRotation()) {
+ case Surface.ROTATION_0:
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ break;
+ case Surface.ROTATION_90:
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ break;
+ case Surface.ROTATION_180:
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
+ break;
+ case Surface.ROTATION_270:
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ break;
+ }
+ }
+
+ /**
+ * Helper method; sets up the tab bar, media adapter, and ViewPager for displaying media content
+ */
+ private void initializeContentView() {
+ setContentView(R.layout.media_picker_activity);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setDisplayShowHomeEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.show();
+ }
+
+ mMediaPickerAdapter = new MediaPickerAdapter(getFragmentManager());
+ mTabLayout = (TabLayout) findViewById(R.id.tab_layout);
+ mViewPager = (WPViewPager) findViewById(R.id.media_picker_pager);
+
+ if (mViewPager != null) {
+ mViewPager.setPagingEnabled(true);
+
+ mMediaPickerAdapter.addTab(mMediaSources[0] != null ? mMediaSources[0] :
+ new ArrayList<MediaSource>(),
+ getString(R.string.tab_title_device_images),
+ getString(R.string.loading_images),
+ getString(R.string.error_loading_images),
+ getString(R.string.no_device_images));
+ mMediaPickerAdapter.addTab(mMediaSources[1] != null ? mMediaSources[1] :
+ new ArrayList<MediaSource>(),
+ getString(R.string.tab_title_device_videos),
+ getString(R.string.loading_videos),
+ getString(R.string.error_loading_videos),
+ getString(R.string.no_device_videos));
+ mMediaPickerAdapter.addTab(mMediaSources[2] != null ? mMediaSources[2] :
+ new ArrayList<MediaSource>(),
+ getString(R.string.tab_title_site_images),
+ getString(R.string.loading_blog_images),
+ getString(R.string.error_loading_blog_images),
+ getString(R.string.no_blog_images));
+ mMediaPickerAdapter.addTab(mMediaSources[3] != null ? mMediaSources[3] :
+ new ArrayList<MediaSource>(),
+ getString(R.string.tab_title_site_videos),
+ getString(R.string.loading_blog_videos),
+ getString(R.string.error_loading_blog_videos),
+ getString(R.string.no_blog_videos));
+
+ mViewPager.setAdapter(mMediaPickerAdapter);
+
+ if (mTabLayout != null) {
+ int normalColor = getResources().getColor(R.color.blue_light);
+ int selectedColor = getResources().getColor(R.color.white);
+ mTabLayout.setTabTextColors(normalColor, selectedColor);
+ mTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
+ mTabLayout.setupWithViewPager(mViewPager);
+ }
+ }
+ }
+
+ /**
+ * Helper method; animates the tab bar and ViewPager in when ActionMode ends
+ */
+ private void animateTabAppear() {
+ TranslateAnimation tabAppearAnimation = new TranslateAnimation(0, 0, -mTabLayout.getHeight(), 0);
+ TranslateAnimation pagerAppearAnimation = new TranslateAnimation(0, 0, -mTabLayout.getHeight(), 0);
+
+ tabAppearAnimation.setDuration(TAB_ANIMATION_DURATION_MS);
+ pagerAppearAnimation.setDuration(TAB_ANIMATION_DURATION_MS);
+ pagerAppearAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mViewPager.getWidth(), mViewPager.getHeight() - mTabLayout.getHeight());
+ mViewPager.setLayoutParams(params);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+
+ mTabLayout.setVisibility(View.VISIBLE);
+ mViewPager.startAnimation(pagerAppearAnimation);
+ mTabLayout.startAnimation(tabAppearAnimation);
+ }
+
+ /**
+ * Helper method; animates the tab bar and ViewPager out when ActionMode begins
+ */
+ private void animateTabGone() {
+ TranslateAnimation tabGoneAnimation = new TranslateAnimation(0, 0, 0, -mTabLayout.getHeight());
+ TranslateAnimation pagerGoneAnimation = new TranslateAnimation(0, 0, 0, -mTabLayout.getHeight());
+ tabGoneAnimation.setDuration(TAB_ANIMATION_DURATION_MS);
+ pagerGoneAnimation.setDuration(TAB_ANIMATION_DURATION_MS);
+ tabGoneAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ mTabLayout.setVisibility(View.GONE);
+ mTabLayout.clearAnimation();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ pagerGoneAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ LinearLayout.LayoutParams newParams = new LinearLayout.LayoutParams(mViewPager.getWidth(), mViewPager.getHeight() + mTabLayout.getHeight());
+ mViewPager.setLayoutParams(newParams);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ mViewPager.clearAnimation();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+
+ mTabLayout.startAnimation(tabGoneAnimation);
+ mViewPager.startAnimation(pagerGoneAnimation);
+ }
+
+ /**
+ * Shows {@link org.wordpress.mediapicker.MediaPickerFragment}'s in a tabbed layout.
+ */
+ public class MediaPickerAdapter extends FragmentPagerAdapter {
+ private class MediaPicker {
+ public String loadingText;
+ public String errorText;
+ public String emptyText;
+ public String pickerTitle;
+ public ArrayList<MediaSource> mediaSources;
+
+ public MediaPicker(String name, String loading, String error, String empty, ArrayList<MediaSource> sources) {
+ loadingText = loading;
+ errorText = error;
+ emptyText = empty;
+ pickerTitle = name;
+ mediaSources = sources;
+ }
+ }
+
+ private final List<MediaPicker> mMediaPickers;
+
+ private MediaPickerAdapter(FragmentManager fragmentManager) {
+ super(fragmentManager);
+
+ mMediaPickers = new ArrayList<>();
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ if (position < mMediaPickers.size()) {
+ MediaPicker mediaPicker = mMediaPickers.get(position);
+ MediaPickerFragment fragment = new MediaPickerFragment();
+ fragment.setLoadingText(mediaPicker.loadingText);
+ fragment.setErrorText(mediaPicker.errorText);
+ fragment.setEmptyText(mediaPicker.emptyText);
+ fragment.setActionModeMenu(R.menu.menu_media_picker_action_mode);
+ fragment.setMediaSources(mediaPicker.mediaSources);
+
+ return fragment;
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getCount() {
+ return mMediaPickers.size();
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mMediaPickers.get(position).pickerTitle;
+ }
+
+ public void addTab(ArrayList<MediaSource> mediaSources, String tabName, String loading, String error, String empty) {
+ mMediaPickers.add(new MediaPicker(tabName, loading, error, empty, mediaSources));
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaSourceWPImages.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaSourceWPImages.java
new file mode 100644
index 000000000..26e61ae12
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaSourceWPImages.java
@@ -0,0 +1,264 @@
+package org.wordpress.android.ui.media;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Parcel;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.volley.toolbox.ImageLoader;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.WordPressDB;
+import org.wordpress.android.models.Blog;
+import org.wordpress.mediapicker.MediaItem;
+import org.wordpress.mediapicker.source.MediaSource;
+import org.wordpress.mediapicker.MediaUtils.LimitedBackgroundOperation;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MediaSourceWPImages implements MediaSource {
+ private final List<MediaItem> mVerifiedItems = new ArrayList<>();
+ private final List<MediaItem> mMediaItems = new ArrayList<>();
+
+ private OnMediaChange mListener;
+
+ public MediaSourceWPImages() {
+ }
+
+ @Override
+ public void gather(Context context) {
+ mMediaItems.clear();
+
+ Blog blog = WordPress.getCurrentBlog();
+
+ if (blog != null) {
+ Cursor imageCursor = WordPressMediaUtils.getWordPressMediaImages(String.valueOf(blog.getLocalTableBlogId()));
+
+ if (imageCursor != null) {
+ addWordPressImagesFromCursor(imageCursor);
+ imageCursor.close();
+ } else if (mListener != null){
+ mListener.onMediaLoaded(false);
+ }
+ } else if (mListener != null){
+ mListener.onMediaLoaded(false);
+ }
+ }
+
+ @Override
+ public void cleanup() {
+ mMediaItems.clear();
+ }
+
+ @Override
+ public void setListener(OnMediaChange listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public int getCount() {
+ return mVerifiedItems.size();
+ }
+
+ @Override
+ public MediaItem getMedia(int position) {
+ return mVerifiedItems.get(position);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent, LayoutInflater inflater, ImageLoader.ImageCache cache) {
+ if (convertView == null) {
+ convertView = inflater.inflate(R.layout.media_item_wp_image, parent, false);
+ }
+
+ if (convertView != null) {
+ MediaItem mediaItem = mVerifiedItems.get(position);
+ Uri imageSource = mediaItem.getPreviewSource();
+ ImageView imageView = (ImageView) convertView.findViewById(R.id.wp_image_view_background);
+ if (imageView != null) {
+ if (imageSource != null) {
+ Bitmap imageBitmap = null;
+ if (cache != null) {
+ imageBitmap = cache.getBitmap(imageSource.toString());
+ }
+
+ if (imageBitmap == null) {
+ imageView.setImageDrawable(placeholderDrawable(convertView.getContext()));
+ WordPressMediaUtils.BackgroundDownloadWebImage bgDownload =
+ new WordPressMediaUtils.BackgroundDownloadWebImage(imageView);
+ imageView.setTag(bgDownload);
+ bgDownload.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, mediaItem.getPreviewSource());
+ } else {
+ imageView.setImageBitmap(imageBitmap);
+ }
+ } else {
+ imageView.setTag(null);
+ imageView.setImageResource(R.color.grey_darken_10);
+ }
+ }
+ }
+
+ return convertView;
+ }
+
+ @Override
+ public boolean onMediaItemSelected(MediaItem mediaItem, boolean selected) {
+ return !selected;
+ }
+
+ private Drawable placeholderDrawable(Context context) {
+ if (context != null && context.getResources() != null) {
+ return context.getResources().getDrawable(R.drawable.media_item_placeholder);
+ }
+
+ return null;
+ }
+
+ private void addWordPressImagesFromCursor(Cursor cursor) {
+ if (cursor.moveToFirst()) {
+ do {
+ int attachmentIdColumnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MEDIA_ID);
+ int fileUrlColumnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_URL);
+ int filePathColumnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_PATH);
+ int thumbnailColumnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_THUMBNAIL_URL);
+
+ String id = "";
+ if (attachmentIdColumnIndex != -1) {
+ id = String.valueOf(cursor.getInt(attachmentIdColumnIndex));
+ }
+ MediaItem newContent = new MediaItem();
+ newContent.setTag(id);
+ newContent.setTitle("");
+
+ if (fileUrlColumnIndex != -1) {
+ String fileUrl = cursor.getString(fileUrlColumnIndex);
+
+ if (fileUrl != null) {
+ newContent.setSource(Uri.parse(fileUrl));
+ newContent.setPreviewSource(Uri.parse(fileUrl));
+ } else if (filePathColumnIndex != -1) {
+ String filePath = cursor.getString(filePathColumnIndex);
+
+ if (filePath != null) {
+ newContent.setSource(Uri.parse(filePath));
+ newContent.setPreviewSource(Uri.parse(filePath));
+ }
+ }
+ }
+
+ if (thumbnailColumnIndex != -1) {
+ String preview = cursor.getString(thumbnailColumnIndex);
+
+ if (preview != null) {
+ newContent.setPreviewSource(Uri.parse(preview));
+ }
+ }
+
+ mMediaItems.add(newContent);
+ } while (cursor.moveToNext());
+
+ removeDeletedEntries();
+ } else if (mListener != null) {
+ mListener.onMediaLoaded(true);
+ }
+ }
+
+ private void removeDeletedEntries() {
+ final List<MediaItem> existingItems = new ArrayList<>(mMediaItems);
+ final List<MediaItem> failedItems = new ArrayList<>();
+
+ for (final MediaItem mediaItem : existingItems) {
+ LimitedBackgroundOperation<MediaItem, Void, MediaItem> backgroundCheck =
+ new LimitedBackgroundOperation<MediaItem, Void, MediaItem>() {
+ private int responseCode;
+
+ @Override
+ protected MediaItem performBackgroundOperation(MediaItem[] params) {
+ MediaItem mediaItem = params[0];
+ try {
+ URL mediaUrl = new URL(mediaItem.getSource().toString());
+ HttpURLConnection connection = (HttpURLConnection) mediaUrl.openConnection();
+ connection.setRequestMethod("GET");
+ connection.connect();
+ responseCode = connection.getResponseCode();
+ } catch (IOException ioException) {
+ Log.e("", "Error reading from " + mediaItem.getSource() + "\nexception:" + ioException);
+
+ return null;
+ }
+
+ return mediaItem;
+ }
+
+ @Override
+ public void performPostExecute(MediaItem result) {
+ if (mListener != null && result != null) {
+ if (responseCode == 200) {
+ mVerifiedItems.add(result);
+ List<MediaItem> resultList = new ArrayList<>();
+ resultList.add(result);
+
+ // Only signal newly loaded data every 3 images
+ if ((existingItems.size() - mVerifiedItems.size()) % 3 == 0) {
+ mListener.onMediaAdded(MediaSourceWPImages.this, resultList);
+ }
+ } else {
+ failedItems.add(result);
+ }
+
+ // Notify of all media loaded if all have been processed
+ if ((failedItems.size() + mVerifiedItems.size()) == existingItems.size()) {
+ mListener.onMediaLoaded(true);
+ }
+ }
+ }
+
+ @Override
+ public void startExecution(Object params) {
+ if (!(params instanceof MediaItem)) {
+ throw new IllegalArgumentException("Params must be of type MediaItem");
+ }
+ executeOnExecutor(THREAD_POOL_EXECUTOR, (MediaItem) params);
+ }
+ };
+ backgroundCheck.executeWithLimit(mediaItem);
+ }
+ }
+
+ /**
+ * {@link android.os.Parcelable} interface
+ */
+
+ public static final Creator<MediaSourceWPImages> CREATOR =
+ new Creator<MediaSourceWPImages>() {
+ public MediaSourceWPImages createFromParcel(Parcel in) {
+ return new MediaSourceWPImages();
+ }
+
+ public MediaSourceWPImages[] newArray(int size) {
+ return new MediaSourceWPImages[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaSourceWPVideos.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaSourceWPVideos.java
new file mode 100644
index 000000000..d87dad566
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaSourceWPVideos.java
@@ -0,0 +1,209 @@
+package org.wordpress.android.ui.media;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.volley.toolbox.ImageLoader;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.WordPressDB;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.util.MediaUtils;
+import org.wordpress.mediapicker.MediaItem;
+import org.wordpress.mediapicker.source.MediaSource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MediaSourceWPVideos implements MediaSource {
+ private static final String VIDEO_PRESS_HOST = "https://videos.files.wordpress.com/";
+ private static final String VIDEO_PRESS_THUMBNAIL_APPEND = "_hd.thumbnail.jpg";
+
+ private OnMediaChange mListener;
+ private List<MediaItem> mMediaItems = new ArrayList<>();
+
+ public MediaSourceWPVideos() {
+ }
+
+ @Override
+ public void gather(Context context) {
+ Blog blog = WordPress.getCurrentBlog();
+
+ if (blog != null) {
+ Cursor videoCursor = WordPressMediaUtils.getWordPressMediaVideos(String.valueOf(blog.getLocalTableBlogId()));
+
+ if (videoCursor != null) {
+ addWordPressVideosFromCursor(videoCursor);
+ videoCursor.close();
+ } else if (mListener != null){
+ mListener.onMediaLoaded(false);
+ }
+ } else if (mListener != null){
+ mListener.onMediaLoaded(false);
+ }
+ }
+
+ @Override
+ public void cleanup() {
+ mMediaItems.clear();
+ }
+
+ @Override
+ public void setListener(OnMediaChange listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public int getCount() {
+ return mMediaItems.size();
+ }
+
+ @Override
+ public MediaItem getMedia(int position) {
+ return mMediaItems.get(position);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent, LayoutInflater inflater, ImageLoader.ImageCache cache) {
+ if (convertView == null) {
+ convertView = inflater.inflate(R.layout.media_item_wp_video, parent, false);
+ }
+
+ if (convertView != null) {
+ MediaItem mediaItem = mMediaItems.get(position);
+ Uri imageSource = mediaItem.getPreviewSource();
+ ImageView imageView = (ImageView) convertView.findViewById(R.id.wp_video_view_background);
+ if (imageView != null) {
+ if (imageSource != null) {
+ Bitmap imageBitmap = null;
+ if (cache != null) {
+ imageBitmap = cache.getBitmap(imageSource.toString());
+ }
+
+ imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ if (imageBitmap == null) {
+ imageView.setImageDrawable(placeholderDrawable(convertView.getContext()));
+ WordPressMediaUtils.BackgroundDownloadWebImage bgDownload =
+ new WordPressMediaUtils.BackgroundDownloadWebImage(imageView);
+ imageView.setTag(bgDownload);
+ bgDownload.execute(mediaItem.getPreviewSource());
+ } else {
+ org.wordpress.mediapicker.MediaUtils.fadeInImage(imageView, imageBitmap);
+ }
+ } else {
+ imageView.setTag(null);
+ imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ imageView.setImageResource(R.drawable.video_thumbnail);
+ }
+ }
+ }
+
+ return convertView;
+ }
+
+ @Override
+ public boolean onMediaItemSelected(MediaItem mediaItem, boolean selected) {
+ return !selected;
+ }
+
+ private Drawable placeholderDrawable(Context context) {
+ if (context != null && context.getResources() != null) {
+ return context.getResources().getDrawable(R.drawable.media_item_placeholder);
+ }
+
+ return null;
+ }
+
+ /**
+ * Helper method; removes unnecessary characters from videoPressShortcode cursor value
+ *
+ * @param cursorEntry
+ * the cursor value for the videoPressShortcode key
+ * @return
+ * the VideoPress code
+ */
+ private String extractVideoPressCode(String cursorEntry) {
+ cursorEntry = cursorEntry.replace("[wpvideo ", "");
+ cursorEntry = cursorEntry.substring(0, cursorEntry.length() - 1);
+
+ return cursorEntry;
+ }
+
+ private void addWordPressVideosFromCursor(Cursor cursor) {
+ if (cursor.moveToFirst()) {
+ do {
+ int attachmentIdColumnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MEDIA_ID);
+ int fileUrlColumnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_URL);
+ int fileNameColumnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_NAME);
+ int videoPressColumnIndex = cursor.getColumnIndex(WordPressDB.COLUMN_NAME_VIDEO_PRESS_SHORTCODE);
+
+ String id = "";
+ if (attachmentIdColumnIndex != -1) {
+ id = String.valueOf(cursor.getInt(attachmentIdColumnIndex));
+ }
+ MediaItem newContent = new MediaItem();
+ newContent.setTag(id);
+ newContent.setTitle("");
+
+ if (fileUrlColumnIndex != -1) {
+ String fileUrl = cursor.getString(fileUrlColumnIndex);
+
+ if (fileUrl != null && MediaUtils.isVideo(fileUrl)) {
+ newContent.setSource(Uri.parse(fileUrl));
+ } else {
+ continue;
+ }
+ }
+
+ if (videoPressColumnIndex != -1 && fileNameColumnIndex != -1) {
+ String videoPressCode = cursor.getString(videoPressColumnIndex);
+ String fileName = cursor.getString(fileNameColumnIndex);
+
+ if (videoPressCode != null && !videoPressCode.isEmpty() && fileName != null && !fileName.isEmpty()) {
+ videoPressCode = extractVideoPressCode(videoPressCode);
+ newContent.setPreviewSource(VIDEO_PRESS_HOST + videoPressCode + "/" + fileName + VIDEO_PRESS_THUMBNAIL_APPEND);
+ }
+ }
+
+ mMediaItems.add(newContent);
+ } while (cursor.moveToNext());
+ }
+
+ if (mListener != null) {
+ mListener.onMediaLoaded(true);
+ }
+ }
+
+ /**
+ * {@link android.os.Parcelable} interface
+ */
+
+ public static final Creator<MediaSourceWPVideos> CREATOR =
+ new Creator<MediaSourceWPVideos>() {
+ public MediaSourceWPVideos createFromParcel(Parcel in) {
+ return new MediaSourceWPVideos();
+ }
+
+ public MediaSourceWPVideos[] newArray(int size) {
+ return new MediaSourceWPVideos[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/WordPressMediaUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/media/WordPressMediaUtils.java
new file mode 100644
index 000000000..16c2c5ccc
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/WordPressMediaUtils.java
@@ -0,0 +1,379 @@
+package org.wordpress.android.ui.media;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.support.v4.content.FileProvider;
+import android.widget.ImageView;
+
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.NetworkImageView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.WordPressDB;
+import org.wordpress.android.ui.RequestCodes;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.DeviceUtils;
+import org.wordpress.android.util.MediaUtils;
+import org.wordpress.android.util.PhotonUtils;
+import org.wordpress.android.util.helpers.Version;
+import org.wordpress.passcodelock.AppLockManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+
+import static org.wordpress.mediapicker.MediaUtils.fadeInImage;
+
+public class WordPressMediaUtils {
+ public interface LaunchCameraCallback {
+ void onMediaCapturePathReady(String mediaCapturePath);
+ }
+
+ private static void showSDCardRequiredDialog(Context context) {
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
+ dialogBuilder.setTitle(context.getResources().getText(R.string.sdcard_title));
+ dialogBuilder.setMessage(context.getResources().getText(R.string.sdcard_message));
+ dialogBuilder.setPositiveButton(context.getString(R.string.ok), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dialog.dismiss();
+ }
+ });
+ dialogBuilder.setCancelable(true);
+ dialogBuilder.create().show();
+ }
+
+ public static void launchVideoLibrary(Activity activity) {
+ AppLockManager.getInstance().setExtendedTimeout();
+ activity.startActivityForResult(prepareVideoLibraryIntent(activity),
+ RequestCodes.VIDEO_LIBRARY);
+ }
+
+ public static void launchVideoLibrary(Fragment fragment) {
+ if (!fragment.isAdded()) {
+ return;
+ }
+ AppLockManager.getInstance().setExtendedTimeout();
+ fragment.startActivityForResult(prepareVideoLibraryIntent(fragment.getActivity()),
+ RequestCodes.VIDEO_LIBRARY);
+ }
+
+
+ public static Intent prepareVideoLibraryIntent(Context context) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("video/*");
+ return Intent.createChooser(intent, context.getString(R.string.pick_video));
+ }
+
+ public static void launchVideoCamera(Activity activity) {
+ AppLockManager.getInstance().setExtendedTimeout();
+ activity.startActivityForResult(prepareVideoCameraIntent(), RequestCodes.TAKE_VIDEO);
+ }
+
+ public static void launchVideoCamera(Fragment fragment) {
+ if (!fragment.isAdded()) {
+ return;
+ }
+ AppLockManager.getInstance().setExtendedTimeout();
+ fragment.startActivityForResult(prepareVideoCameraIntent(), RequestCodes.TAKE_VIDEO);
+ }
+
+ private static Intent prepareVideoCameraIntent() {
+ return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+ }
+
+ public static void launchPictureLibrary(Activity activity) {
+ AppLockManager.getInstance().setExtendedTimeout();
+ activity.startActivityForResult(preparePictureLibraryIntent(activity.getString(R.string.pick_photo)),
+ RequestCodes.PICTURE_LIBRARY);
+ }
+
+ public static void launchPictureLibrary(Fragment fragment) {
+ if (!fragment.isAdded()) {
+ return;
+ }
+ AppLockManager.getInstance().setExtendedTimeout();
+ fragment.startActivityForResult(preparePictureLibraryIntent(fragment.getActivity()
+ .getString(R.string.pick_photo)), RequestCodes.PICTURE_LIBRARY);
+ }
+
+ private static Intent preparePictureLibraryIntent(String title) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ return Intent.createChooser(intent, title);
+ }
+
+ private static Intent prepareGalleryIntent(String title) {
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setType("image/*");
+ return Intent.createChooser(intent, title);
+ }
+
+ public static void launchCamera(Activity activity, String applicationId, LaunchCameraCallback callback) {
+ Intent intent = preparelaunchCamera(activity, applicationId, callback);
+ if (intent != null) {
+ AppLockManager.getInstance().setExtendedTimeout();
+ activity.startActivityForResult(intent, RequestCodes.TAKE_PHOTO);
+ }
+ }
+
+ public static void launchCamera(Fragment fragment, String applicationId, LaunchCameraCallback callback) {
+ if (!fragment.isAdded()) {
+ return;
+ }
+ Intent intent = preparelaunchCamera(fragment.getActivity(), applicationId, callback);
+ if (intent != null) {
+ AppLockManager.getInstance().setExtendedTimeout();
+ fragment.startActivityForResult(intent, RequestCodes.TAKE_PHOTO);
+ }
+ }
+
+ private static Intent preparelaunchCamera(Context context, String applicationId, LaunchCameraCallback callback) {
+ String state = android.os.Environment.getExternalStorageState();
+ if (!state.equals(android.os.Environment.MEDIA_MOUNTED)) {
+ showSDCardRequiredDialog(context);
+ return null;
+ } else {
+ return getLaunchCameraIntent(context, applicationId, callback);
+ }
+ }
+
+ private static Intent getLaunchCameraIntent(Context context, String applicationId, LaunchCameraCallback callback) {
+ File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
+
+ String mediaCapturePath = path + File.separator + "Camera" + File.separator + "wp-" + System
+ .currentTimeMillis() + ".jpg";
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(context,
+ applicationId + ".provider", new File(mediaCapturePath)));
+
+ if (callback != null) {
+ callback.onMediaCapturePathReady(mediaCapturePath);
+ }
+
+ // make sure the directory we plan to store the recording in exists
+ File directory = new File(mediaCapturePath).getParentFile();
+ if (!directory.exists() && !directory.mkdirs()) {
+ try {
+ throw new IOException("Path to file could not be created.");
+ } catch (IOException e) {
+ AppLog.e(T.POSTS, e);
+ }
+ }
+ return intent;
+ }
+
+ public static void launchPictureLibraryOrCapture(Fragment fragment, String applicationId, LaunchCameraCallback
+ callback) {
+ if (!fragment.isAdded()) {
+ return;
+ }
+ AppLockManager.getInstance().setExtendedTimeout();
+ fragment.startActivityForResult(makePickOrCaptureIntent(fragment.getActivity(), applicationId, callback),
+ RequestCodes.PICTURE_LIBRARY_OR_CAPTURE);
+ }
+
+ private static Intent makePickOrCaptureIntent(Context context, String applicationId, LaunchCameraCallback callback) {
+ Intent pickPhotoIntent = prepareGalleryIntent(context.getString(R.string.capture_or_pick_photo));
+
+ if (DeviceUtils.getInstance().hasCamera(context)) {
+ Intent cameraIntent = getLaunchCameraIntent(context, applicationId, callback);
+ pickPhotoIntent.putExtra(
+ Intent.EXTRA_INITIAL_INTENTS,
+ new Intent[]{ cameraIntent });
+ }
+
+ return pickPhotoIntent;
+ }
+
+ public static int getPlaceholder(String url) {
+ if (MediaUtils.isValidImage(url)) {
+ return R.drawable.media_image_placeholder;
+ } else if (MediaUtils.isDocument(url)) {
+ return R.drawable.media_document;
+ } else if (MediaUtils.isPowerpoint(url)) {
+ return R.drawable.media_powerpoint;
+ } else if (MediaUtils.isSpreadsheet(url)) {
+ return R.drawable.media_spreadsheet;
+ } else if (MediaUtils.isVideo(url)) {
+ return org.wordpress.android.editor.R.drawable.media_movieclip;
+ } else if (MediaUtils.isAudio(url)) {
+ return R.drawable.media_audio;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * This is a workaround for WP3.4.2 that deletes the media from the server when editing media properties
+ * within the app. See: https://github.com/wordpress-mobile/WordPress-Android/issues/204
+ */
+ public static boolean isWordPressVersionWithMediaEditingCapabilities() {
+ if (WordPress.currentBlog == null) {
+ return false;
+ }
+
+ if (WordPress.currentBlog.getWpVersion() == null) {
+ return true;
+ }
+
+ if (WordPress.currentBlog.isDotcomFlag()) {
+ return true;
+ }
+
+ Version minVersion;
+ Version currentVersion;
+ try {
+ minVersion = new Version("3.5.2");
+ currentVersion = new Version(WordPress.currentBlog.getWpVersion());
+
+ if (currentVersion.compareTo(minVersion) == -1) {
+ return false;
+ }
+ } catch (IllegalArgumentException e) {
+ AppLog.e(T.POSTS, e);
+ }
+
+ return true;
+ }
+
+ public static boolean canDeleteMedia(String blogId, String mediaID) {
+ Cursor cursor = WordPress.wpDB.getMediaFile(blogId, mediaID);
+ if (!cursor.moveToFirst()) {
+ cursor.close();
+ return false;
+ }
+ String state = cursor.getString(cursor.getColumnIndex("uploadState"));
+ cursor.close();
+ return state == null || !state.equals("uploading");
+ }
+
+ public static class BackgroundDownloadWebImage extends AsyncTask<Uri, String, Bitmap> {
+ WeakReference<ImageView> mReference;
+
+ public BackgroundDownloadWebImage(ImageView resultStore) {
+ mReference = new WeakReference<>(resultStore);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Uri... params) {
+ try {
+ String uri = params[0].toString();
+ Bitmap bitmap = WordPress.getBitmapCache().getBitmap(uri);
+
+ if (bitmap == null) {
+ URL url = new URL(uri);
+ bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
+ WordPress.getBitmapCache().put(uri, bitmap);
+ }
+
+ return bitmap;
+ }
+ catch(IOException notFoundException) {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap result) {
+ ImageView imageView = mReference.get();
+
+ if (imageView != null) {
+ if (imageView.getTag() == this) {
+ imageView.setImageBitmap(result);
+ fadeInImage(imageView, result);
+ }
+ }
+ }
+ }
+
+ public static Cursor getWordPressMediaImages(String blogId) {
+ return WordPress.wpDB.getMediaImagesForBlog(blogId);
+ }
+
+ public static Cursor getWordPressMediaVideos(String blogId) {
+ return WordPress.wpDB.getMediaFilesForBlog(blogId);
+ }
+
+ /**
+ * Given a media file cursor, returns the thumbnail network URL. Will use photon if available, using the specified
+ * width.
+ * @param cursor the media file cursor
+ * @param width width to use for photon request (if applicable)
+ */
+ public static String getNetworkThumbnailUrl(Cursor cursor, int width) {
+ String thumbnailURL = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_THUMBNAIL_URL));
+
+ // Allow non-private wp.com and Jetpack blogs to use photon to get a higher res thumbnail
+ if ((WordPress.getCurrentBlog() != null && WordPress.getCurrentBlog().isPhotonCapable())) {
+ String imageURL = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_URL));
+ if (imageURL != null) {
+ thumbnailURL = PhotonUtils.getPhotonImageUrl(imageURL, width, 0);
+ }
+ }
+
+ return thumbnailURL;
+ }
+
+ /**
+ * Loads the given network image URL into the {@link NetworkImageView}, using the default {@link ImageLoader}.
+ */
+ public static void loadNetworkImage(String imageUrl, NetworkImageView imageView) {
+ loadNetworkImage(imageUrl, imageView, WordPress.imageLoader);
+ }
+
+ /**
+ * Loads the given network image URL into the {@link NetworkImageView}.
+ */
+ public static void loadNetworkImage(String imageUrl, NetworkImageView imageView, ImageLoader imageLoader) {
+ if (imageUrl != null) {
+ Uri uri = Uri.parse(imageUrl);
+ String filepath = uri.getLastPathSegment();
+
+ int placeholderResId = WordPressMediaUtils.getPlaceholder(filepath);
+ imageView.setErrorImageResId(placeholderResId);
+
+ // no default image while downloading
+ imageView.setDefaultImageResId(0);
+
+ if (MediaUtils.isValidImage(filepath)) {
+ imageView.setTag(imageUrl);
+ imageView.setImageUrl(imageUrl, imageLoader);
+ } else {
+ imageView.setImageResource(placeholderResId);
+ }
+ } else {
+ imageView.setImageResource(0);
+ }
+ }
+
+ /**
+ * Returns a poster (thumbnail) URL given a VideoPress video URL
+ * @param videoUrl the remote URL to the VideoPress video
+ */
+ public static String getVideoPressVideoPosterFromURL(String videoUrl) {
+ String posterUrl = "";
+
+ if (videoUrl != null) {
+ int filetypeLocation = videoUrl.lastIndexOf(".");
+ if (filetypeLocation > 0) {
+ posterUrl = videoUrl.substring(0, filetypeLocation) + "_std.original.jpg";
+ }
+ }
+
+ return posterUrl;
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaDeleteService.java b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaDeleteService.java
new file mode 100644
index 000000000..af0497946
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaDeleteService.java
@@ -0,0 +1,121 @@
+package org.wordpress.android.ui.media.services;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Handler;
+import android.os.IBinder;
+
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.MediaUploadState;
+import org.xmlrpc.android.ApiHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A service for deleting media files from the media browser.
+ * Only one file is deleted at a time.
+ */
+public class MediaDeleteService extends Service {
+ // time to wait before trying to delete the next file
+ private static final int DELETE_WAIT_TIME = 1000;
+
+ private Context mContext;
+ private Handler mHandler = new Handler();
+ private boolean mDeleteInProgress;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mContext = this.getApplicationContext();
+ mDeleteInProgress = false;
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ mHandler.post(mFetchQueueTask);
+ }
+
+ private Runnable mFetchQueueTask = new Runnable() {
+ @Override
+ public void run() {
+ Cursor cursor = getQueueItem();
+ try {
+ if ((cursor == null || cursor.getCount() == 0 || mContext == null) && !mDeleteInProgress) {
+ MediaDeleteService.this.stopSelf();
+ return;
+ } else {
+ if (mDeleteInProgress) {
+ mHandler.postDelayed(this, DELETE_WAIT_TIME);
+ } else {
+ deleteMediaFile(cursor);
+ }
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ }
+ };
+
+ private Cursor getQueueItem() {
+ if (WordPress.getCurrentBlog() == null)
+ return null;
+
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ return WordPress.wpDB.getMediaDeleteQueueItem(blogId);
+ }
+
+ private void deleteMediaFile(Cursor cursor) {
+ if (!cursor.moveToFirst())
+ return;
+
+ mDeleteInProgress = true;
+
+ final String blogId = cursor.getString((cursor.getColumnIndex("blogId")));
+ final String mediaId = cursor.getString(cursor.getColumnIndex("mediaId"));
+
+ ApiHelper.DeleteMediaTask task = new ApiHelper.DeleteMediaTask(mediaId,
+ new ApiHelper.GenericCallback() {
+ @Override
+ public void onSuccess() {
+ // only delete them once we get an ok from the server
+ if (WordPress.getCurrentBlog() != null && mediaId != null) {
+ WordPress.wpDB.deleteMediaFile(blogId, mediaId);
+ }
+
+ mDeleteInProgress = false;
+ mHandler.post(mFetchQueueTask);
+ }
+
+ @Override
+ public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) {
+ // Ideally we would do handle the 401 (unauthorized) and 404 (not found) errors,
+ // but the XMLRPCExceptions don't seem to give messages when they are thrown.
+
+ // Instead we'll just set them as "deleted" so they don't show up in the delete queue.
+ // Otherwise the service will continuously try to delete an item they can't delete.
+
+ WordPress.wpDB.updateMediaUploadState(blogId, mediaId, MediaUploadState.DELETED);
+
+ mDeleteInProgress = false;
+ mHandler.post(mFetchQueueTask);
+ }
+ });
+
+ List<Object> apiArgs = new ArrayList<Object>();
+ apiArgs.add(WordPress.getCurrentBlog());
+ task.execute(apiArgs) ;
+
+ mHandler.post(mFetchQueueTask);
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaEvents.java b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaEvents.java
new file mode 100644
index 000000000..e9ae8d9a7
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaEvents.java
@@ -0,0 +1,51 @@
+package org.wordpress.android.ui.media.services;
+
+public class MediaEvents {
+ public static class MediaUploadSucceeded {
+ public final String mLocalBlogId;
+ public final String mLocalMediaId;
+ public final String mRemoteMediaId;
+ public final String mRemoteMediaUrl;
+ public final String mSecondaryRemoteMediaId;
+ MediaUploadSucceeded(String localBlogId, String localMediaId, String remoteMediaId, String remoteMediaUrl,
+ String secondaryRemoteMediaId) {
+ mLocalBlogId = localBlogId;
+ mLocalMediaId = localMediaId;
+ mRemoteMediaId = remoteMediaId;
+ mRemoteMediaUrl = remoteMediaUrl;
+ mSecondaryRemoteMediaId = secondaryRemoteMediaId;
+ }
+ }
+
+ public static class MediaUploadFailed {
+ public final String mLocalMediaId;
+ public final String mErrorMessage;
+ public final boolean mIsGenericMessage;
+ MediaUploadFailed(String localMediaId, String errorMessage, boolean isGenericMessage) {
+ mLocalMediaId = localMediaId;
+ mErrorMessage = errorMessage;
+ mIsGenericMessage = isGenericMessage;
+ }
+ MediaUploadFailed(String localMediaId, String errorMessage) {
+ this(localMediaId, errorMessage, false);
+ }
+ }
+
+ public static class MediaUploadProgress {
+ public final String mLocalMediaId;
+ public final float mProgress;
+ MediaUploadProgress(String localMediaId, float progress) {
+ mLocalMediaId = localMediaId;
+ mProgress = progress;
+ }
+ }
+
+ public static class MediaChanged {
+ public final String mLocalBlogId;
+ public final String mMediaId;
+ public MediaChanged(String localBlogId, String mediaId) {
+ mLocalBlogId = localBlogId;
+ mMediaId = mediaId;
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaUploadService.java b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaUploadService.java
new file mode 100644
index 000000000..11d0ccae5
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaUploadService.java
@@ -0,0 +1,247 @@
+package org.wordpress.android.ui.media.services;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Handler;
+import android.os.IBinder;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.WordPressDB;
+import org.wordpress.android.models.MediaUploadState;
+import org.wordpress.android.ui.media.services.MediaEvents.MediaChanged;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.CrashlyticsUtils;
+import org.wordpress.android.util.CrashlyticsUtils.ExceptionType;
+import org.wordpress.android.util.helpers.MediaFile;
+import org.xmlrpc.android.ApiHelper;
+import org.xmlrpc.android.ApiHelper.ErrorType;
+import org.xmlrpc.android.ApiHelper.GetMediaItemTask;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * A service for uploading media files from the media browser.
+ * Only one file is uploaded at a time.
+ */
+public class MediaUploadService extends Service {
+ // time to wait before trying to upload the next file
+ private static final int UPLOAD_WAIT_TIME = 1000;
+
+ private static MediaUploadService mInstance;
+
+ private Context mContext;
+ private Handler mHandler = new Handler();
+
+ private boolean mUploadInProgress;
+ private ApiHelper.UploadMediaTask mCurrentUploadMediaTask;
+ private String mCurrentUploadMediaId;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mInstance = this;
+
+ mContext = this.getApplicationContext();
+ mUploadInProgress = false;
+
+ cancelOldUploads();
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ mHandler.post(mFetchQueueTask);
+ }
+
+ public static MediaUploadService getInstance() {
+ return mInstance;
+ }
+
+ public void processQueue() {
+ mHandler.post(mFetchQueueTask);
+ }
+
+ /**
+ * Returns whether the service has any media uploads in progress or queued.
+ */
+ public boolean hasUploads() {
+ if (mUploadInProgress) {
+ return true;
+ } else {
+ Cursor queueCursor = getQueue();
+ return (queueCursor == null || queueCursor.getCount() > 0);
+ }
+ }
+
+ /**
+ * Cancel the upload with the given id, whether it's currently uploading or queued.
+ * @param mediaId the id of the media item
+ * @param delete whether to delete the item from the queue or mark it as failed so it can be retried later
+ */
+ public void cancelUpload(String mediaId, boolean delete) {
+ if (mediaId.equals(mCurrentUploadMediaId)) {
+ // The media item is currently uploading - abort the upload process
+ mCurrentUploadMediaTask.cancel(true);
+ mUploadInProgress = false;
+ } else {
+ // Remove the media item from the upload queue
+ if (WordPress.getCurrentBlog() != null) {
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ if (delete) {
+ WordPress.wpDB.deleteMediaFile(blogId, mediaId);
+ } else {
+ WordPress.wpDB.updateMediaUploadState(blogId, mediaId, MediaUploadState.FAILED);
+ }
+ }
+ }
+ }
+
+ private Runnable mFetchQueueTask = new Runnable() {
+ @Override
+ public void run() {
+ Cursor cursor = getQueue();
+ try {
+ if ((cursor == null || cursor.getCount() == 0 || mContext == null) && !mUploadInProgress) {
+ MediaUploadService.this.stopSelf();
+ return;
+ } else {
+ if (mUploadInProgress) {
+ mHandler.postDelayed(this, UPLOAD_WAIT_TIME);
+ } else {
+ uploadMediaFile(cursor);
+ }
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ }
+ };
+
+ private void cancelOldUploads() {
+ // There should be no media files with an upload state of 'uploading' at the start of this service.
+ // Since we won't be able to receive notifications for these, set them to 'failed'.
+
+ if (WordPress.getCurrentBlog() != null) {
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ WordPress.wpDB.setMediaUploadingToFailed(blogId);
+ }
+ }
+
+ private Cursor getQueue() {
+ if (WordPress.getCurrentBlog() == null)
+ return null;
+
+ String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
+ return WordPress.wpDB.getMediaUploadQueue(blogId);
+ }
+
+ private void uploadMediaFile(Cursor cursor) {
+ if (!cursor.moveToFirst())
+ return;
+
+ mUploadInProgress = true;
+
+ final String blogIdStr = cursor.getString((cursor.getColumnIndex(WordPressDB.COLUMN_NAME_BLOG_ID)));
+ final String mediaId = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MEDIA_ID));
+ String fileName = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_NAME));
+ String filePath = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_PATH));
+ String mimeType = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MIME_TYPE));
+
+ MediaFile mediaFile = new MediaFile();
+ mediaFile.setBlogId(blogIdStr);
+ mediaFile.setFileName(fileName);
+ mediaFile.setFilePath(filePath);
+ mediaFile.setMimeType(mimeType);
+
+ mCurrentUploadMediaId = mediaId;
+
+ mCurrentUploadMediaTask = new ApiHelper.UploadMediaTask(mContext, mediaFile,
+ new ApiHelper.UploadMediaTask.Callback() {
+ @Override
+ public void onSuccess(String remoteId, String remoteUrl, String secondaryId) {
+ // once the file has been uploaded, update the local database entry (swap the id with the remote id)
+ // and download the new one
+ WordPress.wpDB.updateMediaLocalToRemoteId(blogIdStr, mediaId, remoteId);
+ EventBus.getDefault().post(new MediaEvents.MediaUploadSucceeded(blogIdStr, mediaId,
+ remoteId, remoteUrl, secondaryId));
+ fetchMediaFile(remoteId);
+ }
+
+ @Override
+ public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) {
+ WordPress.wpDB.updateMediaUploadState(blogIdStr, mediaId, MediaUploadState.FAILED);
+ mUploadInProgress = false;
+ mCurrentUploadMediaId = "";
+
+ MediaEvents.MediaUploadFailed event;
+ if (errorMessage == null) {
+ event = new MediaEvents.MediaUploadFailed(mediaId, getString(R.string.upload_failed), true);
+ } else {
+ event = new MediaEvents.MediaUploadFailed(mediaId, errorMessage);
+ }
+
+ EventBus.getDefault().post(event);
+ mHandler.post(mFetchQueueTask);
+
+ // Only log the error if it's not caused by the network (internal inconsistency)
+ if (errorType != ErrorType.NETWORK_XMLRPC) {
+ CrashlyticsUtils.logException(throwable, ExceptionType.SPECIFIC, T.MEDIA, errorMessage);
+ }
+ }
+
+ @Override
+ public void onProgressUpdate(float progress) {
+ EventBus.getDefault().post(new MediaEvents.MediaUploadProgress(mediaId, progress));
+ }
+ });
+
+ WordPress.wpDB.updateMediaUploadState(blogIdStr, mediaId, MediaUploadState.UPLOADING);
+ List<Object> apiArgs = new ArrayList<Object>();
+ apiArgs.add(WordPress.getCurrentBlog());
+ mCurrentUploadMediaTask.execute(apiArgs);
+ mHandler.post(mFetchQueueTask);
+ }
+
+ private void fetchMediaFile(final String id) {
+ List<Object> apiArgs = new ArrayList<Object>();
+ apiArgs.add(WordPress.getCurrentBlog());
+ GetMediaItemTask task = new GetMediaItemTask(Integer.valueOf(id),
+ new ApiHelper.GetMediaItemTask.Callback() {
+ @Override
+ public void onSuccess(MediaFile mediaFile) {
+ String blogId = mediaFile.getBlogId();
+ String mediaId = mediaFile.getMediaId();
+ WordPress.wpDB.updateMediaUploadState(blogId, mediaId, MediaUploadState.UPLOADED);
+ mUploadInProgress = false;
+ mCurrentUploadMediaId = "";
+ mHandler.post(mFetchQueueTask);
+ EventBus.getDefault().post(new MediaChanged(blogId, mediaId));
+ }
+
+ @Override
+ public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) {
+ mUploadInProgress = false;
+ mCurrentUploadMediaId = "";
+ mHandler.post(mFetchQueueTask);
+ // Only log the error if it's not caused by the network (internal inconsistency)
+ if (errorType != ErrorType.NETWORK_XMLRPC) {
+ CrashlyticsUtils.logException(throwable, ExceptionType.SPECIFIC, T.MEDIA, errorMessage);
+ }
+ }
+ });
+ task.execute(apiArgs);
+ }
+}