blob: dc8761fcc95d90a22b95ca88639f7d64c71e80da [file] [log] [blame]
package org.wordpress.android.ui.posts;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NotificationCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.CharacterStyle;
import android.text.style.SuggestionSpan;
import android.view.ContextMenu;
import android.view.DragEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.webkit.URLUtil;
import android.widget.Toast;
import org.wordpress.android.BuildConfig;
import org.wordpress.android.Constants;
import org.wordpress.android.JavaScriptException;
import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.WordPressDB;
import org.wordpress.android.analytics.AnalyticsTracker;
import org.wordpress.android.analytics.AnalyticsTracker.Stat;
import org.wordpress.android.editor.EditorFragment;
import org.wordpress.android.editor.EditorFragmentAbstract;
import org.wordpress.android.editor.EditorFragmentAbstract.EditorFragmentListener;
import org.wordpress.android.editor.EditorFragmentAbstract.EditorDragAndDropListener;
import org.wordpress.android.editor.EditorFragmentAbstract.TrackableEvent;
import org.wordpress.android.editor.EditorMediaUploadListener;
import org.wordpress.android.editor.EditorWebViewAbstract.ErrorListener;
import org.wordpress.android.editor.EditorWebViewCompatibility;
import org.wordpress.android.editor.EditorWebViewCompatibility.ReflectionException;
import org.wordpress.android.editor.ImageSettingsDialogFragment;
import org.wordpress.android.editor.LegacyEditorFragment;
import org.wordpress.android.models.AccountHelper;
import org.wordpress.android.models.Blog;
import org.wordpress.android.models.MediaUploadState;
import org.wordpress.android.models.Post;
import org.wordpress.android.ui.ActivityId;
import org.wordpress.android.ui.RequestCodes;
import org.wordpress.android.ui.media.MediaGalleryActivity;
import org.wordpress.android.ui.media.MediaGalleryPickerActivity;
import org.wordpress.android.ui.media.MediaGridFragment;
import org.wordpress.android.ui.media.MediaPickerActivity;
import org.wordpress.android.ui.media.MediaSourceWPImages;
import org.wordpress.android.ui.media.MediaSourceWPVideos;
import org.wordpress.android.ui.media.WordPressMediaUtils;
import org.wordpress.android.ui.media.services.MediaEvents;
import org.wordpress.android.ui.media.services.MediaUploadService;
import org.wordpress.android.ui.posts.services.PostUploadService;
import org.wordpress.android.ui.prefs.AppPrefs;
import org.wordpress.android.ui.prefs.SiteSettingsInterface;
import org.wordpress.android.util.AnalyticsUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.AutolinkUtils;
import org.wordpress.android.util.CrashlyticsUtils;
import org.wordpress.android.util.CrashlyticsUtils.ExceptionType;
import org.wordpress.android.util.DeviceUtils;
import org.wordpress.android.util.ImageUtils;
import org.wordpress.android.util.MediaUtils;
import org.wordpress.android.util.NetworkUtils;
import org.wordpress.android.util.PermissionUtils;
import org.wordpress.android.util.StringUtils;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.util.ToastUtils.Duration;
import org.wordpress.android.util.WPHtml;
import org.wordpress.android.util.WPUrlUtils;
import org.wordpress.android.util.helpers.MediaFile;
import org.wordpress.android.util.helpers.MediaGallery;
import org.wordpress.android.util.helpers.MediaGalleryImageSpan;
import org.wordpress.android.util.helpers.WPImageSpan;
import org.wordpress.android.widgets.WPViewPager;
import org.wordpress.mediapicker.MediaItem;
import org.wordpress.mediapicker.source.MediaSource;
import org.wordpress.mediapicker.source.MediaSourceDeviceImages;
import org.wordpress.mediapicker.source.MediaSourceDeviceVideos;
import org.wordpress.passcodelock.AppLockManager;
import org.xmlrpc.android.ApiHelper;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.greenrobot.event.EventBus;
public class EditPostActivity extends AppCompatActivity implements EditorFragmentListener, EditorDragAndDropListener,
ActivityCompat.OnRequestPermissionsResultCallback, EditorWebViewCompatibility.ReflectionFailureListener {
public static final String EXTRA_POSTID = "postId";
public static final String EXTRA_IS_PAGE = "isPage";
public static final String EXTRA_IS_NEW_POST = "isNewPost";
public static final String EXTRA_IS_QUICKPRESS = "isQuickPress";
public static final String EXTRA_QUICKPRESS_BLOG_ID = "quickPressBlogId";
public static final String EXTRA_SAVED_AS_LOCAL_DRAFT = "savedAsLocalDraft";
public static final String STATE_KEY_CURRENT_POST = "stateKeyCurrentPost";
public static final String STATE_KEY_ORIGINAL_POST = "stateKeyOriginalPost";
public static final String STATE_KEY_EDITOR_FRAGMENT = "editorFragment";
public static final String STATE_KEY_DROPPED_MEDIA_URIS = "stateKeyDroppedMediaUri";
// Context menu positioning
private static final int SELECT_PHOTO_MENU_POSITION = 0;
private static final int CAPTURE_PHOTO_MENU_POSITION = 1;
private static final int SELECT_VIDEO_MENU_POSITION = 2;
private static final int CAPTURE_VIDEO_MENU_POSITION = 3;
private static final int ADD_GALLERY_MENU_POSITION = 4;
private static final int SELECT_LIBRARY_MENU_POSITION = 5;
private static final int NEW_PICKER_MENU_POSITION = 6;
public static final int MEDIA_PERMISSION_REQUEST_CODE = 1;
public static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
public static final int DRAG_AND_DROP_MEDIA_PERMISSION_REQUEST_CODE = 3;
private static int PAGE_CONTENT = 0;
private static int PAGE_SETTINGS = 1;
private static int PAGE_PREVIEW = 2;
private static final int AUTOSAVE_INTERVAL_MILLIS = 60000;
private Handler mHandler;
private boolean mShowNewEditor;
// Each element is a list of media IDs being uploaded to a gallery, keyed by gallery ID
private Map<Long, List<String>> mPendingGalleryUploads = new HashMap<>();
// -1=no response yet, 0=unavailable, 1=available
private int mBlogMediaStatus = -1;
private boolean mMediaUploadServiceStarted;
private List<String> mPendingVideoPressInfoRequests;
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {@link FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* may be best to switch to a
* {@link android.support.v13.app.FragmentStatePagerAdapter}.
*/
SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
WPViewPager mViewPager;
private Post mPost;
private Post mOriginalPost;
private EditorFragmentAbstract mEditorFragment;
private EditPostSettingsFragment mEditPostSettingsFragment;
private EditPostPreviewFragment mEditPostPreviewFragment;
private EditorMediaUploadListener mEditorMediaUploadListener;
private boolean mIsNewPost;
private boolean mIsPage;
private boolean mHasSetPostContent;
// For opening the context menu after permissions have been granted
private View mMenuView = null;
// for keeping the media uri while asking for permissions
private ArrayList<Uri> mDroppedMediaUris;
private Runnable mFetchMediaRunnable = new Runnable() {
@Override
public void run() {
if (mDroppedMediaUris != null) {
final List<Uri> mediaUris = mDroppedMediaUris;
mDroppedMediaUris = null;
fetchMedia(mediaUris);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.new_edit_post_activity);
// Check whether to show the visual editor
PreferenceManager.setDefaultValues(this, R.xml.account_settings, false);
mShowNewEditor = AppPrefs.isVisualEditorEnabled();
// Set up the action bar.
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
FragmentManager fragmentManager = getFragmentManager();
Bundle extras = getIntent().getExtras();
String action = getIntent().getAction();
if (savedInstanceState == null) {
if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)
|| NEW_MEDIA_GALLERY.equals(action)
|| NEW_MEDIA_POST.equals(action)
|| getIntent().hasExtra(EXTRA_IS_QUICKPRESS)
|| (extras != null && extras.getInt("quick-media", -1) > -1)) {
if (getIntent().hasExtra(EXTRA_QUICKPRESS_BLOG_ID)) {
// QuickPress might want to use a different blog than the current blog
int blogId = getIntent().getIntExtra(EXTRA_QUICKPRESS_BLOG_ID, -1);
Blog quickPressBlog = WordPress.wpDB.instantiateBlogByLocalId(blogId);
if (quickPressBlog == null) {
showErrorAndFinish(R.string.blog_not_found);
return;
}
if (quickPressBlog.isHidden()) {
showErrorAndFinish(R.string.error_blog_hidden);
return;
}
WordPress.currentBlog = quickPressBlog;
}
// Create a new post for share intents and QuickPress
mPost = new Post(WordPress.getCurrentLocalTableBlogId(), false);
mPost.setCategories("[" + SiteSettingsInterface.getDefaultCategory(this) + "]");
mPost.setPostFormat(SiteSettingsInterface.getDefaultFormat(this));
WordPress.wpDB.savePost(mPost);
mIsNewPost = true;
} else if (extras != null) {
// Load post from the postId passed in extras
long localTablePostId = extras.getLong(EXTRA_POSTID, -1);
mIsPage = extras.getBoolean(EXTRA_IS_PAGE);
mIsNewPost = extras.getBoolean(EXTRA_IS_NEW_POST);
mPost = WordPress.wpDB.getPostForLocalTablePostId(localTablePostId);
mOriginalPost = WordPress.wpDB.getPostForLocalTablePostId(localTablePostId);
} else {
// A postId extra must be passed to this activity
showErrorAndFinish(R.string.post_not_found);
return;
}
} else {
mDroppedMediaUris = savedInstanceState.getParcelable(STATE_KEY_DROPPED_MEDIA_URIS);
if (savedInstanceState.containsKey(STATE_KEY_ORIGINAL_POST)) {
try {
mPost = (Post) savedInstanceState.getSerializable(STATE_KEY_CURRENT_POST);
mOriginalPost = (Post) savedInstanceState.getSerializable(STATE_KEY_ORIGINAL_POST);
} catch (ClassCastException e) {
mPost = null;
}
}
mEditorFragment = (EditorFragmentAbstract) fragmentManager.getFragment(savedInstanceState, STATE_KEY_EDITOR_FRAGMENT);
if (mEditorFragment instanceof EditorMediaUploadListener) {
mEditorMediaUploadListener = (EditorMediaUploadListener) mEditorFragment;
}
}
if (mHasSetPostContent = mEditorFragment != null) {
mEditorFragment.setImageLoader(WordPress.imageLoader);
}
// Ensure we have a valid blog
if (WordPress.getCurrentBlog() == null) {
showErrorAndFinish(R.string.blog_not_found);
return;
}
// Ensure we have a valid post
if (mPost == null) {
showErrorAndFinish(R.string.post_not_found);
return;
}
if (mIsNewPost) {
trackEditorCreatedPost(action, getIntent());
}
setTitle(StringUtils.unescapeHTML(WordPress.getCurrentBlog().getBlogName()));
mSectionsPagerAdapter = new SectionsPagerAdapter(fragmentManager);
// Set up the ViewPager with the sections adapter.
mViewPager = (WPViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOffscreenPageLimit(2);
mViewPager.setPagingEnabled(false);
// When swiping between different sections, select the corresponding
// tab. We can also use ActionBar.Tab#select() to do this if we have
// a reference to the Tab.
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
invalidateOptionsMenu();
if (position == PAGE_CONTENT) {
setTitle(StringUtils.unescapeHTML(WordPress.getCurrentBlog().getBlogName()));
} else if (position == PAGE_SETTINGS) {
setTitle(mPost.isPage() ? R.string.page_settings : R.string.post_settings);
} else if (position == PAGE_PREVIEW) {
setTitle(mPost.isPage() ? R.string.preview_page : R.string.preview_post);
savePostAsync(new AfterSavePostListener() {
@Override
public void onPostSave() {
if (mEditPostPreviewFragment != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mEditPostPreviewFragment != null) {
mEditPostPreviewFragment.loadPost();
}
}
});
}
}
});
}
}
});
ActivityId.trackLastActivity(ActivityId.POST_EDITOR);
}
private Runnable mAutoSave = new Runnable() {
@Override
public void run() {
new Thread(new Runnable() {
@Override
public void run() {
updatePostObject(true);
savePostToDb();
if (mHandler != null) {
mHandler.postDelayed(mAutoSave, AUTOSAVE_INTERVAL_MILLIS);
}
}
}).start();
}
};
@Override
protected void onResume() {
super.onResume();
mHandler = new Handler();
mHandler.postDelayed(mAutoSave, AUTOSAVE_INTERVAL_MILLIS);
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
@Override
protected void onPause() {
super.onPause();
try {
unregisterReceiver(mGalleryReceiver);
} catch (IllegalArgumentException e) {
AppLog.d(T.EDITOR, "Illegal state! Can't unregister receiver that was no registered");
}
stopMediaUploadService();
mHandler.removeCallbacks(mAutoSave);
mHandler = null;
}
@Override
protected void onDestroy() {
AnalyticsTracker.track(AnalyticsTracker.Stat.EDITOR_CLOSED);
super.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Saves both post objects so we can restore them in onCreate()
savePostAsync(null);
outState.putSerializable(STATE_KEY_CURRENT_POST, mPost);
outState.putSerializable(STATE_KEY_ORIGINAL_POST, mOriginalPost);
outState.putParcelableArrayList(STATE_KEY_DROPPED_MEDIA_URIS, mDroppedMediaUris);
if (mEditorFragment != null) {
getFragmentManager().putFragment(outState, STATE_KEY_EDITOR_FRAGMENT, mEditorFragment);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
if (mShowNewEditor) {
inflater.inflate(R.menu.edit_post, menu);
} else {
inflater.inflate(R.menu.edit_post_legacy, menu);
}
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean showMenuItems = true;
if (mViewPager != null && mViewPager.getCurrentItem() > PAGE_CONTENT) {
showMenuItems = false;
}
MenuItem previewMenuItem = menu.findItem(R.id.menu_preview_post);
MenuItem settingsMenuItem = menu.findItem(R.id.menu_post_settings);
if (previewMenuItem != null) {
previewMenuItem.setVisible(showMenuItems);
}
if (settingsMenuItem != null) {
settingsMenuItem.setVisible(showMenuItems);
}
// Set text of the save button in the ActionBar
if (mPost != null) {
MenuItem saveMenuItem = menu.findItem(R.id.menu_save_post);
if (saveMenuItem != null) {
switch (mPost.getStatusEnum()) {
case SCHEDULED:
saveMenuItem.setTitle(getString(R.string.schedule_verb));
break;
case PUBLISHED:
case UNKNOWN:
if (mPost.isLocalDraft()) {
saveMenuItem.setTitle(R.string.publish_post);
} else {
saveMenuItem.setTitle(R.string.update_verb);
}
break;
default:
if (mPost.isLocalDraft()) {
saveMenuItem.setTitle(R.string.save);
} else {
saveMenuItem.setTitle(R.string.update_verb);
}
}
}
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String permissions[],
@NonNull int[] grantResults) {
switch (requestCode) {
case LOCATION_PERMISSION_REQUEST_CODE:
boolean shouldShowLocation = false;
// Check if at least one of the location permission (coarse or fine) is granted
for (int grantResult : grantResults) {
if (grantResult == PackageManager.PERMISSION_GRANTED) {
shouldShowLocation = true;
}
}
if (shouldShowLocation) {
// Permission request was granted, show Location buttons in Settings
mEditPostSettingsFragment.showLocationSearch();
// After permission request was granted add GeoTag to the new post (if GeoTagging is enabled)
if (SiteSettingsInterface.getGeotagging(this) && isNewPost()) {
mEditPostSettingsFragment.searchLocation();
}
return;
}
// Location permission denied
ToastUtils.showToast(this, getString(R.string.add_location_permission_required));
break;
case MEDIA_PERMISSION_REQUEST_CODE:
boolean shouldShowContextMenu = true;
for (int i = 0; i < grantResults.length; ++i) {
switch (permissions[i]) {
case Manifest.permission.CAMERA:
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
shouldShowContextMenu = false;
}
break;
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
shouldShowContextMenu = false;
} else {
registerReceiver(mGalleryReceiver,
new IntentFilter(LegacyEditorFragment.ACTION_MEDIA_GALLERY_TOUCHED));
refreshBlogMedia();
}
break;
}
}
if (shouldShowContextMenu) {
if (mMenuView != null) {
super.openContextMenu(mMenuView);
mMenuView = null;
}
} else {
ToastUtils.showToast(this, getString(R.string.access_media_permission_required));
}
break;
case DRAG_AND_DROP_MEDIA_PERMISSION_REQUEST_CODE:
boolean mediaAccessGranted = false;
for (int i = 0; i < grantResults.length; ++i) {
switch (permissions[i]) {
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
mediaAccessGranted = true;
}
break;
}
}
if (mediaAccessGranted) {
runOnUiThread(mFetchMediaRunnable);
} else {
ToastUtils.showToast(this, getString(R.string.access_media_permission_required));
}
default:
break;
}
}
// Menu actions
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
Fragment fragment = getFragmentManager().findFragmentByTag(
ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_TAG);
if (fragment != null && fragment.isVisible()) {
return false;
}
if (mViewPager.getCurrentItem() > PAGE_CONTENT) {
if (mViewPager.getCurrentItem() == PAGE_SETTINGS) {
mPost.setFeaturedImageId(mEditPostSettingsFragment.getFeaturedImageId());
mEditorFragment.setFeaturedImageId(mPost.getFeaturedImageId());
}
mViewPager.setCurrentItem(PAGE_CONTENT);
invalidateOptionsMenu();
} else {
saveAndFinish();
}
return true;
}
MediaUploadService mediaUploadService = MediaUploadService.getInstance();
// Disable format bar buttons while a media upload is in progress
if ((mediaUploadService != null && mediaUploadService.hasUploads()) || mEditorFragment.isUploadingMedia() ||
mEditorFragment.isActionInProgress()) {
ToastUtils.showToast(this, R.string.editor_toast_uploading_please_wait, Duration.SHORT);
return false;
}
if (itemId == R.id.menu_save_post) {
return publishPost();
} else if (itemId == R.id.menu_preview_post) {
mViewPager.setCurrentItem(PAGE_PREVIEW);
} else if (itemId == R.id.menu_post_settings) {
InputMethodManager imm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
if (mShowNewEditor) {
mEditPostSettingsFragment.updateFeaturedImage(mPost.getFeaturedImageId());
}
mViewPager.setCurrentItem(PAGE_SETTINGS);
}
return false;
}
private boolean publishPost() {
if (!NetworkUtils.isNetworkAvailable(this)) {
ToastUtils.showToast(this, R.string.error_publish_no_network, Duration.SHORT);
return false;
}
// Show an Alert Dialog asking the user if he wants to remove all failed media before upload
if (mEditorFragment.hasFailedMediaUploads()) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.editor_toast_failed_uploads)
.setPositiveButton(R.string.editor_remove_failed_uploads, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Clear failed uploads
mEditorFragment.removeAllFailedMediaUploads();
}
}).setNegativeButton(android.R.string.cancel, null);
builder.create().show();
return true;
}
// Update post, save to db and publish in its own Thread, because 1. update can be pretty slow with a lot of
// text 2. better not to call `updatePostObject()` from the UI thread due to weird thread blocking behavior
// on API 16 with the visual editor.
new Thread(new Runnable() {
@Override
public void run() {
updatePostObject(false);
savePostToDb();
// If the post is empty, don't publish
if (!mPost.isPublishable()) {
mHandler.post(new Runnable() {
@Override
public void run() {
ToastUtils.showToast(EditPostActivity.this, R.string.error_publish_empty_post, Duration.SHORT);
}
});
return;
}
PostUtils.trackSavePostAnalytics(mPost);
PostUploadService.addPostToUpload(mPost);
PostUploadService.setLegacyMode(!mShowNewEditor);
startService(new Intent(EditPostActivity.this, PostUploadService.class));
setResult(RESULT_OK);
finish();
}
}).start();
return true;
}
@Override
public void openContextMenu(View view) {
if (PermissionUtils.checkAndRequestCameraAndStoragePermissions(this, MEDIA_PERMISSION_REQUEST_CODE)) {
super.openContextMenu(view);
} else {
mMenuView = view;
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
menu.add(0, SELECT_PHOTO_MENU_POSITION, 0, getResources().getText(R.string.select_photo));
if (DeviceUtils.getInstance().hasCamera(this)) {
menu.add(0, CAPTURE_PHOTO_MENU_POSITION, 0, getResources().getText(R.string.media_add_popup_capture_photo));
}
menu.add(0, SELECT_VIDEO_MENU_POSITION, 0, getResources().getText(R.string.select_video));
if (DeviceUtils.getInstance().hasCamera(this)) {
menu.add(0, CAPTURE_VIDEO_MENU_POSITION, 0, getResources().getText(R.string.media_add_popup_capture_video));
}
menu.add(0, ADD_GALLERY_MENU_POSITION, 0, getResources().getText(R.string.media_add_new_media_gallery));
menu.add(0, SELECT_LIBRARY_MENU_POSITION, 0, getResources().getText(R.string.select_from_media_library));
menu.add(0, NEW_PICKER_MENU_POSITION, 0, getResources().getText(R.string.select_from_new_picker));
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case SELECT_PHOTO_MENU_POSITION:
launchPictureLibrary();
return true;
case CAPTURE_PHOTO_MENU_POSITION:
launchCamera();
return true;
case SELECT_VIDEO_MENU_POSITION:
launchVideoLibrary();
return true;
case CAPTURE_VIDEO_MENU_POSITION:
launchVideoCamera();
return true;
case ADD_GALLERY_MENU_POSITION:
startMediaGalleryActivity(null);
return true;
case SELECT_LIBRARY_MENU_POSITION:
startMediaGalleryAddActivity();
return true;
case NEW_PICKER_MENU_POSITION:
startMediaSelection();
return true;
default:
return false;
}
}
private void launchPictureLibrary() {
WordPressMediaUtils.launchPictureLibrary(this);
AppLockManager.getInstance().setExtendedTimeout();
}
private void launchVideoLibrary() {
WordPressMediaUtils.launchVideoLibrary(this);
AppLockManager.getInstance().setExtendedTimeout();
}
private void launchVideoCamera() {
WordPressMediaUtils.launchVideoCamera(this);
AppLockManager.getInstance().setExtendedTimeout();
}
private void showErrorAndFinish(int errorMessageId) {
Toast.makeText(this, getResources().getText(errorMessageId), Toast.LENGTH_LONG).show();
finish();
}
public Post getPost() {
return mPost;
}
private void trackEditorCreatedPost(String action, Intent intent) {
Map<String, Object> properties = new HashMap<String, Object>();
// Post created from the post list (new post button).
String normalizedSourceName = "post-list";
if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
// Post created with share with WordPress
normalizedSourceName = "shared-from-external-app";
}
if (EditPostActivity.NEW_MEDIA_GALLERY.equals(action) || EditPostActivity.NEW_MEDIA_POST.equals(
action)) {
// Post created from the media library
normalizedSourceName = "media-library";
}
if (intent != null && intent.hasExtra(EXTRA_IS_QUICKPRESS)) {
// Quick press
normalizedSourceName = "quick-press";
}
if (intent != null && intent.getIntExtra("quick-media", -1) > -1) {
// Quick photo or quick video
normalizedSourceName = "quick-media";
}
properties.put("created_post_source", normalizedSourceName);
AnalyticsUtils.trackWithBlogDetails(
AnalyticsTracker.Stat.EDITOR_CREATED_POST,
WordPress.getBlog(mPost.getLocalTableBlogId()),
properties
);
}
private synchronized void updatePostObject(boolean isAutosave) {
if (mPost == null) {
AppLog.e(AppLog.T.POSTS, "Attempted to save an invalid Post.");
return;
}
// Update post object from fragment fields
if (mEditorFragment != null) {
if (mShowNewEditor) {
updatePostContentNewEditor(isAutosave, (String) mEditorFragment.getTitle(),
(String) mEditorFragment.getContent());
} else {
// TODO: Remove when legacy editor is dropped
updatePostContent(isAutosave);
}
}
if (mEditPostSettingsFragment != null) {
mEditPostSettingsFragment.updatePostSettings();
}
}
private void savePostAsync(final AfterSavePostListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
updatePostObject(false);
savePostToDb();
if (listener != null) {
listener.onPostSave();
}
}
}).start();
}
private interface AfterSavePostListener {
void onPostSave();
}
private synchronized void savePostToDb() {
WordPress.wpDB.updatePost(mPost);
}
@Override
public void onBackPressed() {
Fragment imageSettingsFragment = getFragmentManager().findFragmentByTag(
ImageSettingsDialogFragment.IMAGE_SETTINGS_DIALOG_TAG);
if (imageSettingsFragment != null && imageSettingsFragment.isVisible()) {
((ImageSettingsDialogFragment) imageSettingsFragment).dismissFragment();
return;
}
if (mViewPager.getCurrentItem() > PAGE_CONTENT) {
if (mViewPager.getCurrentItem() == PAGE_SETTINGS) {
mPost.setFeaturedImageId(mEditPostSettingsFragment.getFeaturedImageId());
mEditorFragment.setFeaturedImageId(mPost.getFeaturedImageId());
}
mViewPager.setCurrentItem(PAGE_CONTENT);
invalidateOptionsMenu();
return;
}
if (mEditorFragment != null && !mEditorFragment.onBackPressed()) {
saveAndFinish();
}
}
public boolean isNewPost() {
return mIsNewPost;
}
private class SaveAndFinishTask extends AsyncTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
// Fetch post title and content from editor fields and update the Post object
updatePostObject(false);
if (mEditorFragment != null && mPost.hasEmptyContentFields()) {
// new and empty post? delete it
if (mIsNewPost) {
WordPress.wpDB.deletePost(mPost);
return false;
}
} else if (mOriginalPost != null && !mPost.hasChanges(mOriginalPost)) {
// if no changes have been made to the post, set it back to the original don't save it
WordPress.wpDB.updatePost(mOriginalPost);
return false;
} else {
// changes have been made, save the post and ask for the post list to refresh.
// We consider this being "manual save", it will replace some Android "spans" by an html
// or a shortcode replacement (for instance for images and galleries)
if (mShowNewEditor) {
// Update the post object directly, without re-fetching the fields from the EditorFragment
updatePostContentNewEditor(false, mPost.getTitle(), mPost.getContent());
savePostToDb();
} else {
updatePostObject(false);
savePostToDb();
}
}
return true;
}
@Override
protected void onPostExecute(Boolean saved) {
if (saved) {
Intent i = new Intent();
i.putExtra(EXTRA_SAVED_AS_LOCAL_DRAFT, true);
i.putExtra(EXTRA_IS_PAGE, mIsPage);
setResult(RESULT_OK, i);
ToastUtils.showToast(EditPostActivity.this, R.string.editor_toast_changes_saved);
}
finish();
}
}
private void saveAndFinish() {
new SaveAndFinishTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
* Disable visual editor mode and log the exception if we get a Reflection failure when the webview is being
* initialized.
*/
@Override
public void onReflectionFailure(ReflectionException e) {
CrashlyticsUtils.logException(e, ExceptionType.SPECIFIC, T.EDITOR, "Reflection Failure on Visual Editor init");
// Disable visual editor and show an error message
AppPrefs.setVisualEditorEnabled(false);
ToastUtils.showToast(this, R.string.new_editor_reflection_error, Duration.LONG);
// Restart the activity (will start the legacy editor)
finish();
startActivity(getIntent());
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// Show two pages for the visual editor, and add a third page for the EditPostPreviewFragment for legacy
private static final int NUM_PAGES_VISUAL_EDITOR = 2;
private static final int NUM_PAGES_LEGACY_EDITOR = 3;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
switch (position) {
case 0:
// TODO: switch between legacy and new editor here (AB test?)
if (mShowNewEditor) {
EditorWebViewCompatibility.setReflectionFailureListener(EditPostActivity.this);
return new EditorFragment();
} else {
return new LegacyEditorFragment();
}
case 1:
return new EditPostSettingsFragment();
default:
return new EditPostPreviewFragment();
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
switch (position) {
case 0:
mEditorFragment = (EditorFragmentAbstract) fragment;
if (mEditorFragment instanceof EditorMediaUploadListener) {
mEditorMediaUploadListener = (EditorMediaUploadListener) mEditorFragment;
// Set up custom headers for the visual editor's internal WebView
mEditorFragment.setCustomHttpHeader("User-Agent", WordPress.getUserAgent());
}
break;
case 1:
mEditPostSettingsFragment = (EditPostSettingsFragment) fragment;
break;
case 2:
mEditPostPreviewFragment = (EditPostPreviewFragment) fragment;
break;
}
return fragment;
}
@Override
public int getCount() {
return (mShowNewEditor ? NUM_PAGES_VISUAL_EDITOR : NUM_PAGES_LEGACY_EDITOR);
}
}
public boolean isEditingPostContent() {
return (mViewPager.getCurrentItem() == PAGE_CONTENT);
}
// Moved from EditPostContentFragment
public static final String NEW_MEDIA_GALLERY = "NEW_MEDIA_GALLERY";
public static final String NEW_MEDIA_GALLERY_EXTRA_IDS = "NEW_MEDIA_GALLERY_EXTRA_IDS";
public static final String NEW_MEDIA_POST = "NEW_MEDIA_POST";
public static final String NEW_MEDIA_POST_EXTRA = "NEW_MEDIA_POST_ID";
private String mMediaCapturePath = "";
private int mMaxThumbWidth = 0;
private int getMaximumThumbnailWidthForEditor() {
if (mMaxThumbWidth == 0) {
mMaxThumbWidth = ImageUtils.getMaximumThumbnailWidthForEditor(this);
}
return mMaxThumbWidth;
}
private MediaFile createMediaFile(String blogId, final String mediaId) {
Cursor cursor = WordPress.wpDB.getMediaFile(blogId, mediaId);
if (cursor == null || !cursor.moveToFirst()) {
if (cursor != null) {
cursor.close();
}
return null;
}
String url = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_URL));
if (url == null) {
cursor.close();
return null;
}
MediaFile mediaFile = new MediaFile();
mediaFile.setMediaId(mediaId);
mediaFile.setBlogId(blogId);
mediaFile.setFileURL(url);
mediaFile.setCaption(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_CAPTION)));
mediaFile.setDescription(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_DESCRIPTION)));
mediaFile.setTitle(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_TITLE)));
mediaFile.setWidth(cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_WIDTH)));
mediaFile.setHeight(cursor.getInt(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_HEIGHT)));
mediaFile.setFileName(cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_NAME)));
mediaFile.setDateCreatedGMT(cursor.getLong(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_DATE_CREATED_GMT)));
mediaFile.setVideoPressShortCode(cursor.getString(cursor.getColumnIndex(
WordPressDB.COLUMN_NAME_VIDEO_PRESS_SHORTCODE)));
String mimeType = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MIME_TYPE));
mediaFile.setMimeType(mimeType);
if (mimeType != null && !mimeType.isEmpty()) {
mediaFile.setVideo(mimeType.contains("video"));
} else {
mediaFile.setVideo(MediaUtils.isVideo(url));
}
// Make sure we're using a valid thumbnail for video. XML-RPC returns the video URL itself as the thumbnail URL
// for videos. If we can't get a real thumbnail for the Media Library video (currently only possible for
// VideoPress videos), we should not set any thumbnail.
String thumbnailUrl = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_THUMBNAIL_URL));
if (mediaFile.isVideo() && !MediaUtils.isValidImage(thumbnailUrl)) {
if (WPUrlUtils.isWordPressCom(url)) {
thumbnailUrl = WordPressMediaUtils.getVideoPressVideoPosterFromURL(url);
} else {
thumbnailUrl = "";
}
}
mediaFile.setThumbnailURL(thumbnailUrl);
WordPress.wpDB.saveMediaFile(mediaFile);
cursor.close();
return mediaFile;
}
private void addExistingMediaToEditor(String mediaId) {
if (WordPress.getCurrentBlog() == null) {
return;
}
String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
MediaFile mediaFile = createMediaFile(blogId, mediaId);
if (mediaFile == null) {
return;
}
trackAddMediaEvents(mediaFile.isVideo(), true);
mEditorFragment.appendMediaFile(mediaFile, getMediaUrl(mediaFile), WordPress.imageLoader);
}
/**
* Get media url from a MediaFile, returns a photon URL if the selected blog is Photon capable.
*/
private String getMediaUrl(MediaFile mediaFile) {
if (mediaFile == null) {
return null;
}
// Since Photon doesn't support video, skip Photon checking and return the existing file URL
// (using a Photon URL for video will result in a 404 error)
if (mediaFile.isVideo()) {
return mediaFile.getFileURL();
}
String imageURL;
if (WordPress.getCurrentBlog() != null && WordPress.getCurrentBlog().isPhotonCapable()) {
String photonUrl = mediaFile.getFileURL();
imageURL = StringUtils.getPhotonUrl(photonUrl, getMaximumThumbnailWidthForEditor());
} else {
// Not a Jetpack or wpcom blog
// imageURL = mediaFile.getThumbnailURL(); // do not use fileURL here since downloading picture
// of big dimensions can result in OOM Exception
imageURL = mediaFile.getFileURL() != null ? mediaFile.getFileURL() : mediaFile.getThumbnailURL();
}
return imageURL;
}
private class LoadPostContentTask extends AsyncTask<String, Spanned, Spanned> {
@Override
protected Spanned doInBackground(String... params) {
if (params.length < 1 || getPost() == null) {
return null;
}
String content = StringUtils.notNullStr(params[0]);
return WPHtml.fromHtml(content, EditPostActivity.this, getPost(), getMaximumThumbnailWidthForEditor());
}
@Override
protected void onPostExecute(Spanned spanned) {
if (spanned != null) {
mEditorFragment.setContent(spanned);
}
}
}
private String getUploadErrorHtml(String mediaId, String path) {
String replacement;
if (Build.VERSION.SDK_INT >= 19) {
replacement = String.format(Locale.US,
"<span id=\"img_container_%s\" class=\"img_container failed\" data-failed=\"%s\"><progress " +
"id=\"progress_%s\" value=\"0\" class=\"wp_media_indicator failed\" contenteditable=\"false\">" +
"</progress><img data-wpid=\"%s\" src=\"%s\" alt=\"\" class=\"failed\"></span>",
mediaId, getString(R.string.tap_to_try_again), mediaId, mediaId, path);
} else {
// Before API 19, the WebView didn't support progress tags. Use an upload overlay instead of a progress bar
replacement = String.format(Locale.US,
"<span id=\"img_container_%s\" class=\"img_container compat failed\" contenteditable=\"false\" " +
"data-failed=\"%s\"><span class=\"upload-overlay failed\" " +
"contenteditable=\"false\">Uploading…</span><span class=\"upload-overlay-bg\"></span>" +
"<img data-wpid=\"%s\" src=\"%s\" alt=\"\" class=\"failed\"></span>",
mediaId, getString(R.string.tap_to_try_again), mediaId, path);
}
return replacement;
}
private String migrateLegacyDraft(String content) {
if (content.contains("<img src=\"null\" android-uri=\"")) {
// We must replace image tags specific to the legacy editor local drafts:
// <img src="null" android-uri="file:///..." />
// And trigger an upload action for the specific image / video
Pattern pattern = Pattern.compile("<img src=\"null\" android-uri=\"([^\"]*)\".*>");
Matcher matcher = pattern.matcher(content);
StringBuffer stringBuffer = new StringBuffer();
while (matcher.find()) {
String path = null;
String stringUri = matcher.group(1);
Uri uri = Uri.parse(stringUri);
if (uri != null && stringUri.contains("content:")) {
path = getPathFromContentUri(uri);
if (path == null) {
continue;
}
} else {
path = stringUri.replace("file://", "");
}
MediaFile mediaFile = queueFileForUpload(path, null, "failed");
if (mediaFile == null) {
continue;
}
String replacement = getUploadErrorHtml(mediaFile.getMediaId(), mediaFile.getFilePath());
matcher.appendReplacement(stringBuffer, replacement);
}
matcher.appendTail(stringBuffer);
content = stringBuffer.toString();
}
if (content.contains("[caption")) {
// Convert old legacy post caption formatting to new format, to avoid being stripped by the visual editor
Pattern pattern = Pattern.compile("(\\[caption[^]]*caption=\"([^\"]*)\"[^]]*].+?)(\\[\\/caption])");
Matcher matcher = pattern.matcher(content);
StringBuffer stringBuffer = new StringBuffer();
while (matcher.find()) {
String replacement = matcher.group(1) + matcher.group(2) + matcher.group(3);
matcher.appendReplacement(stringBuffer, replacement);
}
matcher.appendTail(stringBuffer);
content = stringBuffer.toString();
}
return content;
}
private void fillContentEditorFields() {
// Needed blog settings needed by the editor
if (WordPress.getCurrentBlog() != null) {
mEditorFragment.setFeaturedImageSupported(WordPress.getCurrentBlog().isFeaturedImageCapable());
mEditorFragment.setBlogSettingMaxImageWidth(WordPress.getCurrentBlog().getMaxImageWidth());
}
// Set up the placeholder text
mEditorFragment.setContentPlaceholder(getString(R.string.editor_content_placeholder));
mEditorFragment.setTitlePlaceholder(getString(mIsPage ? R.string.editor_page_title_placeholder :
R.string.editor_post_title_placeholder));
// Set post title and content
Post post = getPost();
if (post != null) {
if (!TextUtils.isEmpty(post.getContent()) && !mHasSetPostContent) {
mHasSetPostContent = true;
if (post.isLocalDraft() && !mShowNewEditor) {
// TODO: Unnecessary for new editor, as all images are uploaded right away, even for local drafts
// Load local post content in the background, as it may take time to generate images
new LoadPostContentTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
post.getContent().replaceAll("\uFFFC", ""));
} else {
// TODO: Might be able to drop .replaceAll() when legacy editor is removed
String content = post.getContent().replaceAll("\uFFFC", "");
// Prepare eventual legacy editor local draft for the new editor
content = migrateLegacyDraft(content);
mEditorFragment.setContent(content);
}
}
if (!TextUtils.isEmpty(post.getTitle())) {
mEditorFragment.setTitle(post.getTitle());
}
// TODO: postSettingsButton.setText(post.isPage() ? R.string.page_settings : R.string.post_settings);
mEditorFragment.setLocalDraft(post.isLocalDraft());
mEditorFragment.setFeaturedImageId(mPost.getFeaturedImageId());
}
// Special actions
String action = getIntent().getAction();
int quickMediaType = getIntent().getIntExtra("quick-media", -1);
if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
setPostContentFromShareAction();
} else if (NEW_MEDIA_GALLERY.equals(action)) {
prepareMediaGallery();
} else if (NEW_MEDIA_POST.equals(action)) {
prepareMediaPost();
} else if (quickMediaType >= 0) {
// User selected 'Quick Photo' in the menu drawer
if (quickMediaType == Constants.QUICK_POST_PHOTO_CAMERA) {
launchCamera();
} else if (quickMediaType == Constants.QUICK_POST_PHOTO_LIBRARY) {
WordPressMediaUtils.launchPictureLibrary(this);
}
if (post != null) {
post.setQuickPostType(Post.QUICK_MEDIA_TYPE_PHOTO);
}
}
}
private void launchCamera() {
WordPressMediaUtils.launchCamera(this, BuildConfig.APPLICATION_ID,
new WordPressMediaUtils.LaunchCameraCallback() {
@Override
public void onMediaCapturePathReady(String mediaCapturePath) {
mMediaCapturePath = mediaCapturePath;
AppLockManager.getInstance().setExtendedTimeout();
}
});
}
protected void setPostContentFromShareAction() {
Intent intent = getIntent();
// Check for shared text
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
String title = intent.getStringExtra(Intent.EXTRA_SUBJECT);
if (text != null) {
if (title != null) {
mEditorFragment.setTitle(title);
}
// Create an <a href> element around links
text = AutolinkUtils.autoCreateLinks(text);
if (mEditorFragment instanceof EditorFragment) {
mEditorFragment.setContent(text);
} else {
mEditorFragment.setContent(WPHtml.fromHtml(StringUtils.addPTags(text), this, getPost(),
getMaximumThumbnailWidthForEditor()));
}
}
// Check for shared media
if (intent.hasExtra(Intent.EXTRA_STREAM)) {
String action = intent.getAction();
String type = intent.getType();
ArrayList<Uri> sharedUris;
if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
sharedUris = intent.getParcelableArrayListExtra((Intent.EXTRA_STREAM));
} else {
// For a single media share, we only allow images and video types
if (type != null && (type.startsWith("image") || type.startsWith("video"))) {
sharedUris = new ArrayList<Uri>();
sharedUris.add((Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM));
} else {
return;
}
}
if (sharedUris != null) {
for (Uri uri : sharedUris) {
addMedia(uri);
}
}
}
}
private void startMediaGalleryActivity(MediaGallery mediaGallery) {
Intent intent = new Intent(this, MediaGalleryActivity.class);
intent.putExtra(MediaGalleryActivity.PARAMS_MEDIA_GALLERY, mediaGallery);
if (mediaGallery == null) {
intent.putExtra(MediaGalleryActivity.PARAMS_LAUNCH_PICKER, true);
}
startActivityForResult(intent, MediaGalleryActivity.REQUEST_CODE);
}
private void prepareMediaGallery() {
MediaGallery mediaGallery = new MediaGallery();
mediaGallery.setIds(getIntent().getStringArrayListExtra(NEW_MEDIA_GALLERY_EXTRA_IDS));
startMediaGalleryActivity(mediaGallery);
}
private void prepareMediaPost() {
String mediaId = getIntent().getStringExtra(NEW_MEDIA_POST_EXTRA);
addExistingMediaToEditor(mediaId);
}
// TODO: Replace with contents of the updatePostContentNewEditor() method when legacy editor is dropped
/**
* Updates post object with content of this fragment
*/
public void updatePostContent(boolean isAutoSave) {
Post post = getPost();
if (post == null) {
return;
}
String title = StringUtils.notNullStr((String) mEditorFragment.getTitle());
SpannableStringBuilder postContent;
if (mEditorFragment.getSpannedContent() != null) {
// needed by the legacy editor to save local drafts
try {
postContent = new SpannableStringBuilder(mEditorFragment.getSpannedContent());
} catch (IndexOutOfBoundsException e) {
// A core android bug might cause an out of bounds exception, if so we'll just use the current editable
// See https://code.google.com/p/android/issues/detail?id=5164
postContent = new SpannableStringBuilder(StringUtils.notNullStr((String) mEditorFragment.getContent()));
}
} else {
postContent = new SpannableStringBuilder(StringUtils.notNullStr((String) mEditorFragment.getContent()));
}
String content;
if (post.isLocalDraft()) {
// remove suggestion spans, they cause craziness in WPHtml.toHTML().
CharacterStyle[] characterStyles = postContent.getSpans(0, postContent.length(), CharacterStyle.class);
for (CharacterStyle characterStyle : characterStyles) {
if (characterStyle instanceof SuggestionSpan) {
postContent.removeSpan(characterStyle);
}
}
content = WPHtml.toHtml(postContent);
// replace duplicate <p> tags so there's not duplicates, trac #86
content = content.replace("<p><p>", "<p>");
content = content.replace("</p></p>", "</p>");
content = content.replace("<br><br>", "<br>");
// sometimes the editor creates extra tags
content = content.replace("</strong><strong>", "").replace("</em><em>", "").replace("</u><u>", "")
.replace("</strike><strike>", "").replace("</blockquote><blockquote>", "");
} else {
if (!isAutoSave) {
// Add gallery shortcode
MediaGalleryImageSpan[] gallerySpans = postContent.getSpans(0, postContent.length(),
MediaGalleryImageSpan.class);
for (MediaGalleryImageSpan gallerySpan : gallerySpans) {
int start = postContent.getSpanStart(gallerySpan);
postContent.removeSpan(gallerySpan);
postContent.insert(start, WPHtml.getGalleryShortcode(gallerySpan));
}
}
WPImageSpan[] imageSpans = postContent.getSpans(0, postContent.length(), WPImageSpan.class);
if (imageSpans.length != 0) {
for (WPImageSpan wpIS : imageSpans) {
MediaFile mediaFile = wpIS.getMediaFile();
if (mediaFile == null)
continue;
if (mediaFile.getMediaId() != null) {
updateMediaFileOnServer(wpIS);
} else {
mediaFile.setFileName(wpIS.getImageSource().toString());
mediaFile.setFilePath(wpIS.getImageSource().toString());
WordPress.wpDB.saveMediaFile(mediaFile);
}
int tagStart = postContent.getSpanStart(wpIS);
if (!isAutoSave) {
postContent.removeSpan(wpIS);
// network image has a mediaId
if (mediaFile.getMediaId() != null && mediaFile.getMediaId().length() > 0) {
postContent.insert(tagStart, WPHtml.getContent(wpIS));
} else {
// local image for upload
postContent.insert(tagStart,
"<img android-uri=\"" + wpIS.getImageSource().toString() + "\" />");
}
}
}
}
content = postContent.toString();
}
String moreTag = "<!--more-->";
post.setTitle(title);
// split up the post content if there's a more tag
if (post.isLocalDraft() && content.contains(moreTag)) {
post.setDescription(content.substring(0, content.indexOf(moreTag)));
post.setMoreText(content.substring(content.indexOf(moreTag) + moreTag.length(), content.length()));
} else {
post.setDescription(content);
post.setMoreText("");
}
if (!post.isLocalDraft()) {
post.setLocalChange(true);
}
}
/**
* Updates post object with given title and content
*/
public void updatePostContentNewEditor(boolean isAutoSave, String title, String content) {
Post post = getPost();
if (post == null) {
return;
}
if (!isAutoSave) {
// TODO: Shortcode handling, media handling
}
String moreTag = "<!--more-->";
post.setTitle(title);
// split up the post content if there's a more tag
if (post.isLocalDraft() && content.contains(moreTag)) {
post.setDescription(content.substring(0, content.indexOf(moreTag)));
post.setMoreText(content.substring(content.indexOf(moreTag) + moreTag.length(), content.length()));
} else {
post.setDescription(content);
post.setMoreText("");
}
if (!post.isLocalDraft()) {
post.setLocalChange(true);
}
}
/**
* Media
*/
private void fetchMedia(List<Uri> mediaUris) {
for (Uri mediaUri : mediaUris) {
if (mediaUri == null) {
Toast.makeText(EditPostActivity.this,
getResources().getText(R.string.gallery_error), Toast.LENGTH_SHORT).show();
continue;
}
if (!addMedia(mediaUri)) {
Toast.makeText(EditPostActivity.this, getResources().getText(R.string.gallery_error),
Toast.LENGTH_SHORT).show();
}
}
}
private void updateMediaFileOnServer(WPImageSpan wpIS) {
Blog currentBlog = WordPress.getCurrentBlog();
if (currentBlog == null || wpIS == null)
return;
MediaFile mf = wpIS.getMediaFile();
final String mediaId = mf.getMediaId();
final String title = mf.getTitle();
final String description = mf.getDescription();
final String caption = mf.getCaption();
ApiHelper.EditMediaItemTask task = new ApiHelper.EditMediaItemTask(mf.getMediaId(), mf.getTitle(),
mf.getDescription(), mf.getCaption(),
new ApiHelper.GenericCallback() {
@Override
public void onSuccess() {
if (WordPress.getCurrentBlog() == null) {
return;
}
String localBlogTableIndex = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
WordPress.wpDB.updateMediaFile(localBlogTableIndex, mediaId, title, description, caption);
}
@Override
public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) {
Toast.makeText(EditPostActivity.this, R.string.media_edit_failure, Toast.LENGTH_LONG).show();
}
});
List<Object> apiArgs = new ArrayList<Object>();
apiArgs.add(currentBlog);
task.execute(apiArgs);
}
private void trackAddMediaEvents(boolean isVideo, boolean fromMediaLibrary) {
if (isVideo) {
AnalyticsTracker.track(fromMediaLibrary ? Stat.EDITOR_ADDED_VIDEO_VIA_WP_MEDIA_LIBRARY
: Stat.EDITOR_ADDED_VIDEO_VIA_LOCAL_LIBRARY);
} else {
AnalyticsTracker.track(fromMediaLibrary ? Stat.EDITOR_ADDED_PHOTO_VIA_WP_MEDIA_LIBRARY
: Stat.EDITOR_ADDED_PHOTO_VIA_LOCAL_LIBRARY);
}
}
private boolean addMedia(Uri mediaUri) {
if (mediaUri != null && !MediaUtils.isInMediaStore(mediaUri) && !mediaUri.toString().startsWith("/")) {
mediaUri = MediaUtils.downloadExternalMedia(this, mediaUri);
}
if (mediaUri == null) {
return false;
}
boolean isVideo = MediaUtils.isVideo(mediaUri.toString());
trackAddMediaEvents(isVideo, false);
if (mShowNewEditor) {
// TODO: add video param
return addMediaVisualEditor(mediaUri);
} else {
return addMediaLegacyEditor(mediaUri, isVideo);
}
}
private String getPathFromContentUri(Uri imageUri) {
String path = null;
String[] projection = new String[]{MediaStore.Images.Media.DATA};
Cursor cur = getContentResolver().query(imageUri, projection, null, null, null);
if (cur != null && cur.moveToFirst()) {
int dataColumn = cur.getColumnIndex(MediaStore.Images.Media.DATA);
path = cur.getString(dataColumn);
cur.close();
}
return path;
}
private boolean addMediaVisualEditor(Uri imageUri) {
String path = "";
if (imageUri.toString().contains("content:")) {
path = getPathFromContentUri(imageUri);
} else {
// File is not in media library
path = imageUri.toString().replace("file://", "");
}
if (path == null) {
ToastUtils.showToast(this, R.string.file_not_found, Duration.SHORT);
return false;
}
Blog blog = WordPress.getCurrentBlog();
if (MediaUtils.getImageWidthSettingFromString(blog.getMaxImageWidth()) != Integer.MAX_VALUE) {
// If the user has selected a maximum image width for uploads, rescale the image accordingly
path = ImageUtils.createResizedImageWithMaxWidth(this, path, Integer.parseInt(blog.getMaxImageWidth()));
}
MediaFile mediaFile = queueFileForUpload(path, new ArrayList<String>());
if (mediaFile != null) {
mEditorFragment.appendMediaFile(mediaFile, path, WordPress.imageLoader);
}
return true;
}
private boolean addMediaLegacyEditor(Uri mediaUri, boolean isVideo) {
String mediaTitle;
if (isVideo) {
mediaTitle = getResources().getString(R.string.video);
} else {
mediaTitle = ImageUtils.getTitleForWPImageSpan(this, mediaUri.getEncodedPath());
}
MediaFile mediaFile = new MediaFile();
mediaFile.setPostID(getPost().getLocalTablePostId());
mediaFile.setTitle(mediaTitle);
mediaFile.setFilePath(mediaUri.toString());
if (mediaUri.getEncodedPath() != null) {
mediaFile.setVideo(isVideo);
}
WordPress.wpDB.saveMediaFile(mediaFile);
mEditorFragment.appendMediaFile(mediaFile, mediaFile.getFilePath(), WordPress.imageLoader);
return true;
}
@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))) {
switch (requestCode) {
case MediaPickerActivity.ACTIVITY_REQUEST_CODE_MEDIA_SELECTION:
if (resultCode == MediaPickerActivity.ACTIVITY_RESULT_CODE_MEDIA_SELECTED) {
handleMediaSelectionResult(data);
} else if (resultCode == MediaPickerActivity.ACTIVITY_RESULT_CODE_GALLERY_CREATED) {
handleGalleryResult(data);
}
break;
case MediaGalleryActivity.REQUEST_CODE:
if (resultCode == Activity.RESULT_OK) {
handleMediaGalleryResult(data);
}
break;
case MediaGalleryPickerActivity.REQUEST_CODE:
if (resultCode == Activity.RESULT_OK) {
handleMediaGalleryPickerResult(data);
}
break;
case RequestCodes.PICTURE_LIBRARY:
Uri imageUri = data.getData();
fetchMedia(Arrays.asList(imageUri));
break;
case RequestCodes.TAKE_PHOTO:
if (resultCode == Activity.RESULT_OK) {
try {
File f = new File(mMediaCapturePath);
Uri capturedImageUri = Uri.fromFile(f);
if (!addMedia(capturedImageUri)) {
ToastUtils.showToast(this, R.string.gallery_error, Duration.SHORT);
}
this.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+ Environment.getExternalStorageDirectory())));
} catch (RuntimeException e) {
AppLog.e(T.POSTS, e);
} catch (OutOfMemoryError e) {
AppLog.e(T.POSTS, e);
}
} else if (TextUtils.isEmpty(mEditorFragment.getContent())) {
// TODO: check if it was mQuickMediaType > -1
// Quick Photo was cancelled, delete post and finish activity
WordPress.wpDB.deletePost(getPost());
finish();
}
break;
case RequestCodes.VIDEO_LIBRARY:
Uri videoUri = data.getData();
fetchMedia(Arrays.asList(videoUri));
break;
case RequestCodes.TAKE_VIDEO:
if (resultCode == Activity.RESULT_OK) {
Uri capturedVideoUri = MediaUtils.getLastRecordedVideoUri(this);
if (!addMedia(capturedVideoUri)) {
ToastUtils.showToast(this, R.string.gallery_error, Duration.SHORT);
}
} else if (TextUtils.isEmpty(mEditorFragment.getContent())) {
// TODO: check if it was mQuickMediaType > -1
// Quick Photo was cancelled, delete post and finish activity
WordPress.wpDB.deletePost(getPost());
finish();
}
break;
}
}
}
private void startMediaGalleryAddActivity() {
Intent intent = new Intent(this, MediaGalleryPickerActivity.class);
intent.putExtra(MediaGalleryPickerActivity.PARAM_SELECT_ONE_ITEM, true);
startActivityForResult(intent, MediaGalleryPickerActivity.REQUEST_CODE);
}
private void handleMediaGalleryPickerResult(Intent data) {
ArrayList<String> ids = data.getStringArrayListExtra(MediaGalleryPickerActivity.RESULT_IDS);
if (ids == null || ids.size() == 0) {
return;
}
String mediaId = ids.get(0);
addExistingMediaToEditor(mediaId);
}
private void handleMediaGalleryResult(Intent data) {
MediaGallery gallery = (MediaGallery) data.getSerializableExtra(MediaGalleryActivity.RESULT_MEDIA_GALLERY);
// if blank gallery returned, don't add to span
if (gallery == null || gallery.getIds().size() == 0) {
return;
}
mEditorFragment.appendGallery(gallery);
}
/**
* Handles result from {@link org.wordpress.android.ui.media.MediaPickerActivity}. Uploads local
* media to users blog then adds a gallery to the Post with all the selected media.
*
* @param data
* contains the selected media content with key
* {@link org.wordpress.android.ui.media.MediaPickerActivity#SELECTED_CONTENT_RESULTS_KEY}
*/
private void handleGalleryResult(Intent data) {
if (data != null) {
List<MediaItem> selectedContent = data.getParcelableArrayListExtra(MediaPickerActivity.SELECTED_CONTENT_RESULTS_KEY);
if (selectedContent != null && selectedContent.size() > 0) {
ArrayList<String> blogMediaIds = new ArrayList<>();
ArrayList<String> localMediaIds = new ArrayList<>();
for (MediaItem content : selectedContent) {
Uri source = content.getSource();
final String id = content.getTag();
if (source != null && id != null) {
final String sourceString = source.toString();
if (MediaUtils.isVideo(sourceString)) {
// Videos cannot be added to a gallery, insert inline instead
addMedia(source);
} else if (URLUtil.isNetworkUrl(sourceString)) {
blogMediaIds.add(id);
} else if (MediaUtils.isValidImage(sourceString)) {
queueFileForUpload(sourceString, localMediaIds);
}
}
}
MediaGallery gallery = new MediaGallery();
gallery.setIds(blogMediaIds);
if (localMediaIds.size() > 0) {
NotificationManager notificationManager = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext());
builder.setSmallIcon(android.R.drawable.stat_sys_upload);
builder.setContentTitle("Uploading gallery");
notificationManager.notify(10, builder.build());
mPendingGalleryUploads.put(gallery.getUniqueId(), new ArrayList<>(localMediaIds));
}
// Only insert gallery span if images were added
if (localMediaIds.size() > 0 || blogMediaIds.size() > 0) {
mEditorFragment.appendGallery(gallery);
}
}
}
}
/**
* Handles result from {@link org.wordpress.android.ui.media.MediaPickerActivity} by adding the
* selected media to the Post.
*
* @param data
* result {@link android.content.Intent} with selected media items
*/
private void handleMediaSelectionResult(Intent data) {
if (data == null) {
return;
}
final List<MediaItem> selectedContent =
data.getParcelableArrayListExtra(MediaPickerActivity.SELECTED_CONTENT_RESULTS_KEY);
if (selectedContent != null && selectedContent.size() > 0) {
for (MediaItem media : selectedContent) {
if (URLUtil.isNetworkUrl(media.getSource().toString())) {
addExistingMediaToEditor(media.getTag());
} else {
addMedia(media.getSource());
}
}
}
}
/**
* Create image {@link org.wordpress.mediapicker.source.MediaSource}'s for media selection.
*
* @return
* list containing all sources to gather image media from
*/
private ArrayList<MediaSource> imageMediaSelectionSources() {
ArrayList<MediaSource> imageMediaSources = new ArrayList<>();
imageMediaSources.add(new MediaSourceDeviceImages());
return imageMediaSources;
}
private ArrayList<MediaSource> blogImageMediaSelectionSources() {
ArrayList<MediaSource> imageMediaSources = new ArrayList<>();
imageMediaSources.add(new MediaSourceWPImages());
return imageMediaSources;
}
private ArrayList<MediaSource> blogVideoMediaSelectionSources() {
ArrayList<MediaSource> imageMediaSources = new ArrayList<>();
imageMediaSources.add(new MediaSourceWPVideos());
return imageMediaSources;
}
/**
* Create video {@link org.wordpress.mediapicker.source.MediaSource}'s for media selection.
*
* @return
* list containing all sources to gather video media from
*/
private ArrayList<MediaSource> videoMediaSelectionSources() {
ArrayList<MediaSource> videoMediaSources = new ArrayList<>();
videoMediaSources.add(new MediaSourceDeviceVideos());
return videoMediaSources;
}
private BroadcastReceiver mGalleryReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (LegacyEditorFragment.ACTION_MEDIA_GALLERY_TOUCHED.equals(intent.getAction())) {
startMediaGalleryActivity((MediaGallery)intent.getSerializableExtra(LegacyEditorFragment.EXTRA_MEDIA_GALLERY));
}
}
};
/**
* Handles media upload notifications. Used by the visual editor when uploading local media, and for both
* the visual and the legacy editor to create a gallery after media selection from local media.
*/
@SuppressWarnings("unused")
public void onEventMainThread(MediaEvents.MediaUploadSucceeded event) {
for (Long galleryId : mPendingGalleryUploads.keySet()) {
if (mPendingGalleryUploads.get(galleryId).contains(event.mLocalMediaId)) {
if (mEditorMediaUploadListener != null) {
// Notify the visual editor of gallery image upload
int remaining = mPendingGalleryUploads.get(galleryId).size() - 1;
mEditorMediaUploadListener.onGalleryMediaUploadSucceeded(galleryId, event.mRemoteMediaId, remaining);
} else {
handleGalleryImageUploadedLegacyEditor(galleryId, event.mLocalMediaId, event.mRemoteMediaId);
}
mPendingGalleryUploads.get(galleryId).remove(event.mLocalMediaId);
if (mPendingGalleryUploads.get(galleryId).size() == 0) {
mPendingGalleryUploads.remove(galleryId);
}
if (mPendingGalleryUploads.size() == 0) {
stopMediaUploadService();
NotificationManager notificationManager = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
notificationManager.cancel(10);
}
return;
}
}
// Notify visual editor that a normal media item has finished uploading (not part of a gallery)
if (mEditorMediaUploadListener != null) {
MediaFile mediaFile = new MediaFile();
mediaFile.setPostID(getPost().getLocalTablePostId());
mediaFile.setMediaId(event.mRemoteMediaId);
mediaFile.setFileURL(event.mRemoteMediaUrl);
mediaFile.setVideoPressShortCode(event.mSecondaryRemoteMediaId);
mediaFile.setThumbnailURL(WordPressMediaUtils.getVideoPressVideoPosterFromURL(event.mRemoteMediaUrl));
mEditorMediaUploadListener.onMediaUploadSucceeded(event.mLocalMediaId, mediaFile);
}
}
public void onEventMainThread(MediaEvents.MediaUploadFailed event) {
AnalyticsTracker.track(Stat.EDITOR_UPLOAD_MEDIA_FAILED);
if (mEditorMediaUploadListener != null) {
if (event.mIsGenericMessage) {
mEditorMediaUploadListener.onMediaUploadFailed(event.mLocalMediaId, getString(R.string.tap_to_try_again));
} else {
mEditorMediaUploadListener.onMediaUploadFailed(event.mLocalMediaId, event.mErrorMessage);
}
}
}
public void onEventMainThread(MediaEvents.MediaUploadProgress event) {
if (mEditorMediaUploadListener != null) {
mEditorMediaUploadListener.onMediaUploadProgress(event.mLocalMediaId, event.mProgress);
}
}
private void handleGalleryImageUploadedLegacyEditor(Long galleryId, String localId, String remoteId) {
SpannableStringBuilder postContent;
if (mEditorFragment.getSpannedContent() != null) {
// needed by the legacy editor to save local drafts
postContent = new SpannableStringBuilder(mEditorFragment.getSpannedContent());
} else {
postContent = new SpannableStringBuilder(StringUtils.notNullStr((String)
mEditorFragment.getContent()));
}
int selectionStart = 0;
int selectionEnd = postContent.length();
MediaGalleryImageSpan[] gallerySpans = postContent.getSpans(selectionStart, selectionEnd,
MediaGalleryImageSpan.class);
if (gallerySpans.length != 0) {
for (MediaGalleryImageSpan gallerySpan : gallerySpans) {
MediaGallery gallery = gallerySpan.getMediaGallery();
if (gallery.getUniqueId() == galleryId) {
ArrayList<String> galleryIds = gallery.getIds();
galleryIds.add(remoteId);
gallery.setIds(galleryIds);
gallerySpan.setMediaGallery(gallery);
int spanStart = postContent.getSpanStart(gallerySpan);
int spanEnd = postContent.getSpanEnd(gallerySpan);
postContent.setSpan(gallerySpan, spanStart, spanEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
/**
* Starts {@link org.wordpress.android.ui.media.MediaPickerActivity} after refreshing the blog media.
*/
private void startMediaSelection() {
Intent intent = new Intent(this, MediaPickerActivity.class);
intent.putExtra(MediaPickerActivity.ACTIVITY_TITLE_KEY, getString(R.string.add_to_post));
intent.putParcelableArrayListExtra(MediaPickerActivity.DEVICE_IMAGE_MEDIA_SOURCES_KEY,
imageMediaSelectionSources());
intent.putParcelableArrayListExtra(MediaPickerActivity.DEVICE_VIDEO_MEDIA_SOURCES_KEY,
videoMediaSelectionSources());
if (mBlogMediaStatus != 0) {
intent.putParcelableArrayListExtra(MediaPickerActivity.BLOG_IMAGE_MEDIA_SOURCES_KEY,
blogImageMediaSelectionSources());
intent.putParcelableArrayListExtra(MediaPickerActivity.BLOG_VIDEO_MEDIA_SOURCES_KEY,
blogVideoMediaSelectionSources());
}
startActivityForResult(intent, MediaPickerActivity.ACTIVITY_REQUEST_CODE_MEDIA_SELECTION);
overridePendingTransition(R.anim.slide_up, R.anim.fade_out);
}
private void refreshBlogMedia() {
if (NetworkUtils.isNetworkAvailable(this)) {
List<Object> apiArgs = new ArrayList<Object>();
apiArgs.add(WordPress.getCurrentBlog());
ApiHelper.SyncMediaLibraryTask.Callback callback = new ApiHelper.SyncMediaLibraryTask.Callback() {
@Override
public void onSuccess(int count) {
mBlogMediaStatus = 1;
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mPendingVideoPressInfoRequests != null && !mPendingVideoPressInfoRequests.isEmpty()) {
// If there are pending requests for video URLs from VideoPress ids, query the DB for
// them again and notify the editor
String blogId = String.valueOf(WordPress.currentBlog.getLocalTableBlogId());
for (String videoId : mPendingVideoPressInfoRequests) {
String videoUrl = WordPress.wpDB.getMediaUrlByVideoPressId(blogId, videoId);
String posterUrl = WordPressMediaUtils.getVideoPressVideoPosterFromURL(videoUrl);
mEditorFragment.setUrlForVideoPressId(videoId, videoUrl, posterUrl);
}
mPendingVideoPressInfoRequests.clear();
}
}
});
}
@Override
public void onFailure(final ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) {
mBlogMediaStatus = 0;
ToastUtils.showToast(EditPostActivity.this, R.string.error_refresh_media, ToastUtils.Duration.SHORT);
}
};
ApiHelper.SyncMediaLibraryTask getMediaTask = new ApiHelper.SyncMediaLibraryTask(0,
MediaGridFragment.Filter.ALL, callback);
getMediaTask.execute(apiArgs);
} else {
mBlogMediaStatus = 0;
ToastUtils.showToast(this, R.string.error_refresh_media, ToastUtils.Duration.SHORT);
}
}
/**
* Starts the upload service to upload selected media.
*/
private void startMediaUploadService() {
if (!mMediaUploadServiceStarted) {
startService(new Intent(this, MediaUploadService.class));
mMediaUploadServiceStarted = true;
}
}
/**
* Stops the upload service.
*/
private void stopMediaUploadService() {
if (mMediaUploadServiceStarted) {
stopService(new Intent(this, MediaUploadService.class));
mMediaUploadServiceStarted = false;
}
}
/**
* Queues a media file for upload and starts the MediaUploadService. Toasts will alert the user
* if there are issues with the file.
*
* @param path
* local path of the media file to upload
* @param mediaIdOut
* the new {@link org.wordpress.android.util.helpers.MediaFile} ID is added if non-null
*/
private MediaFile queueFileForUpload(String path, ArrayList<String> mediaIdOut) {
return queueFileForUpload(path, mediaIdOut, "queued");
}
private MediaFile queueFileForUpload(String path, ArrayList<String> mediaIdOut, String startingState) {
// Invalid file path
if (TextUtils.isEmpty(path)) {
Toast.makeText(this, R.string.editor_toast_invalid_path, Toast.LENGTH_SHORT).show();
return null;
}
// File not found
File file = new File(path);
if (!file.exists()) {
Toast.makeText(this, R.string.file_not_found, Toast.LENGTH_SHORT).show();
return null;
}
Blog blog = WordPress.getCurrentBlog();
long currentTime = System.currentTimeMillis();
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(startingState);
mediaFile.setDateCreatedGMT(currentTime);
mediaFile.setMediaId(String.valueOf(currentTime));
mediaFile.setVideo(MediaUtils.isVideo(path));
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);
}
if (mediaIdOut != null) {
mediaIdOut.add(mediaFile.getMediaId());
}
saveMediaFile(mediaFile);
startMediaUploadService();
return mediaFile;
}
/**
* EditorFragmentListener methods
*/
@Override
public void onSettingsClicked() {
mViewPager.setCurrentItem(PAGE_SETTINGS);
}
@Override
public void onAddMediaClicked() {
// no op
}
@Override
public void onMediaDropped(final ArrayList<Uri> mediaUris) {
mDroppedMediaUris = mediaUris;
if (PermissionUtils.checkAndRequestStoragePermission(this, DRAG_AND_DROP_MEDIA_PERMISSION_REQUEST_CODE)) {
runOnUiThread(mFetchMediaRunnable);
}
}
@Override
public void onRequestDragAndDropPermissions(DragEvent dragEvent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
requestTemporaryPermissions(dragEvent);
}
}
@TargetApi(Build.VERSION_CODES.N)
private void requestTemporaryPermissions(DragEvent dragEvent) {
requestDragAndDropPermissions(dragEvent);
}
@Override
public void onMediaRetryClicked(String mediaId) {
String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId());
WordPress.wpDB.updateMediaUploadState(blogId, mediaId, MediaUploadState.QUEUED);
MediaUploadService mediaUploadService = MediaUploadService.getInstance();
if (mediaUploadService == null) {
startMediaUploadService();
} else {
mediaUploadService.processQueue();
}
AnalyticsTracker.track(Stat.EDITOR_UPLOAD_MEDIA_RETRIED);
}
@Override
public void onMediaUploadCancelClicked(String mediaId, boolean delete) {
MediaUploadService mediaUploadService = MediaUploadService.getInstance();
if (mediaUploadService != null) {
mediaUploadService.cancelUpload(mediaId, delete);
}
}
@Override
public void onFeaturedImageChanged(long mediaId) {
mPost.setFeaturedImageId(mediaId);
mEditPostSettingsFragment.updateFeaturedImage(mediaId);
}
@Override
public void onVideoPressInfoRequested(final String videoId) {
String blogId = String.valueOf(WordPress.currentBlog.getLocalTableBlogId());
String videoUrl = WordPress.wpDB.getMediaUrlByVideoPressId(blogId, videoId);
if (videoUrl.isEmpty()) {
if (PermissionUtils.checkAndRequestCameraAndStoragePermissions(this, MEDIA_PERMISSION_REQUEST_CODE)) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mPendingVideoPressInfoRequests == null) {
mPendingVideoPressInfoRequests = new ArrayList<>();
}
mPendingVideoPressInfoRequests.add(videoId);
refreshBlogMedia();
}
});
}
}
String posterUrl = WordPressMediaUtils.getVideoPressVideoPosterFromURL(videoUrl);
mEditorFragment.setUrlForVideoPressId(videoId, videoUrl, posterUrl);
}
@Override
public String onAuthHeaderRequested(String url) {
String authHeader = "";
Blog currentBlog = WordPress.getCurrentBlog();
String token = AccountHelper.getDefaultAccount().getAccessToken();
if (currentBlog != null && currentBlog.isPrivate() && WPUrlUtils.safeToAddWordPressComAuthToken(url) &&
!TextUtils.isEmpty(token)) {
authHeader = "Bearer " + token;
}
return authHeader;
}
@Override
public void onEditorFragmentInitialized() {
fillContentEditorFields();
// Set the error listener
if (mEditorFragment instanceof EditorFragment) {
mEditorFragment.setDebugModeEnabled(BuildConfig.DEBUG);
((EditorFragment) mEditorFragment).setWebViewErrorListener(new ErrorListener() {
@Override
public void onJavaScriptError(String sourceFile, int lineNumber, String message) {
CrashlyticsUtils.logException(new JavaScriptException(sourceFile, lineNumber, message),
ExceptionType.SPECIFIC, T.EDITOR,
String.format(Locale.US, "%s:%d: %s", sourceFile, lineNumber, message));
}
@Override
public void onJavaScriptAlert(String url, String message) {
// no op
}
});
}
}
@Override
public void saveMediaFile(MediaFile mediaFile) {
WordPress.wpDB.saveMediaFile(mediaFile);
}
@Override
public void onTrackableEvent(TrackableEvent event) {
switch (event) {
case HTML_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_HTML);
break;
case MEDIA_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_IMAGE);
break;
case UNLINK_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_UNLINK);
break;
case LINK_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_LINK);
break;
case IMAGE_EDITED:
AnalyticsTracker.track(Stat.EDITOR_EDITED_IMAGE);
break;
case BOLD_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_BOLD);
break;
case ITALIC_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_ITALIC);
break;
case OL_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_ORDERED_LIST);
break;
case UL_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_UNORDERED_LIST);
break;
case BLOCKQUOTE_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_BLOCKQUOTE);
break;
case STRIKETHROUGH_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_STRIKETHROUGH);
break;
case UNDERLINE_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_UNDERLINE);
break;
case MORE_BUTTON_TAPPED:
AnalyticsTracker.track(Stat.EDITOR_TAPPED_MORE);
break;
}
}
}