tdf#106370 Android: add ability to insert pictures

Added ability to insert pictures to Android Viewer.
You can take photo or select photo from device or
the cloud (Google photos, Dropbox). You can also
compress the picture before inserting it with multiple
compress grades. So far, inserting doesn't work for
Writer due LO native library issues (I think).

Change-Id: If6841ba04fe18585703c8b85909cf39747dbbc2f
Reviewed-on: https://gerrit.libreoffice.org/41150
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
Tested-by: Tomaž Vajngerl <quikee@gmail.com>
This commit is contained in:
Ximeng Zu 2017-08-14 11:41:30 -05:00 committed by Tomaž Vajngerl
parent 4e2555b7f3
commit 8d977511e3
9 changed files with 262 additions and 2 deletions

View file

@ -7,6 +7,7 @@
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<!-- App wants to know if device supports USB host capability(not mandatory) -->
<uses-feature android:name="android.hardware.usb.host" android:required="false"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
@ -133,6 +134,16 @@
android:value=".LibreOfficeMainActivity" />
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View file

@ -328,6 +328,16 @@
android:paddingBottom="12dp"
android:paddingTop="12dp"
app:srcCompat="@drawable/ic_rect" />
<ImageButton
android:id="@+id/button_insert_picture"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:background="@drawable/image_button_background"
android:paddingBottom="12dp"
android:paddingTop="12dp"
app:srcCompat="@drawable/ic_folder_black_24dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View file

@ -157,4 +157,15 @@
<string name="action_pwd_dialog_cancel">Cancel</string>
<string name="action_pwd_dialog_title">Please enter password</string>
<!-- Insert Image Strings -->
<string name="take_photo">Take Photo</string>
<string name="select_photo">Select Photo</string>
<string name="select_photo_title">Select Picture</string>
<string name="no_camera_found">No Camera Found</string>
<string name="compress_photo_smallest_size">Smallest Size</string>
<string name="compress_photo_medium_size">Medium Size</string>
<string name="compress_photo_max_quality">Max Quality</string>
<string name="compress_photo_no_compress">Don\'t Compress</string>
<string name="compress_photo_title">Do you want to compress the photo?</string>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="LO_files_universal" path="Android/" />
</paths>

View file

