Kaydet (Commit) 8d977511 authored tarafından Ximeng Zu's avatar Ximeng Zu Kaydeden (comit) Tomaž Vajngerl

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/41150Reviewed-by: 's avatarTomaž Vajngerl <quikee@gmail.com>
Tested-by: 's avatarTomaž Vajngerl <quikee@gmail.com>
üst 4e2555b7
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<uses-feature android:glEsVersion="0x00020000" android:required="true" /> <uses-feature android:glEsVersion="0x00020000" android:required="true" />
<!-- App wants to know if device supports USB host capability(not mandatory) --> <!-- 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.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.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
...@@ -133,6 +134,16 @@ ...@@ -133,6 +134,16 @@
android:value=".LibreOfficeMainActivity" /> android:value=".LibreOfficeMainActivity" />
</activity> </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> </application>
</manifest> </manifest>
......
...@@ -328,6 +328,16 @@ ...@@ -328,6 +328,16 @@
android:paddingBottom="12dp" android:paddingBottom="12dp"
android:paddingTop="12dp" android:paddingTop="12dp"
app:srcCompat="@drawable/ic_rect" /> 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> </LinearLayout>
</ScrollView> </ScrollView>
</LinearLayout> </LinearLayout>
......
...@@ -157,4 +157,15 @@ ...@@ -157,4 +157,15 @@
<string name="action_pwd_dialog_cancel">Cancel</string> <string name="action_pwd_dialog_cancel">Cancel</string>
<string name="action_pwd_dialog_title">Please enter password</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> </resources>
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="LO_files_universal" path="Android/" />
</paths>
\ No newline at end of file
package org.libreoffice; 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.util.Log;
import android.view.View; import android.view.View;
import android.widget.ImageButton; import android.widget.ImageButton;
import org.json.JSONException;
import org.json.JSONObject;
import org.libreoffice.kit.Document; 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 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 LibreOfficeMainActivity mContext;
private String mCurrentPhotoPath;
FormattingController(LibreOfficeMainActivity context) { FormattingController(LibreOfficeMainActivity context) {
mContext = context; mContext = context;
...@@ -29,6 +59,7 @@ import org.libreoffice.kit.Document; ...@@ -29,6 +59,7 @@ import org.libreoffice.kit.Document;
mContext.findViewById(R.id.button_insert_line).setOnClickListener(this); mContext.findViewById(R.id.button_insert_line).setOnClickListener(this);
mContext.findViewById(R.id.button_insert_rect).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_shrink).setOnClickListener(this);
mContext.findViewById(R.id.button_font_grow).setOnClickListener(this); mContext.findViewById(R.id.button_font_grow).setOnClickListener(this);
...@@ -99,6 +130,8 @@ import org.libreoffice.kit.Document; ...@@ -99,6 +130,8 @@ import org.libreoffice.kit.Document;
case R.id.button_superscript: case R.id.button_superscript:
LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SuperScript")); LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SuperScript"));
break; break;
case R.id.button_insert_picture:
insertPicture();
} }
} }
...@@ -152,4 +185,177 @@ import org.libreoffice.kit.Document; ...@@ -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;
}
} }
...@@ -39,6 +39,7 @@ public class LOEvent implements Comparable<LOEvent> { ...@@ -39,6 +39,7 @@ public class LOEvent implements Comparable<LOEvent> {
public static final int UPDATE_PART_PAGE_RECT = 18; public static final int UPDATE_PART_PAGE_RECT = 18;
public static final int UPDATE_ZOOM_CONSTRAINTS = 19; public static final int UPDATE_ZOOM_CONSTRAINTS = 19;
public static final int UPDATE_CALC_HEADERS = 20; public static final int UPDATE_CALC_HEADERS = 20;
public static final int REFRESH = 21;
public final int mType; public final int mType;
public int mPriority = 0; public int mPriority = 0;
......
...@@ -354,7 +354,9 @@ class LOKitThread extends Thread { ...@@ -354,7 +354,9 @@ class LOKitThread extends Thread {
case LOEvent.UPDATE_CALC_HEADERS: case LOEvent.UPDATE_CALC_HEADERS:
updateCalcHeaders(); updateCalcHeaders();
break; break;
case LOEvent.REFRESH:
refresh();
break;
} }
} }
......
...@@ -150,6 +150,14 @@ class LOKitTileProvider implements TileProvider { ...@@ -150,6 +150,14 @@ class LOKitTileProvider implements TileProvider {
mContext.getDocumentPartViewListAdapter().notifyDataSetChanged(); mContext.getDocumentPartViewListAdapter().notifyDataSetChanged();
} }
}); });
mContext.runOnUiThread(new Runnable() {
@Override
public void run() {
if (mContext.pendingInsertGraphic) {
mContext.getFormattingController().popCompressImageGradeSelection();
}
}
});
} }
@Override @Override
......
...@@ -98,6 +98,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin ...@@ -98,6 +98,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
private LOKitTileProvider mTileProvider; private LOKitTileProvider mTileProvider;
private String mPassword; private String mPassword;
private boolean mPasswordProtected; private boolean mPasswordProtected;
public boolean pendingInsertGraphic; // boolean indicating a pending insert graphic action, used in LOKitTileProvider.postLoad()
public GeckoLayerClient getLayerClient() { public GeckoLayerClient getLayerClient() {
return mLayerClient; return mLayerClient;
...@@ -863,6 +864,12 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin ...@@ -863,6 +864,12 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
.setPositiveButton(R.string.alert_copy_svg_slide_show_to_clipboard_dismiss, null).show(); .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: */ /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment