diff options
9 files changed, 262 insertions, 2 deletions
diff --git a/android/source/AndroidManifest.xml b/android/source/AndroidManifest.xml index c2a3656f8e92..fb51eb4b0e43 100644 --- a/android/source/AndroidManifest.xml +++ b/android/source/AndroidManifest.xml @@ -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> diff --git a/android/source/res/layout/toolbar_bottom.xml b/android/source/res/layout/toolbar_bottom.xml index 1b4730d89adf..a537a52d32b9 100644 --- a/android/source/res/layout/toolbar_bottom.xml +++ b/android/source/res/layout/toolbar_bottom.xml @@ -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> diff --git a/android/source/res/values/strings.xml b/android/source/res/values/strings.xml index e84c496db3c5..3f6955cd7a7b 100644 --- a/android/source/res/values/strings.xml +++ b/android/source/res/values/strings.xml @@ -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> diff --git a/android/source/res/xml/file_paths.xml b/android/source/res/xml/file_paths.xml new file mode 100644 index 000000000000..2bbe2aef17ea --- /dev/null +++ b/android/source/res/xml/file_paths.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths> + <external-path name="LO_files_universal" path="Android/" /> +</paths>
\ No newline at end of file diff --git a/android/source/src/java/org/libreoffice/FormattingController.java b/android/source/src/java/org/libreoffice/FormattingController.java index 4d36249dc6b9..0ba72cf50875 100644 --- a/android/source/src/java/org/libreoffice/FormattingController.java +++ b/android/source/src/java/org/libreoffice/FormattingController.java @@ -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; + } } diff --git a/android/source/src/java/org/libreoffice/LOEvent.java b/android/source/src/java/org/libreoffice/LOEvent.java index 7846f7331bbd..4d081e61c0f2 100644 --- a/android/source/src/java/org/libreoffice/LOEvent.java +++ b/android/source/src/java/org/libreoffice/LOEvent.java @@ -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; diff --git a/android/source/src/java/org/libreoffice/LOKitThread.java b/android/source/src/java/org/libreoffice/LOKitThread.java index 721853d08a99..99b53397b9d0 100644 --- a/android/source/src/java/org/libreoffice/LOKitThread.java +++ b/android/source/src/java/org/libreoffice/LOKitThread.java @@ -354,7 +354,9 @@ class LOKitThread extends Thread { case LOEvent.UPDATE_CALC_HEADERS: updateCalcHeaders(); break; - + case LOEvent.REFRESH: + refresh(); + break; } } diff --git a/android/source/src/java/org/libreoffice/LOKitTileProvider.java b/android/source/src/java/org/libreoffice/LOKitTileProvider.java index 7be5ac31f60c..a68f65221795 100644 --- a/android/source/src/java/org/libreoffice/LOKitTileProvider.java +++ b/android/source/src/java/org/libreoffice/LOKitTileProvider.java @@ -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 diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java index defd0d18476e..534eaf44de59 100644 --- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java +++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java @@ -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: */ |