@ -1,15 +1,45 @@
package org.libreoffice;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.design.widget.Snackbar;
import android.support.v4.content.FileProvider;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import org.json.JSONException;
import org.json.JSONObject;
import org.libreoffice.kit.Document;
class FormattingController implements View.OnClickListener {
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static org.libreoffice.SearchController.addProperty;
class FormattingController implements View.OnClickListener {
private static final String LOGTAG = ToolbarController.class.getSimpleName();
private static final int TAKE_PHOTO = 1;
private static final int SELECT_PHOTO = 2;
private static final int IMAGE_BUFFER_SIZE = 4 * 1024;
private LibreOfficeMainActivity mContext;
private String mCurrentPhotoPath;
FormattingController(LibreOfficeMainActivity context) {
mContext = context;
@ -29,6 +59,7 @@ import org.libreoffice.kit.Document;
mContext.findViewById(R.id.button_insert_line).setOnClickListener(this);
mContext.findViewById(R.id.button_insert_rect).setOnClickListener(this);
mContext.findViewById(R.id.button_insert_picture).setOnClickListener(this);
mContext.findViewById(R.id.button_font_shrink).setOnClickListener(this);
mContext.findViewById(R.id.button_font_grow).setOnClickListener(this);
@ -99,6 +130,8 @@ import org.libreoffice.kit.Document;
case R.id.button_superscript:
LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SuperScript"));
break;
case R.id.button_insert_picture:
insertPicture();
}
}
@ -152,4 +185,177 @@ import org.libreoffice.kit.Document;
}
});
}
private void insertPicture() {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
String[] options = {mContext.getResources().getString(R.string.take_photo),
mContext.getResources().getString(R.string.select_photo)};
builder.setItems(options, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
dispatchTakePictureIntent();
break;
case 1:
sendImagePickingIntent();
break;
default:
sendImagePickingIntent();
}
}
});
builder.show();
}
private void sendImagePickingIntent() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_PICK);
mContext.startActivityForResult(Intent.createChooser(intent,
mContext.getResources().getString(R.string.select_photo_title)), SELECT_PHOTO);
}
private void dispatchTakePictureIntent() {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Snackbar.make(mContext.findViewById(R.id.button_insert_picture),
mContext.getResources().getString(R.string.no_camera_found), Snackbar.LENGTH_SHORT).show();
return;
}
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(mContext.getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
ex.printStackTrace();
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(mContext,
mContext.getPackageName() + ".fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
// Grant permissions to potential photo/camera apps (for some Android versions)
List<ResolveInfo> resInfoList = mContext.getPackageManager()
.queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
mContext.grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
mContext.startActivityForResult(takePictureIntent, TAKE_PHOTO);
}
}
}
void handleActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TAKE_PHOTO && resultCode == Activity.RESULT_OK) {
mContext.pendingInsertGraphic = true;
} else if (requestCode == SELECT_PHOTO && resultCode == Activity.RESULT_OK) {
getFileFromURI(data.getData());
mContext.pendingInsertGraphic = true;
}
}
// Called by LOKitTileProvider when activity is resumed from photo/gallery/camera/cloud apps
void popCompressImageGradeSelection() {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
String[] options = {mContext.getResources().getString(R.string.compress_photo_smallest_size),
mContext.getResources().getString(R.string.compress_photo_medium_size),
mContext.getResources().getString(R.string.compress_photo_max_quality),
mContext.getResources().getString(R.string.compress_photo_no_compress)};
builder.setTitle(mContext.getResources().getString(R.string.compress_photo_title));
builder.setItems(options, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int compressGrade;
switch (which) {
case 0:
compressGrade = 0;
break;
case 1:
compressGrade = 50;
break;
case 2:
compressGrade = 100;
break;
case 3:
compressGrade = -1;
break;
default:
compressGrade = -1;
}
compressImage(compressGrade);
sendInsertGraphic();
}
});
builder.show();
}
private void getFileFromURI(Uri uri) {
try {
InputStream input = mContext.getContentResolver().openInputStream(uri);
mCurrentPhotoPath = createImageFile().getAbsolutePath();
FileOutputStream output = new FileOutputStream(mCurrentPhotoPath);
if (input != null) {
byte[] buffer = new byte[IMAGE_BUFFER_SIZE];
int read;
while ((read = input.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
input.close();
}
output.flush();
output.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendInsertGraphic() {
JSONObject rootJson = new JSONObject();
try {
addProperty(rootJson, "FileName", "string", "file://" + mCurrentPhotoPath);
} catch (JSONException ex) {
ex.printStackTrace();
}
LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertGraphic", rootJson.toString()));
LOKitShell.sendEvent(new LOEvent(LOEvent.REFRESH));
mContext.setDocumentChanged(true);
mContext.pendingInsertGraphic = false;
}
private void compressImage(int grade) {
if (grade < 0 || grade > 100) {
return;
}
mContext.showProgressSpinner();
Bitmap bmp = BitmapFactory.decodeFile(mCurrentPhotoPath);
try {
mCurrentPhotoPath = createImageFile().getAbsolutePath();
FileOutputStream out = new FileOutputStream(mCurrentPhotoPath);
bmp.compress(Bitmap.CompressFormat.JPEG, grade, out);
} catch (Exception e) {
e.printStackTrace();
}
mContext.hideProgressSpinner();
}
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
}

View file

@ -39,6 +39,7 @@ public class LOEvent implements Comparable<LOEvent> {
public static final int UPDATE_PART_PAGE_RECT = 18;
public static final int UPDATE_ZOOM_CONSTRAINTS = 19;
public static final int UPDATE_CALC_HEADERS = 20;
public static final int REFRESH = 21;
public final int mType;
public int mPriority = 0;

View file

@ -354,7 +354,9 @@ class LOKitThread extends Thread {
case LOEvent.UPDATE_CALC_HEADERS:
updateCalcHeaders();
break;
case LOEvent.REFRESH:
refresh();
break;
}
}

View file

@ -150,6 +150,14 @@ class LOKitTileProvider implements TileProvider {
mContext.getDocumentPartViewListAdapter().notifyDataSetChanged();
}
});
mContext.runOnUiThread(new Runnable() {
@Override
public void run() {
if (mContext.pendingInsertGraphic) {
mContext.getFormattingController().popCompressImageGradeSelection();
}
}
});
}
@Override

View file

@ -98,6 +98,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
private LOKitTileProvider mTileProvider;
private String mPassword;
private boolean mPasswordProtected;
public boolean pendingInsertGraphic; // boolean indicating a pending insert graphic action, used in LOKitTileProvider.postLoad()
public GeckoLayerClient getLayerClient() {
return mLayerClient;
@ -863,6 +864,12 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
.setPositiveButton(R.string.alert_copy_svg_slide_show_to_clipboard_dismiss, null).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFormattingController.handleActivityResult(requestCode, resultCode, data);
hideBottomToolbar();
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */