diff options
Diffstat (limited to 'android/source/src/java/org')
59 files changed, 1174 insertions, 4408 deletions
diff --git a/android/source/src/java/org/libreoffice/AboutDialogFragment.java b/android/source/src/java/org/libreoffice/AboutDialogFragment.java index 6c944bae7ef1..0d9fc45856ef 100644 --- a/android/source/src/java/org/libreoffice/AboutDialogFragment.java +++ b/android/source/src/java/org/libreoffice/AboutDialogFragment.java @@ -18,21 +18,16 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; import android.text.Html; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.view.View; import android.widget.TextView; -import java.io.File; - public class AboutDialogFragment extends DialogFragment { - private static final String DEFAULT_DOC_PATH = "/assets/example.odt"; - - @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { @@ -45,66 +40,60 @@ public class AboutDialogFragment extends DialogFragment { int defaultColor = textView.getTextColors().getDefaultColor(); textView.setTextColor(defaultColor); - // Take care of placeholders in the version and vendor text views. - TextView versionView = messageView.findViewById(R.id.about_version); - TextView vendorView = messageView.findViewById(R.id.about_vendor); + // Take care of placeholders and set text in version and vendor text views. try { String versionName = getActivity().getPackageManager() .getPackageInfo(getActivity().getPackageName(), 0).versionName; - String[] tokens = versionName.split("/"); - if (tokens.length == 3) - { - String version = String.format(versionView.getText().toString().replace("\n", "<br/>"), - tokens[0], "<a href=\"https://hub.libreoffice.org/git-core/" + tokens[1] + "\">" + tokens[1] + "</a>"); - @SuppressWarnings("deprecation") // since 24 with additional option parameter - Spanned versionString = Html.fromHtml(version); - versionView.setText(versionString); - versionView.setMovementMethod(LinkMovementMethod.getInstance()); - String vendor = vendorView.getText().toString(); - vendor = vendor.replace("$VENDOR", tokens[2]); - vendorView.setText(vendor); - } - else - throw new PackageManager.NameNotFoundException(); + String version = String.format(getString(R.string.app_version), versionName, BuildConfig.BUILD_ID_SHORT); + @SuppressWarnings("deprecation") // since 24 with additional option parameter + Spanned versionString = Html.fromHtml(version); + TextView versionView = messageView.findViewById(R.id.about_version); + versionView.setText(versionString); + versionView.setMovementMethod(LinkMovementMethod.getInstance()); + TextView vendorView = messageView.findViewById(R.id.about_vendor); + String vendor = getString(R.string.app_vendor).replace("$VENDOR", BuildConfig.VENDOR); + vendorView.setText(vendor); } catch (PackageManager.NameNotFoundException e) { - versionView.setText(""); - vendorView.setText(""); } AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder .setIcon(R.drawable.lo_icon) + builder .setIcon(R.mipmap.ic_launcher) .setTitle(R.string.app_name) .setView(messageView) .setNegativeButton(R.string.about_license, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { - loadFromAbout("/assets/license.txt"); + loadFromAbout(R.raw.license); dialog.dismiss(); } }) .setPositiveButton(R.string.about_notice, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { - loadFromAbout("/assets/notice.txt"); - dialog.dismiss(); - } - }) - .setNeutralButton(R.string.about_moreinfo, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - loadFromAbout(DEFAULT_DOC_PATH); + loadFromAbout(R.raw.notice); dialog.dismiss(); } }); + // when privacy policy URL is set (via '--with-privacy-policy-url=<url>' autogen option), + // add button to open that URL + final String privacyUrl = BuildConfig.PRIVACY_POLICY_URL; + if (!privacyUrl.isEmpty() && privacyUrl != "undefined") { + builder.setNeutralButton(R.string.about_privacy_policy, (DialogInterface dialog, int id) -> { + Intent openPrivacyUrlIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(privacyUrl)); + startActivity(openPrivacyUrlIntent); + dialog.dismiss(); + }); + } + return builder.create(); } - private void loadFromAbout(String input) { - Intent i = new Intent(Intent.ACTION_VIEW, Uri.fromFile(new File(input))); + private void loadFromAbout(int resourceId) { + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("android.resource://" + BuildConfig.APPLICATION_ID + "/" + resourceId)); String packageName = getActivity().getApplicationContext().getPackageName(); ComponentName componentName = new ComponentName(packageName, LibreOfficeMainActivity.class.getName()); i.setComponent(componentName); diff --git a/android/source/src/java/org/libreoffice/ColorPaletteAdapter.java b/android/source/src/java/org/libreoffice/ColorPaletteAdapter.java index 6ec6aa138f66..16d8a977864f 100644 --- a/android/source/src/java/org/libreoffice/ColorPaletteAdapter.java +++ b/android/source/src/java/org/libreoffice/ColorPaletteAdapter.java @@ -1,7 +1,7 @@ package org.libreoffice; import android.content.Context; -import android.support.v7.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,12 +12,12 @@ import android.widget.ImageButton; public class ColorPaletteAdapter extends RecyclerView.Adapter<ColorPaletteAdapter.ColorPaletteViewHolder> { - int[][] color_palette; - Context mContext; - int upperSelectedBox = -1; - int selectedBox = 0; - boolean animate; - ColorPaletteListener colorPaletteListener; + private int[][] color_palette; + private final Context mContext; + private int upperSelectedBox = -1; + private int selectedBox = 0; + private boolean animate; + private final ColorPaletteListener colorPaletteListener; public ColorPaletteAdapter(Context mContext, ColorPaletteListener colorPaletteListener) { this.mContext = mContext; @@ -36,6 +36,10 @@ public class ColorPaletteAdapter extends RecyclerView.Adapter<ColorPaletteAdapte return selectedBox; } + public int getUpperSelectedBox() { + return upperSelectedBox; + } + @Override public void onBindViewHolder(final ColorPaletteViewHolder holder, int position) { @@ -128,4 +132,4 @@ public class ColorPaletteAdapter extends RecyclerView.Adapter<ColorPaletteAdapte } -}
\ No newline at end of file +} diff --git a/android/source/src/java/org/libreoffice/ColorPickerAdapter.java b/android/source/src/java/org/libreoffice/ColorPickerAdapter.java index c93d5a01bbb4..a17dd264fb99 100644 --- a/android/source/src/java/org/libreoffice/ColorPickerAdapter.java +++ b/android/source/src/java/org/libreoffice/ColorPickerAdapter.java @@ -3,7 +3,7 @@ package org.libreoffice; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; -import android.support.v7.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,12 +12,11 @@ import android.widget.ImageButton; public class ColorPickerAdapter extends RecyclerView.Adapter<ColorPickerAdapter.ColorPickerViewHolder> { - Context mContext; - ColorPaletteAdapter colorPaletteAdapter; - ColorPaletteListener colorPaletteListener; - int[] colorList; - int[][] colorPalette = new int[11][8]; - int selectedBox = 0; + private final Context mContext; + private final ColorPaletteAdapter colorPaletteAdapter; + private final ColorPaletteListener colorPaletteListener; + private final int[] colorList; + private final int[][] colorPalette = new int[11][8]; public ColorPickerAdapter(Context mContext, final ColorPaletteAdapter colorPaletteAdapter, ColorPaletteListener colorPaletteListener) { this.mContext = mContext; @@ -41,10 +40,11 @@ public class ColorPickerAdapter extends RecyclerView.Adapter<ColorPickerAdapter. public void onBindViewHolder(final ColorPickerViewHolder holder, int position) { holder.colorBox.setBackgroundColor(colorList[position]); - if (selectedBox != position) - holder.colorBox.setImageDrawable(null); - else { + if (colorPaletteAdapter.getUpperSelectedBox() == position + && colorPaletteAdapter.getSelectedBox() >= 0) { holder.colorBox.setImageResource(R.drawable.ic_done_white_12dp); + } else { + holder.colorBox.setImageDrawable(null); } holder.colorBox.setOnClickListener(new View.OnClickListener() { @@ -64,12 +64,20 @@ public class ColorPickerAdapter extends RecyclerView.Adapter<ColorPickerAdapter. private void setPosition(int position) { - this.selectedBox = position; selectSubColor(position, position==0?0:3); colorPaletteListener.applyColor(colorList[position]); updateAdapter(); } + /** + * Switches to first palette, but doesn't mark any color as selected. + * Use this if no color in the palette matches the actual one. + */ + public void unselectColors() { + colorPaletteAdapter.changePosition(0, -1); + updateAdapter(); + } + private void selectSubColor(int position1, int position2) { colorPaletteAdapter.setPosition(position1, position2); } @@ -88,7 +96,15 @@ public class ColorPickerAdapter extends RecyclerView.Adapter<ColorPickerAdapter. int red_shade = red; int green_shade = green; int blue_shade = blue; - if (i != 0) { + if (i == 0) { + colorPalette[0][0] = colorList[i]; + for (int k = 1; k < 7; k++) { + red_tint = (int) (red_tint + (255 - red_tint) * 0.25); + green_tint = (int) (green_tint + (255 - green_tint) * 0.25); + blue_tint = (int) (blue_tint + (255 - blue_tint) * 0.25); + colorPalette[i][k] = (Color.rgb(red_tint, green_tint, blue_tint)); + } + } else { colorPalette[i][3] = colorList[i]; for (int k = 2; k >= 0; k--) { red_shade = (int) (red_shade * 0.75); @@ -102,48 +118,28 @@ public class ColorPickerAdapter extends RecyclerView.Adapter<ColorPickerAdapter. blue_tint = (int) (blue_tint + (255 - blue_tint) * 0.45); colorPalette[i][k] = (Color.rgb(red_tint, green_tint, blue_tint)); } - } else { - colorPalette[0][0] = colorList[i]; - for (int k = 1; k < 7; k++) { - red_tint = (int) (red_tint + (255 - red_tint) * 0.25); - green_tint = (int) (green_tint + (255 - green_tint) * 0.25); - blue_tint = (int) (blue_tint + (255 - blue_tint) * 0.25); - colorPalette[i][k] = (Color.rgb(red_tint, green_tint, blue_tint)); - } } - } - for (int i = 0; i < 11; i++){ - this.colorPalette[i][7] = (Color.rgb(255, 255, 255)); // last one is always white + colorPalette[i][7] = Color.WHITE; // last one is always white } colorPaletteAdapter.setColorPalette(colorPalette); } public void findSelectedTextColor(int color) { - /* - Libreoffice recognizes -1 as Black - */ - if (color == -1) { - colorPaletteAdapter.changePosition(0, 0); - selectedBox = 0; - updateAdapter(); - return; - } - /* - Find the color if the palette points another color - */ - if (colorPalette[selectedBox][colorPaletteAdapter.getSelectedBox()] != color) { - for (int i = 0; i < 11; i++) { - for (int k = 0; k < 8; k++) { - if (colorPalette[i][k] == color) { - colorPaletteAdapter.changePosition(i, k); - selectedBox = i; - updateAdapter(); - return; - } + // try to find and highlight the color in the existing palettes + for (int i = 0; i < 11; i++) { + for (int k = 0; k < 8; k++) { + if (colorPalette[i][k] == color) { + colorPaletteAdapter.changePosition(i, k); + updateAdapter(); + return; } } } + + // no color in the palettes matched + unselectColors(); } + private void updateAdapter(){ LOKitShell.getMainHandler().post(new Runnable() { @Override @@ -163,4 +159,4 @@ public class ColorPickerAdapter extends RecyclerView.Adapter<ColorPickerAdapter. this.colorBox = itemView.findViewById(R.id.fontColorBox); } } -}
\ No newline at end of file +} diff --git a/android/source/src/java/org/libreoffice/DocumentPartViewListAdapter.java b/android/source/src/java/org/libreoffice/DocumentPartViewListAdapter.java index a576fc67dcb2..a0ed871a40d2 100644 --- a/android/source/src/java/org/libreoffice/DocumentPartViewListAdapter.java +++ b/android/source/src/java/org/libreoffice/DocumentPartViewListAdapter.java @@ -19,7 +19,6 @@ import android.widget.TextView; import java.util.List; public class DocumentPartViewListAdapter extends ArrayAdapter<DocumentPartView> { - private static final String LOGTAG = DocumentPartViewListAdapter.class.getSimpleName(); private final Activity activity; private final ThumbnailCreator thumbnailCollector; diff --git a/android/source/src/java/org/libreoffice/FontController.java b/android/source/src/java/org/libreoffice/FontController.java index a00e13e1485c..72f35d8b42d8 100644 --- a/android/source/src/java/org/libreoffice/FontController.java +++ b/android/source/src/java/org/libreoffice/FontController.java @@ -2,12 +2,13 @@ package org.libreoffice; import android.graphics.Color; import android.graphics.Rect; -import android.support.design.widget.BottomSheetBehavior; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; @@ -23,12 +24,15 @@ import java.util.Iterator; public class FontController implements AdapterView.OnItemSelectedListener { + /** -1 as value in ".uno:Color" et al. means "automatic color"/no color set. */ + private static final int COLOR_AUTO = -1; + private boolean mFontNameSpinnerSet = false; private boolean mFontSizeSpinnerSet = false; private final LibreOfficeMainActivity mActivity; - private final ArrayList<String> mFontList = new ArrayList<String>(); - private final ArrayList<String> mFontSizes = new ArrayList<String>(); - private final HashMap<String, ArrayList<String>> mAllFontSizes = new HashMap<String, ArrayList<String>>(); + private final ArrayList<String> mFontList = new ArrayList<>(); + private final ArrayList<String> mFontSizes = new ArrayList<>(); + private final HashMap<String, ArrayList<String>> mAllFontSizes = new HashMap<>(); private String mCurrentFontSelected = null; private String mCurrentFontSizeSelected = null; @@ -45,29 +49,44 @@ public class FontController implements AdapterView.OnItemSelectedListener { final ColorPaletteListener colorPaletteListener = new ColorPaletteListener() { @Override public void applyColor(int color) { - sendFontColorChange(color); + sendFontColorChange(color, false); } @Override public void updateColorPickerPosition(int color) { - if (null == colorPickerAdapter) return; - colorPickerAdapter.findSelectedTextColor(color + 0xFF000000); - changeFontColorBoxColor(color + 0xFF000000); + if (colorPickerAdapter == null) { + return; + } + if (color == COLOR_AUTO) { + colorPickerAdapter.unselectColors(); + changeFontColorBoxColor(Color.TRANSPARENT); + return; + } + final int colorWithAlpha = color | 0xFF000000; + colorPickerAdapter.findSelectedTextColor(colorWithAlpha); + changeFontColorBoxColor(colorWithAlpha); } }; final ColorPaletteListener backColorPaletteListener = new ColorPaletteListener() { @Override public void applyColor(int color) { - sendFontBackColorChange(color); + sendFontBackColorChange(color, false); } @Override public void updateColorPickerPosition(int color) { - if(backColorPickerAdapter != null) - backColorPickerAdapter.findSelectedTextColor(color + 0xFF000000); - changeFontBackColorBoxColor(color + 0xFF000000); - + if (backColorPickerAdapter == null) { + return; + } + if (color == COLOR_AUTO) { + backColorPickerAdapter.unselectColors(); + changeFontBackColorBoxColor(Color.TRANSPARENT); + return; + } + final int colorWithAlpha = color | 0xFF000000; + backColorPickerAdapter.findSelectedTextColor(colorWithAlpha); + changeFontBackColorBoxColor(colorWithAlpha); } }; @@ -77,11 +96,7 @@ public class FontController implements AdapterView.OnItemSelectedListener { LOKitShell.getMainHandler().post(new Runnable() { @Override public void run() { - if(color == -1){ //Libreoffice recognizes -1 as black - fontColorPickerButton.setBackgroundColor(Color.BLACK); - }else{ - fontColorPickerButton.setBackgroundColor(color); - } + fontColorPickerButton.setBackgroundColor(color); } }); } @@ -92,12 +107,7 @@ public class FontController implements AdapterView.OnItemSelectedListener { LOKitShell.getMainHandler().post(new Runnable() { @Override public void run() { - if(color == -1){ //Libreoffice recognizes -1 as black - fontBackColorPickerButton.setBackgroundColor(Color.BLACK); - }else{ - fontBackColorPickerButton.setBackgroundColor(color); - - } + fontBackColorPickerButton.setBackgroundColor(color); } }); } @@ -132,12 +142,12 @@ public class FontController implements AdapterView.OnItemSelectedListener { } } - private void sendFontColorChange(int color){ + private void sendFontColorChange(int color, boolean keepAlpha){ try { JSONObject json = new JSONObject(); JSONObject valueJson = new JSONObject(); valueJson.put("type", "long"); - valueJson.put("value", 0x00FFFFFF & color); + valueJson.put("value", keepAlpha ? color : 0x00FFFFFF & color); json.put("Color", valueJson); LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Color", json.toString())); @@ -152,21 +162,18 @@ public class FontController implements AdapterView.OnItemSelectedListener { * 0x00FFFFFF & color operation removes the alpha which is FF, * if we don't remove it, the color value becomes negative which is not recognized by LOK */ - private void sendFontBackColorChange(int color){ + private void sendFontBackColorChange(int color, boolean keepAlpha) { try { JSONObject json = new JSONObject(); JSONObject valueJson = new JSONObject(); valueJson.put("type", "long"); - valueJson.put("value", 0x00FFFFFF & color); - if(mActivity.isSpreadsheet()){ + valueJson.put("value", keepAlpha ? color : 0x00FFFFFF & color); + if(mActivity.getTileProvider().isSpreadsheet()){ json.put("BackgroundColor", valueJson); LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:BackgroundColor", json.toString())); - }else if(mActivity.getTileProvider().isPresentation()){ + }else { json.put("CharBackColor", valueJson); LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:CharBackColor", json.toString())); - }else { - json.put("BackColor", valueJson); - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:BackColor", json.toString())); } changeFontBackColorBoxColor(color); @@ -213,7 +220,7 @@ public class FontController implements AdapterView.OnItemSelectedListener { String key = keys.next(); mFontList.add(key); JSONArray array = jObject2.getJSONArray(key); - fontSizes = new ArrayList<String>(); + fontSizes = new ArrayList<>(); for (int i = 0; i < array.length(); i++) { fontSizes.add(array.getString(i)); } @@ -237,14 +244,14 @@ public class FontController implements AdapterView.OnItemSelectedListener { private void setupFontNameSpinner() { Spinner fontSpinner = mActivity.findViewById(R.id.font_name_spinner); - ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(mActivity, android.R.layout.simple_spinner_item, mFontList); + ArrayAdapter<String> dataAdapter = new ArrayAdapter<>(mActivity, android.R.layout.simple_spinner_item, mFontList); dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); fontSpinner.setAdapter(dataAdapter); } private void setupFontSizeSpinner() { Spinner fontSizeSpinner = mActivity.findViewById(R.id.font_size_spinner); - ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(mActivity, android.R.layout.simple_spinner_item, mFontSizes); + ArrayAdapter<String> dataAdapter = new ArrayAdapter<>(mActivity, android.R.layout.simple_spinner_item, mFontSizes); dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); fontSizeSpinner.setAdapter(dataAdapter); } @@ -306,6 +313,10 @@ public class FontController implements AdapterView.OnItemSelectedListener { fontColorPicker.setOnClickListener(clickListener); fontColorPickerButton.setOnClickListener(clickListener); + final Button autoColorButton = colorPickerLayout.findViewById(R.id.button_auto_color); + autoColorButton.setOnClickListener(view -> { + sendFontColorChange(COLOR_AUTO, true); + }); } private void setupBackColorPicker(){ @@ -365,6 +376,10 @@ public class FontController implements AdapterView.OnItemSelectedListener { fontColorPicker.setOnClickListener(clickListener); fontColorPickerButton.setOnClickListener(clickListener); + final Button autoColorButton = backColorPickerLayout.findViewById(R.id.button_auto_color); + autoColorButton.setOnClickListener(view -> { + sendFontBackColorChange(COLOR_AUTO, true); + }); } public void selectFont(final String fontName) { diff --git a/android/source/src/java/org/libreoffice/FormattingController.java b/android/source/src/java/org/libreoffice/FormattingController.java index a34c4c41ee29..49e81eb69784 100644 --- a/android/source/src/java/org/libreoffice/FormattingController.java +++ b/android/source/src/java/org/libreoffice/FormattingController.java @@ -11,8 +11,8 @@ 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 com.google.android.material.snackbar.Snackbar; +import androidx.core.content.FileProvider; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -40,7 +40,7 @@ class FormattingController implements View.OnClickListener { private static final int SELECT_PHOTO = 2; private static final int IMAGE_BUFFER_SIZE = 4 * 1024; - private LibreOfficeMainActivity mContext; + private final LibreOfficeMainActivity mContext; private String mCurrentPhotoPath; FormattingController(LibreOfficeMainActivity context) { @@ -48,6 +48,8 @@ class FormattingController implements View.OnClickListener { mContext.findViewById(R.id.button_insertFormatListBullets).setOnClickListener(this); mContext.findViewById(R.id.button_insertFormatListNumbering).setOnClickListener(this); + mContext.findViewById(R.id.button_increaseIndent).setOnClickListener(this); + mContext.findViewById(R.id.button_decreaseIndent).setOnClickListener(this); mContext.findViewById(R.id.button_bold).setOnClickListener(this); mContext.findViewById(R.id.button_italic).setOnClickListener(this); @@ -84,70 +86,51 @@ class FormattingController implements View.OnClickListener { button.getBackground().setState(new int[]{android.R.attr.state_selected}); } - switch(button.getId()) { - - case R.id.button_insertFormatListBullets: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DefaultBullet")); - break; - - case R.id.button_insertFormatListNumbering: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DefaultNumbering")); - break; - - case R.id.button_bold: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Bold")); - break; - case R.id.button_italic: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Italic")); - break; - case R.id.button_strikethrough: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Strikeout")); - break; - case R.id.button_clearformatting: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:ResetAttributes")); - break; - case R.id.button_underlined: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:UnderlineDouble")); - break; - case R.id.button_align_left: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:LeftPara")); - break; - case R.id.button_align_center: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:CenterPara")); - break; - case R.id.button_align_right: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:RightPara")); - break; - case R.id.button_align_justify: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:JustifyPara")); - break; - case R.id.button_insert_line: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Line")); - break; - case R.id.button_insert_rect: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Rect")); - break; - case R.id.button_font_shrink: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Shrink")); - break; - case R.id.button_font_grow: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Grow")); - break; - case R.id.button_subscript: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SubScript")); - break; - case R.id.button_superscript: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SuperScript")); - break; - case R.id.button_insert_picture: - insertPicture(); - break; - case R.id.button_insert_table: - insertTable(); - break; - case R.id.button_delete_table: - deleteTable(); - break; + final int buttonId = button.getId(); + if (buttonId == R.id.button_insertFormatListBullets) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DefaultBullet")); + } else if (buttonId == R.id.button_insertFormatListNumbering) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DefaultNumbering")); + } else if (buttonId == R.id.button_increaseIndent) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:IncrementIndent")); + } else if (buttonId == R.id.button_decreaseIndent) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:DecrementIndent")); + } else if (buttonId == R.id.button_bold) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Bold")); + } else if (buttonId == R.id.button_italic) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Italic")); + } else if (buttonId == R.id.button_strikethrough) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Strikeout")); + } else if (buttonId == R.id.button_clearformatting) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:ResetAttributes")); + } else if (buttonId == R.id.button_underlined) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:UnderlineDouble")); + } else if (buttonId == R.id.button_align_left) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:LeftPara")); + } else if (buttonId == R.id.button_align_center) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:CenterPara")); + } else if (buttonId == R.id.button_align_right) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:RightPara")); + } else if (buttonId == R.id.button_align_justify) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:JustifyPara")); + } else if (buttonId == R.id.button_insert_line) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Line")); + } else if (buttonId == R.id.button_insert_rect) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Rect")); + } else if (buttonId == R.id.button_font_shrink) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Shrink")); + } else if (buttonId == R.id.button_font_grow) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Grow")); + } else if (buttonId == R.id.button_subscript) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SubScript")); + }else if (buttonId == R.id.button_superscript) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:SuperScript")); + } else if (buttonId == R.id.button_insert_picture) { + insertPicture(); + } else if (buttonId == R.id.button_insert_table) { + insertTable(); + } else if (buttonId == R.id.button_delete_table) { + deleteTable(); } } @@ -243,15 +226,11 @@ class FormattingController implements View.OnClickListener { public void onClick(View v) { int rowCount = Integer.parseInt(npRowCount.getText().toString()); int colCount = Integer.parseInt(npColCount.getText().toString()); - switch (v.getId()){ - case R.id.number_picker_rows_positive: - if(rowCount < maxValue) - npRowCount.setText(String.valueOf(++rowCount)); - break; - case R.id.number_picker_cols_positive: - if(colCount < maxValue) - npColCount.setText(String.valueOf(++colCount)); - break; + final int id = v.getId(); + if (id == R.id.number_picker_rows_positive && rowCount < maxValue) { + npRowCount.setText(String.valueOf(++rowCount)); + } else if (id == R.id.number_picker_cols_positive && colCount < maxValue) { + npColCount.setText(String.valueOf(++colCount)); } } }; @@ -261,15 +240,11 @@ class FormattingController implements View.OnClickListener { public void onClick(View v) { int rowCount = Integer.parseInt(npRowCount.getText().toString()); int colCount = Integer.parseInt(npColCount.getText().toString()); - switch (v.getId()){ - case R.id.number_picker_rows_negative: - if(rowCount > minValue) - npRowCount.setText(String.valueOf(--rowCount)); - break; - case R.id.number_picker_cols_negative: - if(colCount > minValue) - npColCount.setText(String.valueOf(--colCount)); - break; + final int id = v.getId(); + if (id == R.id.number_picker_rows_negative && rowCount > minValue) { + npRowCount.setText(String.valueOf(--rowCount)); + } else if (id == R.id.number_picker_cols_negative && colCount > minValue) { + npColCount.setText(String.valueOf(--colCount)); } } }; @@ -421,15 +396,14 @@ class FormattingController implements View.OnClickListener { void handleActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == TAKE_PHOTO && resultCode == Activity.RESULT_OK) { - mContext.pendingInsertGraphic = true; + compressAndInsertImage(); } else if (requestCode == SELECT_PHOTO && resultCode == Activity.RESULT_OK) { getFileFromURI(data.getData()); - mContext.pendingInsertGraphic = true; + compressAndInsertImage(); } } - // Called by LOKitTileProvider when activity is resumed from photo/gallery/camera/cloud apps - void popCompressImageGradeSelection() { + void compressAndInsertImage() { 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), @@ -493,7 +467,6 @@ class FormattingController implements View.OnClickListener { 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) { diff --git a/android/source/src/java/org/libreoffice/InvalidationHandler.java b/android/source/src/java/org/libreoffice/InvalidationHandler.java index 32e9b56656dd..c48127cce67f 100644 --- a/android/source/src/java/org/libreoffice/InvalidationHandler.java +++ b/android/source/src/java/org/libreoffice/InvalidationHandler.java @@ -25,12 +25,12 @@ import java.util.List; * Parses (interprets) and handles invalidation messages from LibreOffice. */ public class InvalidationHandler implements Document.MessageCallback, Office.MessageCallback { - private static String LOGTAG = InvalidationHandler.class.getSimpleName(); + private static final String LOGTAG = InvalidationHandler.class.getSimpleName(); private final DocumentOverlay mDocumentOverlay; private final GeckoLayerClient mLayerClient; private OverlayState mState; private boolean mKeyEvent = false; - private LibreOfficeMainActivity mContext; + private final LibreOfficeMainActivity mContext; private int currentTotalPageNumber = 0; // total page number of the current document @@ -55,6 +55,7 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes && messageID != Document.CALLBACK_DOCUMENT_PASSWORD && messageID != Document.CALLBACK_HYPERLINK_CLICKED && messageID != Document.CALLBACK_SEARCH_RESULT_SELECTION + && messageID != Document.CALLBACK_SC_FOLLOW_JUMP && messageID != Document.CALLBACK_TEXT_SELECTION && messageID != Document.CALLBACK_TEXT_SELECTION_START && messageID != Document.CALLBACK_TEXT_SELECTION_END) @@ -114,6 +115,9 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes case Document.CALLBACK_CELL_CURSOR: invalidateCellCursor(payload); break; + case Document.CALLBACK_SC_FOLLOW_JUMP: + jumpToCell(payload); + break; case Document.CALLBACK_INVALIDATE_HEADER: invalidateHeader(); break; @@ -139,7 +143,7 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes JSONObject payloadObject = new JSONObject(payload); if (payloadObject.getString("commandName").equals(".uno:Save")) { if (payloadObject.getString("success").equals("true")) { - mContext.saveFilesToCloud(); + mContext.saveFileToOriginalSource(); } }else if(payloadObject.getString("commandName").equals(".uno:Name") || payloadObject.getString("commandName").equals(".uno:RenamePage")){ @@ -214,6 +218,14 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes } } + private void jumpToCell(String payload) { + RectF cellCursorRect = convertPayloadCellToRectangle(payload); + + if (cellCursorRect != null) { + moveViewportToMakeSelectionVisible(cellCursorRect); + } + } + /** * Handles the search result selection message, which is a JSONObject * @@ -330,7 +342,7 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes mContext.getFormattingController().onToggleStateChanged(Document.NUMBERED_LIST, pressed); } else if (parts[0].equals(".uno:Color")) { mContext.getFontController().colorPaletteListener.updateColorPickerPosition(Integer.parseInt(value)); - } else if (mContext.getTileProvider().isTextDocument() && parts[0].equals(".uno:BackColor")) { + } else if (mContext.getTileProvider().isTextDocument() && (parts[0].equals(".uno:BackColor") || parts[0].equals(".uno:CharBackColor"))) { mContext.getFontController().backColorPaletteListener.updateColorPickerPosition(Integer.parseInt(value)); } else if (mContext.getTileProvider().isPresentation() && parts[0].equals(".uno:CharBackColor")) { mContext.getFontController().backColorPaletteListener.updateColorPickerPosition(Integer.parseInt(value)); @@ -368,6 +380,40 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes if (coordinates.length != 4) { return null; } + return convertPayloadToRectangle(coordinates); + } + + /** + * Parses the payload text with rectangle coordinates and converts to rectangle in pixel coordinates + * + * @param payload - invalidation message payload text + * @return rectangle in pixel coordinates + */ + public RectF convertPayloadCellToRectangle(String payload) { + String payloadWithoutWhitespace = payload.replaceAll("\\s", ""); // remove all whitespace from the string + + if (payloadWithoutWhitespace.isEmpty() || payloadWithoutWhitespace.equals("EMPTY")) { + return null; + } + + String[] coordinates = payloadWithoutWhitespace.split(","); + + if (coordinates.length != 6 ) { + return null; + } + return convertPayloadToRectangle(coordinates); + } + + /** + * Converts rectangle coordinates to rectangle in pixel coordinates + * + * @param coordinates - the first four items defines the rectangle + * @return rectangle in pixel coordinates + */ + public RectF convertPayloadToRectangle(String[] coordinates) { + if (coordinates.length < 4 ) { + return null; + } int x = Integer.decode(coordinates[0]); int y = Integer.decode(coordinates[1]); @@ -377,10 +423,10 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes float dpi = LOKitShell.getDpi(mContext); return new RectF( - LOKitTileProvider.twipToPixel(x, dpi), - LOKitTileProvider.twipToPixel(y, dpi), - LOKitTileProvider.twipToPixel(x + width, dpi), - LOKitTileProvider.twipToPixel(y + height, dpi) + LOKitTileProvider.twipToPixel(x, dpi), + LOKitTileProvider.twipToPixel(y, dpi), + LOKitTileProvider.twipToPixel(x + width, dpi), + LOKitTileProvider.twipToPixel(y + height, dpi) ); } @@ -505,7 +551,7 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes changeStateTo(OverlayState.TRANSITION); } mDocumentOverlay.changeSelections(Collections.<RectF>emptyList()); - if (mContext.isSpreadsheet()) { + if (mContext.getTileProvider().isSpreadsheet()) { mDocumentOverlay.showHeaderSelection(null); } mContext.getToolbarController().showHideClipboardCutAndCopy(false); @@ -516,7 +562,7 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes } changeStateTo(OverlayState.SELECTION); mDocumentOverlay.changeSelections(rectangles); - if (mContext.isSpreadsheet()) { + if (mContext.getTileProvider().isSpreadsheet()) { mDocumentOverlay.showHeaderSelection(rectangles.get(0)); } String selectedText = mContext.getTileProvider().getTextSelection(""); diff --git a/android/source/src/java/org/libreoffice/LOEvent.java b/android/source/src/java/org/libreoffice/LOEvent.java index 4db48a5bbd6d..d1170eee12ad 100644 --- a/android/source/src/java/org/libreoffice/LOEvent.java +++ b/android/source/src/java/org/libreoffice/LOEvent.java @@ -33,7 +33,6 @@ public class LOEvent implements Comparable<LOEvent> { public static final int SWIPE_LEFT = 12; public static final int NAVIGATION_CLICK = 13; public static final int UNO_COMMAND = 14; - public static final int RESUME = 15; public static final int LOAD_NEW = 16; public static final int SAVE_AS = 17; public static final int UPDATE_PART_PAGE_RECT = 18; @@ -42,6 +41,7 @@ public class LOEvent implements Comparable<LOEvent> { public static final int REFRESH = 21; public static final int PAGE_SIZE_CHANGED = 22; public static final int UNO_COMMAND_NOTIFY = 23; + public static final int SAVE_COPY_AS = 24; public final int mType; @@ -104,13 +104,6 @@ public class LOEvent implements Comparable<LOEvent> { mValue = value; } - public LOEvent(int type, String key, int value) { - mType = type; - mTypeString = "Resume partIndex"; - mString = key; - mPartIndex = value; - } - public LOEvent(String filePath, int type) { mType = type; mTypeString = "Load"; diff --git a/android/source/src/java/org/libreoffice/LOKitInputConnectionHandler.java b/android/source/src/java/org/libreoffice/LOKitInputConnectionHandler.java index bbef709af297..7b50ef5ff707 100644 --- a/android/source/src/java/org/libreoffice/LOKitInputConnectionHandler.java +++ b/android/source/src/java/org/libreoffice/LOKitInputConnectionHandler.java @@ -19,7 +19,7 @@ import org.mozilla.gecko.gfx.InputConnectionHandler; * directed to this class which is then directed further to LOKitThread. */ public class LOKitInputConnectionHandler implements InputConnectionHandler { - private static String LOGTAG = LOKitInputConnectionHandler.class.getSimpleName(); + private static final String LOGTAG = LOKitInputConnectionHandler.class.getSimpleName(); @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { diff --git a/android/source/src/java/org/libreoffice/LOKitShell.java b/android/source/src/java/org/libreoffice/LOKitShell.java index c69e02669619..f6a76228e008 100644 --- a/android/source/src/java/org/libreoffice/LOKitShell.java +++ b/android/source/src/java/org/libreoffice/LOKitShell.java @@ -24,10 +24,10 @@ import org.mozilla.gecko.gfx.ComposedTileLayer; * Common static LOKit functions, functions to send events. */ public class LOKitShell { - private static final String LOGTAG = LOKitShell.class.getSimpleName(); - public static float getDpi(Context context) { - if (((LibreOfficeMainActivity)context).isSpreadsheet()) return 96f; + LOKitTileProvider tileProvider = ((LibreOfficeMainActivity)context).getTileProvider(); + if (tileProvider != null && tileProvider.isSpreadsheet()) + return 96f; DisplayMetrics metrics = context.getResources().getDisplayMetrics(); return metrics.density * 160; } @@ -61,7 +61,7 @@ public class LOKitShell { } public static boolean isEditingEnabled() { - return LibreOfficeMainActivity.isExperimentalMode(); + return !LibreOfficeMainActivity.isReadOnlyMode(); } // EVENTS @@ -119,8 +119,8 @@ public class LOKitShell { LOKitShell.sendEvent(new LOEvent(filePath, fileFormat, LOEvent.SAVE_AS)); } - public static void sendResumeEvent(String inputFile, int partIndex) { - LOKitShell.sendEvent(new LOEvent(LOEvent.RESUME, inputFile, partIndex)); + public static void sendSaveCopyAsEvent(String filePath, String fileFormat) { + LOKitShell.sendEvent(new LOEvent(filePath, fileFormat, LOEvent.SAVE_COPY_AS)); } public static void sendCloseEvent() { diff --git a/android/source/src/java/org/libreoffice/LOKitThread.java b/android/source/src/java/org/libreoffice/LOKitThread.java index e554f0800cf0..fd40c3089102 100644 --- a/android/source/src/java/org/libreoffice/LOKitThread.java +++ b/android/source/src/java/org/libreoffice/LOKitThread.java @@ -7,7 +7,6 @@ import android.util.Log; import android.view.KeyEvent; import org.libreoffice.canvas.SelectionHandle; -import org.libreoffice.ui.LibreOfficeUIActivity; import org.mozilla.gecko.ZoomConstraints; import org.mozilla.gecko.gfx.CairoImage; import org.mozilla.gecko.gfx.ComposedTileLayer; @@ -26,13 +25,13 @@ import java.util.concurrent.LinkedBlockingQueue; class LOKitThread extends Thread { private static final String LOGTAG = LOKitThread.class.getSimpleName(); - private LinkedBlockingQueue<LOEvent> mEventQueue = new LinkedBlockingQueue<LOEvent>(); + private final LinkedBlockingQueue<LOEvent> mEventQueue = new LinkedBlockingQueue<LOEvent>(); private TileProvider mTileProvider; private InvalidationHandler mInvalidationHandler; private ImmutableViewportMetrics mViewportMetrics; private GeckoLayerClient mLayerClient; - private LibreOfficeMainActivity mContext; + private final LibreOfficeMainActivity mContext; LOKitThread(LibreOfficeMainActivity context) { mContext = context; @@ -112,7 +111,7 @@ class LOKitThread extends Thread { /** * Handle the geometry change + draw. */ - private void redraw() { + private void redraw(boolean resetZoomAndPosition) { if (mLayerClient == null || mTileProvider == null) { // called too early... return; @@ -122,7 +121,9 @@ class LOKitThread extends Thread { mViewportMetrics = mLayerClient.getViewportMetrics(); mLayerClient.setViewportMetrics(mViewportMetrics); - zoomAndRepositionTheDocument(); + if (resetZoomAndPosition) { + zoomAndRepositionTheDocument(); + } mLayerClient.forceRedraw(); mLayerClient.forceRender(); @@ -152,9 +153,9 @@ class LOKitThread extends Thread { /** * Invalidate everything + handle the geometry change */ - private void refresh() { + private void refresh(boolean resetZoomAndPosition) { mLayerClient.clearAndResetlayers(); - redraw(); + redraw(resetZoomAndPosition); updatePartPageRectangles(); if (mTileProvider != null && mTileProvider.isSpreadsheet()) { updateCalcHeaders(); @@ -177,35 +178,17 @@ class LOKitThread extends Thread { private void updatePageSize(int pageWidth, int pageHeight){ mTileProvider.setDocumentSize(pageWidth, pageHeight); - redraw(); + redraw(true); } private void updateZoomConstraints() { if (mTileProvider == null) return; mLayerClient = mContext.getLayerClient(); - // Set min zoom to the page width so that you cannot zoom below page width - final float minZoom = mLayerClient.getViewportMetrics().getWidth()/mTileProvider.getPageWidth(); - mLayerClient.setZoomConstraints(new ZoomConstraints(true, 1f, minZoom, 0f)); - } - - - /** - * Resume the document with the current part - */ - - private void resumeDocument(String filename, int partIndex){ - - mLayerClient = mContext.getLayerClient(); - - mInvalidationHandler = new InvalidationHandler(mContext); - mTileProvider = TileProviderFactory.create(mContext, mInvalidationHandler, filename); - - if (mTileProvider.isReady()) { - updateZoomConstraints(); - changePart(partIndex); - } else { - closeDocument(); - } + // Set default zoom to the page width and min zoom so that the whole page is visible + final float pageHeightZoom = mLayerClient.getViewportMetrics().getHeight() / mTileProvider.getPageHeight(); + final float pageWidthZoom = mLayerClient.getViewportMetrics().getWidth() / mTileProvider.getPageWidth(); + final float minZoom = Math.min(pageWidthZoom, pageHeightZoom); + mLayerClient.setZoomConstraints(new ZoomConstraints(pageWidthZoom, minZoom, 0f)); } /** @@ -216,15 +199,16 @@ class LOKitThread extends Thread { mTileProvider.changePart(partIndex); mViewportMetrics = mLayerClient.getViewportMetrics(); // mLayerClient.setViewportMetrics(mViewportMetrics.scaleTo(0.9f, new PointF())); - refresh(); + refresh(true); LOKitShell.hideProgressSpinner(mContext); } /** * Handle load document event. * @param filePath - filePath to where the document is located + * @return Whether the document has been loaded successfully. */ - private void loadDocument(String filePath) { + private boolean loadDocument(String filePath) { mLayerClient = mContext.getLayerClient(); mInvalidationHandler = new InvalidationHandler(mContext); @@ -233,18 +217,12 @@ class LOKitThread extends Thread { if (mTileProvider.isReady()) { LOKitShell.showProgressSpinner(mContext); updateZoomConstraints(); - LOKitShell.getMainHandler().post(new Runnable() { - @Override - public void run() { - // synchronize to avoid deletion while loading - synchronized (LOKitThread.this) { - refresh(); - } - } - }); + refresh(true); LOKitShell.hideProgressSpinner(mContext); + return true; } else { closeDocument(); + return false; } } @@ -254,47 +232,27 @@ class LOKitThread extends Thread { * @param fileType - fileType what type of new document is to be loaded */ private void loadNewDocument(String filePath, String fileType) { - mLayerClient = mContext.getLayerClient(); - - mInvalidationHandler = new InvalidationHandler(mContext); - mTileProvider = TileProviderFactory.create(mContext, mInvalidationHandler, fileType); - - if (mTileProvider.isReady()) { - LOKitShell.showProgressSpinner(mContext); - updateZoomConstraints(); - refresh(); - LOKitShell.hideProgressSpinner(mContext); - - if (fileType.matches(LibreOfficeUIActivity.NEW_WRITER_STRING_KEY)) - mTileProvider.saveDocumentAs(filePath, "odt"); - else if (fileType.matches(LibreOfficeUIActivity.NEW_CALC_STRING_KEY)) - mTileProvider.saveDocumentAs(filePath, "ods"); - else if (fileType.matches(LibreOfficeUIActivity.NEW_IMPRESS_STRING_KEY)) - mTileProvider.saveDocumentAs(filePath, "odp"); - else - mTileProvider.saveDocumentAs(filePath, "odg"); - - } else { - closeDocument(); + boolean ok = loadDocument(fileType); + if (ok) { + mTileProvider.saveDocumentAs(filePath, true); } } /** * Save the currently loaded document. */ - private void saveDocumentAs(String filePath, String fileType) { + private void saveDocumentAs(String filePath, String fileType, boolean bTakeOwnership) { if (mTileProvider == null) { Log.e(LOGTAG, "Error in saving, Tile Provider instance is null"); } else { - mTileProvider.saveDocumentAs(filePath, fileType); + mTileProvider.saveDocumentAs(filePath, fileType, bTakeOwnership); } } /** * Close the currently loaded document. */ - // needs to be synchronized to not destroy doc while it's loaded - private synchronized void closeDocument() { + private void closeDocument() { if (mTileProvider != null) { mTileProvider.close(); mTileProvider = null; @@ -313,16 +271,16 @@ class LOKitThread extends Thread { loadNewDocument(event.filePath, event.fileType); break; case LOEvent.SAVE_AS: - saveDocumentAs(event.filePath, event.fileType); + saveDocumentAs(event.filePath, event.fileType, true); break; - case LOEvent.RESUME: - resumeDocument(event.mString, event.mPartIndex); + case LOEvent.SAVE_COPY_AS: + saveDocumentAs(event.filePath, event.fileType, false); break; case LOEvent.CLOSE: closeDocument(); break; case LOEvent.SIZE_CHANGED: - redraw(); + redraw(false); break; case LOEvent.CHANGE_PART: changePart(event.mPartIndex); @@ -376,7 +334,7 @@ class LOKitThread extends Thread { mTileProvider.postUnoCommand(event.mString, event.mValue, event.mNotify); break; case LOEvent.REFRESH: - refresh(); + refresh(false); break; case LOEvent.PAGE_SIZE_CHANGED: updatePageSize(event.mPageWidth, event.mPageHeight); @@ -448,7 +406,7 @@ class LOKitThread extends Thread { boolean editing = LOKitShell.isEditingEnabled(); float zoomFactor = mViewportMetrics.getZoomFactor(); - if (touchType.equals("LongPress") && editing) { + if (touchType.equals("LongPress")) { mInvalidationHandler.changeStateTo(InvalidationHandler.OverlayState.TRANSITION); mTileProvider.mouseButtonDown(documentCoordinate, 1, zoomFactor); mTileProvider.mouseButtonUp(documentCoordinate, 1, zoomFactor); diff --git a/android/source/src/java/org/libreoffice/LOKitTileProvider.java b/android/source/src/java/org/libreoffice/LOKitTileProvider.java index 0e2649337322..5d1cf12209dc 100644 --- a/android/source/src/java/org/libreoffice/LOKitTileProvider.java +++ b/android/source/src/java/org/libreoffice/LOKitTileProvider.java @@ -12,7 +12,6 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.PointF; import android.os.Build; -import android.os.Environment; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintManager; @@ -26,7 +25,6 @@ import org.libreoffice.kit.DirectBufferAllocator; import org.libreoffice.kit.Document; import org.libreoffice.kit.LibreOfficeKit; import org.libreoffice.kit.Office; -import org.libreoffice.ui.FileUtilities; import org.mozilla.gecko.gfx.BufferedCairoImage; import org.mozilla.gecko.gfx.CairoImage; import org.mozilla.gecko.gfx.IntSize; @@ -39,22 +37,22 @@ import java.nio.ByteBuffer; */ class LOKitTileProvider implements TileProvider { private static final String LOGTAG = LOKitTileProvider.class.getSimpleName(); - private static int TILE_SIZE = 256; + private static final int TILE_SIZE = 256; private final float mTileWidth; private final float mTileHeight; - private final String mInputFile; + private String mInputFile; private Office mOffice; private Document mDocument; - private boolean mIsReady = false; - private LibreOfficeMainActivity mContext; + private final boolean mIsReady; + private final LibreOfficeMainActivity mContext; - private float mDPI; + private final float mDPI; private float mWidthTwip; private float mHeightTwip; - private Document.MessageCallback mMessageCallback; + private final Document.MessageCallback mMessageCallback; - private long objectCreationTime = System.currentTimeMillis(); + private final long objectCreationTime = System.currentTimeMillis(); /** * Initialize LOKit and load the document. @@ -73,32 +71,16 @@ class LOKitTileProvider implements TileProvider { mOffice.setOptionalFeatures(Document.LOK_FEATURE_DOCUMENT_PASSWORD); mContext.setTileProvider(this); mInputFile = input; - File f = new File(mInputFile); - final String cacheFile = mContext.getExternalCacheDir().getAbsolutePath() + "/lo_cached_" + f.getName(); - - if(mContext.firstStart){ - File cacheFileObj = new File(cacheFile); - if(cacheFileObj.exists()) { - cacheFileObj.delete(); - } - mContext.firstStart=false; - } Log.i(LOGTAG, "====> Loading file '" + input + "'"); - File fileToBeEncoded; - if(isDocumentCached()){ - fileToBeEncoded = new File(cacheFile); - }else{ - fileToBeEncoded = new File(input); - } + File fileToBeEncoded = new File(input); String encodedFileName = android.net.Uri.encode(fileToBeEncoded.getName()); mDocument = mOffice.documentLoad( (new File(fileToBeEncoded.getParent(),encodedFileName)).getPath() ); - if (mDocument == null && !mContext.isPasswordProtected()) { Log.i(LOGTAG, "====> mOffice.documentLoad() returned null, trying to restart 'Office' and loading again"); mOffice.destroy(); @@ -117,10 +99,6 @@ class LOKitTileProvider implements TileProvider { Log.i(LOGTAG, "====> mDocument = " + mDocument); - if(isSpreadsheet()) { - mContext.setIsSpreadsheet(true); // Calc is treated differently e.g. DPI = 96f - } - mDPI = LOKitShell.getDpi(mContext); mTileWidth = pixelToTwip(TILE_SIZE, mDPI); mTileHeight = pixelToTwip(TILE_SIZE, mDPI); @@ -142,27 +120,9 @@ class LOKitTileProvider implements TileProvider { private void postLoad() { mDocument.setMessageCallback(mMessageCallback); - int parts = mDocument.getParts(); - Log.i(LOGTAG, "Document parts: " + parts); - mContext.getDocumentPartView().clear(); - + resetParts(); // Writer documents always have one part, so hide the navigation drawer. - if (mDocument.getDocumentType() != Document.DOCTYPE_TEXT) { - for (int i = 0; i < parts; i++) { - String partName = mDocument.getPartName(i); - if (partName.isEmpty()) { - partName = getGenericPartName(i); - }else if (partName.startsWith("Slide") || partName.startsWith("Sheet") || partName.startsWith("Part")) { - partName = getGenericPartName(i); - } - Log.i(LOGTAG, "Document part " + i + " name:'" + partName + "'"); - - mDocument.setPart(i); - resetDocumentSize(); - final DocumentPartView partView = new DocumentPartView(i, partName); - mContext.getDocumentPartView().add(partView); - } - } else { + if (mDocument.getDocumentType() == Document.DOCTYPE_TEXT) { mContext.disableNavigationDrawer(); mContext.getToolbarController().hideItem(R.id.action_parts); } @@ -182,14 +142,6 @@ class LOKitTileProvider implements TileProvider { mContext.getDocumentPartViewListAdapter().notifyDataSetChanged(); } }); - mContext.runOnUiThread(new Runnable() { - @Override - public void run() { - if (mContext.pendingInsertGraphic) { - mContext.getFormattingController().popCompressImageGradeSelection(); - } - } - }); } public void addPart(){ @@ -224,9 +176,9 @@ class LOKitTileProvider implements TileProvider { } public void resetParts(){ - int parts = mDocument.getParts(); mContext.getDocumentPartView().clear(); if (mDocument.getDocumentType() != Document.DOCTYPE_TEXT) { + int parts = mDocument.getParts(); for (int i = 0; i < parts; i++) { String partName = mDocument.getPartName(i); @@ -240,7 +192,9 @@ class LOKitTileProvider implements TileProvider { mContext.getDocumentPartView().add(partView); } } - } public void renamePart(String partName) { + } + + public void renamePart(String partName) { try{ for(int i=0; i<mDocument.getParts(); i++){ if(mContext.getDocumentPartView().get(i).partName.equals(partName)){ @@ -270,7 +224,7 @@ class LOKitTileProvider implements TileProvider { public void removePart() { try{ - if(isSpreadsheet() == false && isPresentation() == false) { + if (!isSpreadsheet() && !isPresentation()) { //document must be spreadsheet or presentation return; } @@ -295,38 +249,37 @@ class LOKitTileProvider implements TileProvider { } } - - @Override - public void saveDocumentAs(final String filePath, String format) { + public boolean saveDocumentAs(final String filePath, String format, boolean takeOwnership) { + String options = ""; + if (takeOwnership) { + options = "TakeOwnership"; + } + final String newFilePath = "file://" + filePath; Log.d("saveFilePathURL", newFilePath); LOKitShell.showProgressSpinner(mContext); - mDocument.saveAs(newFilePath, format, ""); + mDocument.saveAs(newFilePath, format, options); + final boolean ok; if (!mOffice.getError().isEmpty()){ + ok = true; Log.e("Save Error", mOffice.getError()); if (format.equals("svg")) { // error in creating temp slideshow svg file Log.d(LOGTAG, "Error in creating temp slideshow svg file"); } else if(format.equals("pdf")){ Log.d(LOGTAG, "Error in creating pdf file"); + } else { LOKitShell.getMainHandler().post(new Runnable() { @Override public void run() { // There was some error - mContext.showCustomStatusMessage(mContext.getString(R.string.unable_to_export_pdf)); - } - }); - }else { - LOKitShell.getMainHandler().post(new Runnable() { - @Override - public void run() { - // There was some error - mContext.showSaveStatusMessage(true); + mContext.showCustomStatusMessage(mContext.getString(R.string.unable_to_save)); } }); } } else { + ok = false; if (format.equals("svg")) { // successfully created temp slideshow svg file LOKitShell.getMainHandler().post(new Runnable() { @@ -335,99 +288,49 @@ class LOKitTileProvider implements TileProvider { mContext.startPresentation(newFilePath); } }); - }else if(format.equals("pdf")){ - LOKitShell.getMainHandler().post(new Runnable() { - @Override - public void run() { - // There was no error - mContext.showCustomStatusMessage(mContext.getString(R.string.pdf_exported_at)+filePath); - } - }); - } else { - LOKitShell.getMainHandler().post(new Runnable() { - @Override - public void run() { - // There was no error - mContext.showSaveStatusMessage(false); - } - }); + } else if (takeOwnership) { + mInputFile = filePath; } } LOKitShell.hideProgressSpinner(mContext); + return ok; } - public void exportToPDF(boolean print){ - String dir = Environment.getExternalStorageDirectory().getAbsolutePath()+"/Documents"; - File docDir = new File(dir); - if(!docDir.exists()){ - docDir.mkdir(); - } - String mInputFileName = (new File(mInputFile)).getName(); - String file = mInputFileName.substring(0,(mInputFileName.length()-3))+"pdf"; - if(print){ - String cacheFile = mContext.getExternalCacheDir().getAbsolutePath() - + "/" + file; - mDocument.saveAs("file://"+cacheFile,"pdf",""); - printDocument(cacheFile); - }else{ - saveDocumentAs(dir+"/"+file,"pdf"); - } - } - - private void printDocument(String cacheFile) { - if (Build.VERSION.SDK_INT >= 19) { - try { - PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE); - PrintDocumentAdapter printAdapter = new PDFDocumentAdapter(mContext, cacheFile); - printManager.print("Document", printAdapter, new PrintAttributes.Builder().build()); - - } catch (Exception e) { - e.printStackTrace(); - } - } else { - mContext.showCustomStatusMessage(mContext.getString(R.string.printing_not_supported)); - } - } - - public boolean isDocumentCached(){ - File input = new File(mInputFile); - final String cacheFile = mContext.getExternalCacheDir().getAbsolutePath() + "/lo_cached_" + input.getName(); - File cacheFileObj = new File(cacheFile); - if(cacheFileObj.exists()) - return true; - + @Override + public boolean saveDocumentAs(final String filePath, boolean takeOwnership) { + final int docType = mDocument.getDocumentType(); + if (docType == Document.DOCTYPE_TEXT) + return saveDocumentAs(filePath, "odt", takeOwnership); + else if (docType == Document.DOCTYPE_SPREADSHEET) + return saveDocumentAs(filePath, "ods", takeOwnership); + else if (docType == Document.DOCTYPE_PRESENTATION) + return saveDocumentAs(filePath, "odp", takeOwnership); + else if (docType == Document.DOCTYPE_DRAWING) + return saveDocumentAs(filePath, "odg", takeOwnership); + + Log.w(LOGTAG, "Cannot determine file format from document. Not saving."); return false; } - public void cacheDocument() { - String cacheDir = mContext.getExternalCacheDir().getAbsolutePath(); - File input = new File(mInputFile); - final String cacheFile = cacheDir + "/lo_cached_" + input.getName(); - Log.i(LOGTAG, "cacheDocument: " + cacheFile); - if(isDocumentCached()){ - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Save")); - }else if(mDocument != null){ - mDocument.saveAs("file://"+cacheFile, FileUtilities.getExtension(input.getPath()).substring(1),""); - }else{ - Log.w(LOGTAG, "mDocument was null when trying to save cacheDocument: " + cacheFile); + public void printDocument() { + String mInputFileName = (new File(mInputFile)).getName(); + String file = mInputFileName.substring(0,(mInputFileName.length()-3))+"pdf"; + String cacheFile = mContext.getExternalCacheDir().getAbsolutePath() + "/" + file; + mDocument.saveAs("file://"+cacheFile,"pdf",""); + try { + PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE); + PrintDocumentAdapter printAdapter = new PDFDocumentAdapter(mContext, cacheFile); + printManager.print("Document", printAdapter, new PrintAttributes.Builder().build()); + + } catch (Exception e) { + e.printStackTrace(); } } public void saveDocument(){ - if(isDocumentCached()){ - String format = FileUtilities.getExtension(mInputFile).substring(1); - String cacheDir = mContext.getExternalCacheDir().getAbsolutePath(); - File input = new File(mInputFile); - final String cacheFile = cacheDir + "/lo_cached_" + input.getName(); - String path = input.getAbsolutePath(); - saveDocumentAs(path, format); - (new File(cacheFile)).delete(); - }else{ - mContext.saveDocument(); - } + mContext.saveDocument(); } - private void setupDocumentFonts() { String values = mDocument.getCommandValues(".uno:CharFontName"); if (values == null || values.isEmpty()) @@ -674,6 +577,14 @@ class LOKitTileProvider implements TileProvider { } /** + * @see TileProvider#isDrawing() + */ + @Override + public boolean isDrawing() { + return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_DRAWING; + } + + /** * @see TileProvider#isTextDocument() */ @Override diff --git a/android/source/src/java/org/libreoffice/LibreOfficeApplication.java b/android/source/src/java/org/libreoffice/LibreOfficeApplication.java index cb79219fc999..ebe54cf27c64 100644 --- a/android/source/src/java/org/libreoffice/LibreOfficeApplication.java +++ b/android/source/src/java/org/libreoffice/LibreOfficeApplication.java @@ -10,11 +10,11 @@ package org.libreoffice; -import android.app.Application; import android.content.Context; import android.os.Handler; +import androidx.multidex.MultiDexApplication; -public class LibreOfficeApplication extends Application { +public class LibreOfficeApplication extends MultiDexApplication { private static Handler mainHandler; diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java index cbc628e94e48..cf60ff37c5da 100644 --- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java +++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java @@ -1,6 +1,5 @@ package org.libreoffice; -import android.app.Activity; import android.app.AlertDialog; import android.content.ClipData; import android.content.ClipboardManager; @@ -9,19 +8,18 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.graphics.RectF; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.design.widget.BottomSheetBehavior; -import android.support.design.widget.Snackbar; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import android.provider.DocumentsContract; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.snackbar.Snackbar; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import android.text.InputType; import android.util.Log; import android.view.KeyEvent; @@ -36,24 +34,25 @@ import android.widget.Toast; import org.libreoffice.overlay.CalcHeadersController; import org.libreoffice.overlay.DocumentOverlay; -import org.libreoffice.storage.DocumentProviderFactory; -import org.libreoffice.storage.IFile; import org.libreoffice.ui.FileUtilities; import org.libreoffice.ui.LibreOfficeUIActivity; import org.mozilla.gecko.gfx.GeckoLayerClient; import org.mozilla.gecko.gfx.LayerView; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.net.URI; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.List; +import java.util.UUID; /** * Main activity of the LibreOffice App. It is started in the UI thread. @@ -61,10 +60,11 @@ import java.util.List; public class LibreOfficeMainActivity extends AppCompatActivity implements SettingsListenerModel.OnSettingsPreferenceChangedListener { private static final String LOGTAG = "LibreOfficeMainActivity"; - private static final String DEFAULT_DOC_PATH = "/assets/example.odt"; - private static final String ENABLE_EXPERIMENTAL_PREFS_KEY = "ENABLE_EXPERIMENTAL"; + public static final String ENABLE_EXPERIMENTAL_PREFS_KEY = "ENABLE_EXPERIMENTAL"; private static final String ASSETS_EXTRACTED_PREFS_KEY = "ASSETS_EXTRACTED"; private static final String ENABLE_DEVELOPER_PREFS_KEY = "ENABLE_DEVELOPER"; + private static final int REQUEST_CODE_SAVEAS = 12345; + private static final int REQUEST_CODE_EXPORT_TO_PDF = 12346; //TODO "public static" is a temporary workaround public static LOKitThread loKitThread; @@ -73,23 +73,20 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin private static boolean mIsExperimentalMode; private static boolean mIsDeveloperMode; - - private int providerId; - private URI documentUri; + private static boolean mbISReadOnlyMode; private DrawerLayout mDrawerLayout; Toolbar toolbarTop; private ListView mDrawerList; - private List<DocumentPartView> mDocumentPartView = new ArrayList<DocumentPartView>(); + private final List<DocumentPartView> mDocumentPartView = new ArrayList<DocumentPartView>(); private DocumentPartViewListAdapter mDocumentPartViewListAdapter; - private int partIndex=-1; - private File mInputFile; private DocumentOverlay mDocumentOverlay; + /** URI to save the document to. */ + private Uri mDocumentUri; + /** Temporary local copy of the document. */ private File mTempFile = null; private File mTempSlideShowFile = null; - private String newDocumentType = null; - public boolean firstStart = true; BottomSheetBehavior bottomToolbarSheetBehavior; BottomSheetBehavior toolbarColorPickerBottomSheetBehavior; @@ -100,11 +97,10 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin private SearchController mSearchController; private UNOCommandsController mUNOCommandsController; private CalcHeadersController mCalcHeadersController; - private boolean mIsSpreadsheet; private LOKitTileProvider mTileProvider; private String mPassword; private boolean mPasswordProtected; - public boolean pendingInsertGraphic; // boolean indicating a pending insert graphic action, used in LOKitTileProvider.postLoad() + private boolean mbSkipNextRefresh; public GeckoLayerClient getLayerClient() { return mLayerClient; @@ -118,17 +114,11 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin return mIsDeveloperMode; } - public boolean usesTemporaryFile() { - return mTempFile != null; - } - private boolean isKeyboardOpen = false; private boolean isFormattingToolbarOpen = false; private boolean isSearchToolbarOpen = false; private static boolean isDocumentChanged = false; private boolean isUNOCommandsToolbarOpen = false; - public boolean isNewDocument = false; - private long lastModified = 0; @Override public void onCreate(Bundle savedInstanceState) { @@ -168,8 +158,8 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin layerView.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View view, int i, KeyEvent keyEvent) { - if(keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK){ - isDocumentChanged=true; + if(!isReadOnlyMode() && keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK){ + setDocumentChanged(true); } return false; } @@ -178,40 +168,54 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin // create TextCursorLayer mDocumentOverlay = new DocumentOverlay(this, layerView); - // New document type string is not null, meaning we want to open a new document - if (getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY) != null) { - String newDocumentType = getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY); - String newFilePath = getIntent().getStringExtra(LibreOfficeUIActivity.NEW_FILE_PATH_KEY); + mbISReadOnlyMode = !isExperimentalMode(); - // Load the new document - loadNewDocument(newFilePath, newDocumentType); - } + final Uri docUri = getIntent().getData(); + if (docUri != null) { + if (docUri.getScheme().equals(ContentResolver.SCHEME_CONTENT) + || docUri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) { + final boolean isReadOnlyDoc = (getIntent().getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0; + mbISReadOnlyMode = !isExperimentalMode() || isReadOnlyDoc; + Log.d(LOGTAG, "SCHEME_CONTENT: getPath(): " + docUri.getPath()); - if (getIntent().getData() != null) { - if (getIntent().getData().getScheme().equals(ContentResolver.SCHEME_CONTENT)) { - if (copyFileToTemp() && mTempFile != null) { - mInputFile = mTempFile; - Log.d(LOGTAG, "SCHEME_CONTENT: getPath(): " + getIntent().getData().getPath()); - toolbarTop.setTitle(mInputFile.getName()); - } else { - // TODO: can't open the file - Log.e(LOGTAG, "couldn't create temporary file from " + getIntent().getData()); - } - } else if (getIntent().getData().getScheme().equals(ContentResolver.SCHEME_FILE)) { - mInputFile = new File(getIntent().getData().getPath()); - Log.d(LOGTAG, "SCHEME_FILE: getPath(): " + getIntent().getData().getPath()); - toolbarTop.setTitle(mInputFile.getName()); - // Gather data to rebuild IFile object later - providerId = getIntent().getIntExtra( - "org.libreoffice.document_provider_id", 0); - documentUri = (URI) getIntent().getSerializableExtra( - "org.libreoffice.document_uri"); + String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri); + toolbarTop.setTitle(displayName); + + } else if (docUri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + mbISReadOnlyMode = true; + Log.d(LOGTAG, "SCHEME_FILE: getPath(): " + docUri.getPath()); + toolbarTop.setTitle(docUri.getLastPathSegment()); } - } else { - if (!isNewDocument) { - mInputFile = new File(DEFAULT_DOC_PATH); + // create a temporary local copy to work with + boolean copyOK = copyFileToTemp(docUri) && mTempFile != null; + if (!copyOK) { + // TODO: can't open the file + Log.e(LOGTAG, "couldn't create temporary file from " + docUri); + return; + } + + // if input doc is a template, a new doc is created and a proper URI to save to + // will only be available after a "Save As" + if (isTemplate(docUri)) { + toolbarTop.setTitle(R.string.default_document_name); + } else { + mDocumentUri = docUri; } + + LOKitShell.sendLoadEvent(mTempFile.getPath()); + } else if (getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY) != null) { + // New document type string is not null, meaning we want to open a new document + String newDocumentType = getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY); + // create a temporary local file, will be copied to the actual URI when saving + loadNewDocument(newDocumentType); + toolbarTop.setTitle(getString(R.string.default_document_name)); + } else { + Log.e(LOGTAG, "No document specified. This should never happen."); + return; } + // the loadDocument/loadNewDocument event already triggers a refresh as well, + // so there's no need to do another refresh in 'onStart' + mbSkipNextRefresh = true; mDrawerLayout = findViewById(R.id.drawer_layout); @@ -223,8 +227,6 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin mDrawerList.setOnItemClickListener(new DocumentPartClickListener()); } - lastModified = mInputFile.lastModified(); - mToolbarController.setupToolbars(); TabHost host = findViewById(R.id.toolbarTabHost); @@ -274,22 +276,18 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin } } - // Loads a new Document - private void loadNewDocument(String newFilePath, String newDocumentType) { - mInputFile = new File(newFilePath); - LOKitShell.sendNewDocumentLoadEvent(newFilePath, newDocumentType); - isNewDocument = true; - toolbarTop.setTitle(mInputFile.getName()); + // Loads a new Document and saves it to a temporary file + private void loadNewDocument(String newDocumentType) { + String tempFileName = "LibreOffice_" + UUID.randomUUID().toString(); + mTempFile = new File(this.getCacheDir(), tempFileName); + LOKitShell.sendNewDocumentLoadEvent(mTempFile.getPath(), newDocumentType); } public RectF getCurrentCursorPosition() { return mDocumentOverlay.getCurrentCursorPosition(); } - private boolean copyFileToTemp() { - ContentResolver contentResolver = getContentResolver(); - FileChannel inputChannel = null; - FileChannel outputChannel = null; + private boolean copyFileToTemp(Uri documentUri) { // CSV files need a .csv suffix to be opened in Calc. String suffix = null; String intentType = getIntent().getType(); @@ -298,27 +296,9 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin suffix = ".csv"; try { - try { - AssetFileDescriptor assetFD = contentResolver.openAssetFileDescriptor(getIntent().getData(), "r"); - if (assetFD == null) { - Log.e(LOGTAG, "couldn't create assetfiledescriptor from " + getIntent().getDataString()); - return false; - } - inputChannel = assetFD.createInputStream().getChannel(); - mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir()); - - outputChannel = new FileOutputStream(mTempFile).getChannel(); - long bytesTransferred = 0; - // might not copy all at once, so make sure everything gets copied... - while (bytesTransferred < inputChannel.size()) { - bytesTransferred += outputChannel.transferFrom(inputChannel, bytesTransferred, inputChannel.size()); - } - Log.e(LOGTAG, "Success copying " + bytesTransferred + " bytes"); - return true; - } finally { - if (inputChannel != null) inputChannel.close(); - if (outputChannel != null) outputChannel.close(); - } + mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir()); + final FileOutputStream outputStream = new FileOutputStream(mTempFile); + return copyUriToStream(documentUri, outputStream); } catch (FileNotFoundException e) { return false; } catch (IOException e) { @@ -327,65 +307,150 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin } /** - * Save a new document - * */ - public void saveAs(){ - LOKitShell.sendSaveAsEvent(mInputFile.getPath(), FileUtilities.getExtension(mInputFile.getPath()).substring(1)); - } - - /** - * Save the document and invoke save on document provider to upload the file - * to the cloud if necessary. + * Save the document. */ public void saveDocument() { - if (!mInputFile.exists()) { - // Needed for handling null in case new document is not created. - mInputFile = new File(DEFAULT_DOC_PATH); - lastModified = mInputFile.lastModified(); - } Toast.makeText(this, R.string.message_saving, Toast.LENGTH_SHORT).show(); // local save LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:Save", true)); } - public void saveFilesToCloud(){ - final Activity activity = LibreOfficeMainActivity.this; - final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - try { - // rebuild the IFile object from the data passed in the Intent - IFile mStorageFile = DocumentProviderFactory.getInstance() - .getProvider(providerId).createFromUri(LibreOfficeMainActivity.this, documentUri); - // call document provider save operation - mStorageFile.saveDocument(mInputFile); - } - catch (final RuntimeException e) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, e.getMessage(), - Toast.LENGTH_SHORT).show(); - } - }); - Log.e(LOGTAG, e.getMessage(), e.getCause()); - } - return null; + /** + * Open file chooser and save the document to the URI + * selected there. + */ + public void saveDocumentAs() { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + String mimeType = getODFMimeTypeForDocument(); + intent.setType(mimeType); + if (Build.VERSION.SDK_INT >= 26) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mDocumentUri); + } + + startActivityForResult(intent, REQUEST_CODE_SAVEAS); + } + + /** + * Saves the document under the given URI using ODF format + * and uses that URI from now on for all operations. + * @param newUri URI to save the document and use from now on. + */ + private void saveDocumentAs(Uri newUri) { + mDocumentUri = newUri; + // save in ODF format + mTileProvider.saveDocumentAs(mTempFile.getPath(), true); + saveFileToOriginalSource(); + + String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), mDocumentUri); + toolbarTop.setTitle(displayName); + mbISReadOnlyMode = !isExperimentalMode(); + getToolbarController().setupToolbars(); + } + + public void exportToPDF() { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(FileUtilities.MIMETYPE_PDF); + // suggest directory and file name based on the doc + if (Build.VERSION.SDK_INT >= 26) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mDocumentUri); + } + final String displayName = toolbarTop.getTitle().toString(); + final String suggestedFileName = FileUtilities.stripExtensionFromFileName(displayName) + ".pdf"; + intent.putExtra(Intent.EXTRA_TITLE, suggestedFileName); + + startActivityForResult(intent, REQUEST_CODE_EXPORT_TO_PDF); + } + + private void exportToPDF(final Uri uri) { + boolean exportOK = false; + File tempFile = null; + try { + tempFile = File.createTempFile("LibreOffice_", ".pdf"); + mTileProvider.saveDocumentAs(tempFile.getAbsolutePath(),"pdf", false); + + try { + FileInputStream inputStream = new FileInputStream(tempFile); + exportOK = copyStreamToUri(inputStream, uri); + } catch (FileNotFoundException e) { + e.printStackTrace(); } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (tempFile != null && tempFile.exists()) { + tempFile.delete(); + } + } + + final int msgId = exportOK ? R.string.pdf_export_finished : R.string.unable_to_export_pdf; + LOKitShell.getMainHandler().post(new Runnable() { @Override - protected void onPostExecute(Void param) { - Toast.makeText(activity, R.string.message_saved, - Toast.LENGTH_SHORT).show(); - setDocumentChanged(false); + public void run() { + showCustomStatusMessage(getString(msgId)); } - }; + }); + } + + /** + * Returns the ODF MIME type that can be used for the current document, + * regardless of whether the document is an ODF Document or not + * (e.g. returns FileUtilities.MIMETYPE_OPENDOCUMENT_TEXT for a DOCX file). + * @return MIME type, or empty string, if no appropriate MIME type could be found. + */ + private String getODFMimeTypeForDocument() { + if (mTileProvider.isTextDocument()) + return FileUtilities.MIMETYPE_OPENDOCUMENT_TEXT; + else if (mTileProvider.isSpreadsheet()) + return FileUtilities.MIMETYPE_OPENDOCUMENT_SPREADSHEET; + else if (mTileProvider.isPresentation()) + return FileUtilities.MIMETYPE_OPENDOCUMENT_PRESENTATION; + else if (mTileProvider.isDrawing()) + return FileUtilities.MIMETYPE_OPENDOCUMENT_GRAPHICS; + else { + Log.w(LOGTAG, "Cannot determine MIME type to use."); + return ""; + } + } - if (lastModified < mInputFile.lastModified()) { - task.execute(); - lastModified = mInputFile.lastModified(); + /** + * Returns whether the MIME type for the URI is considered one for a document template. + */ + private boolean isTemplate(final Uri documentUri) { + final String mimeType = getContentResolver().getType(documentUri); + return FileUtilities.isTemplateMimeType(mimeType); + } + + public void saveFileToOriginalSource() { + if (mTempFile == null || mDocumentUri == null || !mDocumentUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) + return; + + boolean copyOK = false; + try { + final FileInputStream inputStream = new FileInputStream(mTempFile); + copyOK = copyStreamToUri(inputStream, mDocumentUri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + if (copyOK) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(LibreOfficeMainActivity.this, R.string.message_saved, + Toast.LENGTH_SHORT).show(); + } + }); + setDocumentChanged(false); } else { - Toast.makeText(activity, R.string.message_save_incomplete, Toast.LENGTH_LONG).show(); + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(LibreOfficeMainActivity.this, R.string.message_saving_failed, + Toast.LENGTH_SHORT).show(); + } + }); } } @@ -412,28 +477,23 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin protected void onStart() { Log.i(LOGTAG, "onStart.."); super.onStart(); - if (!isNewDocument){ - if (partIndex == -1) - LOKitShell.sendLoadEvent(mInputFile.getPath()); - else - LOKitShell.sendResumeEvent(mInputFile.getPath(), partIndex); + if (!mbSkipNextRefresh) { + LOKitShell.sendEvent(new LOEvent(LOEvent.REFRESH)); } + mbSkipNextRefresh = false; } @Override protected void onStop() { Log.i(LOGTAG, "onStop.."); - //save document to cache - if (mTileProvider != null) - mTileProvider.cacheDocument(); hideSoftKeyboardDirect(); - LOKitShell.sendCloseEvent(); super.onStop(); } @Override protected void onDestroy() { Log.i(LOGTAG, "onDestroy.."); + LOKitShell.sendCloseEvent(); mLayerClient.destroy(); super.onDestroy(); @@ -461,11 +521,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin public void onClick(DialogInterface dialog, int which) { switch (which){ case DialogInterface.BUTTON_POSITIVE: - if (isNewDocument) { - saveAs(); - } else { - mTileProvider.saveDocument(); - } + mTileProvider.saveDocument(); isDocumentChanged=false; onBackPressed(); break; @@ -802,7 +858,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin // this function can only be called in InvalidationHandler.java public void setPassword() { - mTileProvider.setDocumentPassword("file://"+mInputFile.getPath(), mPassword); + mTileProvider.setDocumentPassword("file://" + mTempFile.getPath(), mPassword); } // setTileProvider is meant to let main activity have a handle of LOKit when dealing with password @@ -844,12 +900,12 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin }); } - public void setIsSpreadsheet(boolean b) { - mIsSpreadsheet = b; + public static boolean isReadOnlyMode() { + return mbISReadOnlyMode; } - public boolean isSpreadsheet() { - return mIsSpreadsheet; + public boolean hasLocationForSave() { + return mDocumentUri != null; } public static void setDocumentChanged (boolean changed) { @@ -860,7 +916,6 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { DocumentPartView partView = mDocumentPartViewListAdapter.getItem(position); - partIndex = partView.partIndex; LOKitShell.sendChangePartEvent(partView.partIndex); mDrawerLayout.closeDrawer(mDrawerList); } @@ -923,12 +978,103 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin } } - // This method is used in LOKitTileProvider.java to show status of new file creation. - public void showSaveStatusMessage(boolean error) { - if (!error) - Snackbar.make(mDrawerLayout, getString(R.string.create_new_file_success) + mInputFile.getName(), Snackbar.LENGTH_LONG).show(); - else - Snackbar.make(mDrawerLayout, getString(R.string.create_new_file_error) + mInputFile.getName(), Snackbar.LENGTH_LONG).show(); } + /** + * Copies everything from the given input stream to the given output stream + * and closes both streams in the end. + * @return Whether copy operation was successful. + */ + private boolean copyStream(InputStream inputStream, OutputStream outputStream) { + try { + byte[] buffer = new byte[4096]; + int readBytes = inputStream.read(buffer); + while (readBytes != -1) { + outputStream.write(buffer, 0, readBytes); + readBytes = inputStream.read(buffer); + } + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + inputStream.close(); + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Copies everything from the given Uri to the given OutputStream + * and closes the OutputStream in the end. + * The copy operation runs in a separate thread, but the method only returns + * after the thread has finished its execution. + * This can be used to copy in a blocking way when network access is involved, + * which is not allowed from the main thread, but that may happen when an underlying + * DocumentsProvider (like the NextCloud one) does network access. + */ + private boolean copyUriToStream(final Uri inputUri, final OutputStream outputStream) { + class CopyThread extends Thread { + /** Whether copy operation was successful. */ + private boolean result = false; + + @Override + public void run() { + final ContentResolver contentResolver = getContentResolver(); + try { + InputStream inputStream = contentResolver.openInputStream(inputUri); + result = copyStream(inputStream, outputStream); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + CopyThread copyThread = new CopyThread(); + copyThread.start(); + try { + // wait for copy operation to finish + // NOTE: might be useful to add some indicator in UI for long copy operations involving network... + copyThread.join(); + } catch(InterruptedException e) { + e.printStackTrace(); + } + return copyThread.result; + } + + /** + * Copies everything from the given InputStream to the given URI and closes the + * InputStream in the end. + * @see LibreOfficeMainActivity#copyUriToStream(Uri, OutputStream) + * which does the same thing the other way around. + */ + private boolean copyStreamToUri(final InputStream inputStream, final Uri outputUri) { + class CopyThread extends Thread { + /** Whether copy operation was successful. */ + private boolean result = false; + + @Override + public void run() { + final ContentResolver contentResolver = getContentResolver(); + try { + OutputStream outputStream = contentResolver.openOutputStream(outputUri); + result = copyStream(inputStream, outputStream); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + CopyThread copyThread = new CopyThread(); + copyThread.start(); + try { + // wait for copy operation to finish + // NOTE: might be useful to add some indicator in UI for long copy operations involving network... + copyThread.join(); + } catch(InterruptedException e) { + e.printStackTrace(); + } + return copyThread.result; + } public void showCustomStatusMessage(String message){ Snackbar.make(mDrawerLayout, message, Snackbar.LENGTH_LONG).show(); @@ -936,45 +1082,36 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin public void preparePresentation() { if (getExternalCacheDir() != null) { - String tempPath = getExternalCacheDir().getPath() + "/" + mInputFile.getName() + ".svg"; + String tempPath = getExternalCacheDir().getPath() + "/" + mTempFile.getName() + ".svg"; mTempSlideShowFile = new File(tempPath); if (mTempSlideShowFile.exists() && !isDocumentChanged) { startPresentation("file://" + tempPath); } else { - LOKitShell.sendSaveAsEvent(tempPath, "svg"); + LOKitShell.sendSaveCopyAsEvent(tempPath, "svg"); } } } public void startPresentation(String tempPath) { - // pre-KitKat android doesn't have chrome-based WebView, which is needed to show svg slideshow - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - Intent intent = new Intent(this, PresentationActivity.class); - intent.setData(Uri.parse(tempPath)); - startActivity(intent); - } else { - // copy the svg file path to clipboard for the user to paste in a browser - ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("temp svg file path", tempPath); - clipboard.setPrimaryClip(clip); - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(R.string.alert_copy_svg_slide_show_to_clipboard) - .setPositiveButton(R.string.alert_copy_svg_slide_show_to_clipboard_dismiss, null).show(); - } + Intent intent = new Intent(this, PresentationActivity.class); + intent.setData(Uri.parse(tempPath)); + startActivity(intent); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - mFormattingController.handleActivityResult(requestCode, resultCode, data); - hideBottomToolbar(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_SAVEAS && resultCode == RESULT_OK) { + final Uri fileUri = data.getData(); + saveDocumentAs(fileUri); + } else if (requestCode == REQUEST_CODE_EXPORT_TO_PDF && resultCode == RESULT_OK) { + final Uri fileUri = data.getData(); + exportToPDF(fileUri); + } else { + mFormattingController.handleActivityResult(requestCode, resultCode, data); + hideBottomToolbar(); + } } - } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/android/source/src/java/org/libreoffice/LocaleHelper.java b/android/source/src/java/org/libreoffice/LocaleHelper.java index 8c0e9b3fbbed..a87c63f09990 100644 --- a/android/source/src/java/org/libreoffice/LocaleHelper.java +++ b/android/source/src/java/org/libreoffice/LocaleHelper.java @@ -38,8 +38,7 @@ public class LocaleHelper { Resources res = context.getResources(); Configuration cfg = res.getConfiguration(); cfg.locale = locale; - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) - cfg.setLayoutDirection(locale); + cfg.setLayoutDirection(locale); res.updateConfiguration(cfg, res.getDisplayMetrics()); return context; diff --git a/android/source/src/java/org/libreoffice/PasswordDialogFragment.java b/android/source/src/java/org/libreoffice/PasswordDialogFragment.java index 112e35c4b7ed..08bc7f596894 100644 --- a/android/source/src/java/org/libreoffice/PasswordDialogFragment.java +++ b/android/source/src/java/org/libreoffice/PasswordDialogFragment.java @@ -4,9 +4,9 @@ import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/android/source/src/java/org/libreoffice/PresentationActivity.java b/android/source/src/java/org/libreoffice/PresentationActivity.java index 3308e6884fe9..ede7c0c40101 100644 --- a/android/source/src/java/org/libreoffice/PresentationActivity.java +++ b/android/source/src/java/org/libreoffice/PresentationActivity.java @@ -1,16 +1,14 @@ package org.libreoffice; import android.content.Intent; -import android.os.Build; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.view.GestureDetectorCompat; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.Nullable; +import androidx.core.view.GestureDetectorCompat; +import androidx.appcompat.app.AppCompatActivity; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.WindowManager; import android.webkit.WebView; import android.widget.Button; import android.widget.ImageButton; @@ -25,19 +23,10 @@ public class PresentationActivity extends AppCompatActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // First we hide the status bar - if (Build.VERSION.SDK_INT < 16) { - // If the Android version is lower than Jellybean, use this call to hide - // the status bar. - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - } else { - // If higher than Jellybean - View decorView = getWindow().getDecorView(); - // Hide the status bar. - int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; - decorView.setSystemUiVisibility(uiOptions); - } + View decorView = getWindow().getDecorView(); + // Hide the status bar. + int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; + decorView.setSystemUiVisibility(uiOptions); setContentView(R.layout.presentation_mode); @@ -185,4 +174,4 @@ public class PresentationActivity extends AppCompatActivity { private void pageRight() { mWebView.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT)); } -}
\ No newline at end of file +} diff --git a/android/source/src/java/org/libreoffice/SearchController.java b/android/source/src/java/org/libreoffice/SearchController.java index a9414f7f7a71..6095e1fd2afe 100644 --- a/android/source/src/java/org/libreoffice/SearchController.java +++ b/android/source/src/java/org/libreoffice/SearchController.java @@ -11,7 +11,7 @@ import org.json.JSONException; import org.json.JSONObject; public class SearchController implements View.OnClickListener { - private LibreOfficeMainActivity mActivity; + private final LibreOfficeMainActivity mActivity; private enum SearchDirection { UP, DOWN @@ -69,15 +69,8 @@ public class SearchController implements View.OnClickListener { ImageButton button = (ImageButton) view; SearchDirection direction = SearchDirection.DOWN; - switch(button.getId()) { - case R.id.button_search_down: - direction = SearchDirection.DOWN; - break; - case R.id.button_search_up: - direction = SearchDirection.UP; - break; - default: - break; + if (button.getId() == R.id.button_search_up) { + direction = SearchDirection.UP; } String searchText = ((EditText) mActivity.findViewById(R.id.search_string)).getText().toString(); diff --git a/android/source/src/java/org/libreoffice/ThumbnailCreator.java b/android/source/src/java/org/libreoffice/ThumbnailCreator.java index 52870b67a5b9..c0c097747c69 100644 --- a/android/source/src/java/org/libreoffice/ThumbnailCreator.java +++ b/android/source/src/java/org/libreoffice/ThumbnailCreator.java @@ -71,7 +71,7 @@ public class ThumbnailCreator { class ThumbnailCreationTask{ private final WeakReference<ImageView> imageViewReference; - private int partNumber; + private final int partNumber; private boolean cancelled = false; public ThumbnailCreationTask(ImageView imageView, int partNumber) { diff --git a/android/source/src/java/org/libreoffice/TileProvider.java b/android/source/src/java/org/libreoffice/TileProvider.java index dabf30b834f7..c979a9883c13 100644 --- a/android/source/src/java/org/libreoffice/TileProvider.java +++ b/android/source/src/java/org/libreoffice/TileProvider.java @@ -22,9 +22,24 @@ import org.mozilla.gecko.gfx.IntSize; public interface TileProvider { /** - * Save the current document. + * Save the current document under the given path. + * @param takeOwnership Whether to take ownership of the new file, + * i.e. whether the current document is changed to the + * newly saved document (takeOwnership = true), + * as compared to just saving a copy of the current document + * or exporting to a different file format. + * Must be 'false' when using this method for export to e.g. PNG or PDF. + * @return Whether saving was successful. */ - void saveDocumentAs(String filePath, String format); + boolean saveDocumentAs(String filePath, String format, boolean takeOwnership); + + /** + * Saves the current document under the given path, + * using the default file format. + * @param takeOwnership (s. documentation for + * 'saveDocumentAs(String filePath, String format, boolean takeOwnership)') + */ + boolean saveDocumentAs(String filePath, boolean takeOwnership); /** * Returns the page width in pixels. @@ -72,6 +87,11 @@ public interface TileProvider { void close(); /** + * Returns true if the current open document is a drawing. + */ + boolean isDrawing(); + + /** * Returns true if the current open document is a text document. */ boolean isTextDocument(); diff --git a/android/source/src/java/org/libreoffice/ToolbarController.java b/android/source/src/java/org/libreoffice/ToolbarController.java index d21396cf4615..603d2258167e 100644 --- a/android/source/src/java/org/libreoffice/ToolbarController.java +++ b/android/source/src/java/org/libreoffice/ToolbarController.java @@ -11,7 +11,7 @@ package org.libreoffice; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; @@ -45,12 +45,12 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener { clipboardManager = (ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE); } - public void disableMenuItem(final int menuItemId, final boolean disabled) { + private void enableMenuItem(final int menuItemId, final boolean enabled) { LOKitShell.getMainHandler().post(new Runnable() { public void run() { MenuItem menuItem = mMainMenu.findItem(menuItemId); if (menuItem != null) { - menuItem.setEnabled(!disabled); + menuItem.setEnabled(enabled); } else { Log.e(LOGTAG, "MenuItem not found."); } @@ -72,6 +72,8 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener { void switchToEditMode() { if (!LOKitShell.isEditingEnabled()) return; + + setEditModeOn(true); // Ensure the change is done on UI thread LOKitShell.getMainHandler().post(new Runnable() { @Override @@ -89,7 +91,6 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener { } mToolbarTop.setNavigationIcon(R.drawable.ic_check); mToolbarTop.setLogo(null); - setEditModeOn(true); } }); } @@ -145,7 +146,7 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener { @Override public void run() { mMainMenu.setGroupVisible(R.id.group_edit_actions, false); - mToolbarTop.setNavigationIcon(R.drawable.lo_icon); + mToolbarTop.setNavigationIcon(R.mipmap.ic_launcher); mToolbarTop.setLogo(null); setEditModeOn(false); mContext.hideBottomToolbar(); @@ -161,94 +162,91 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener { @Override public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_keyboard: - mContext.showSoftKeyboard(); - break; - case R.id.action_format: - mContext.showFormattingToolbar(); - break; - case R.id.action_about: - mContext.showAbout(); - return true; - case R.id.action_save: - if (mContext.isNewDocument) { - mContext.saveAs(); - } else { - mContext.getTileProvider().saveDocument(); - } - return true; - case R.id.action_parts: - mContext.openDrawer(); - return true; - case R.id.action_exportToPDF: - mContext.getTileProvider().exportToPDF(false); - return true; - case R.id.action_print: - mContext.getTileProvider().exportToPDF(true); - return true; - case R.id.action_settings: - mContext.showSettings(); - return true; - case R.id.action_search: - mContext.showSearchToolbar(); - return true; - case R.id.action_undo: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Undo")); - return true; - case R.id.action_redo: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Redo")); - return true; - case R.id.action_presentation: - mContext.preparePresentation(); - return true; - case R.id.action_add_slide: - mContext.addPart(); - return true; - case R.id.action_add_worksheet: - mContext.addPart(); - return true; - case R.id.action_rename_worksheet: - case R.id.action_rename_slide: - mContext.renamePart(); - return true; - case R.id.action_delete_worksheet: - mContext.deletePart(); - return true; - case R.id.action_delete_slide: - mContext.deletePart(); - return true; - case R.id.action_back: - hideClipboardActions(); - return true; - case R.id.action_copy: - LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Copy")); - clipData = ClipData.newPlainText("clipboard data", clipboardText); - clipboardManager.setPrimaryClip(clipData); - Toast.makeText(mContext, mContext.getResources().getString(R.string.action_text_copied), Toast.LENGTH_SHORT).show(); - return true; - case R.id.action_paste: - clipData = clipboardManager.getPrimaryClip(); - ClipData.Item clipItem = clipData.getItemAt(0); - mContext.setDocumentChanged(true); - return mContext.getTileProvider().paste("text/plain;charset=utf-16", clipItem.getText().toString()); - case R.id.action_cut: - clipData = ClipData.newPlainText("clipboard data", clipboardText); - clipboardManager.setPrimaryClip(clipData); - LOKitShell.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); - mContext.setDocumentChanged(true); - return true; - case R.id.action_UNO_commands: - mContext.showUNOCommandsToolbar(); - return true; + final int itemId = item.getItemId(); + if (itemId == R.id.action_keyboard) { + mContext.showSoftKeyboard(); + } else if (itemId == R.id.action_format) { + mContext.showFormattingToolbar(); + } else if (itemId == R.id.action_about) { + mContext.showAbout(); + return true; + } else if (itemId == R.id.action_save) { + mContext.getTileProvider().saveDocument(); + return true; + } else if (itemId == R.id.action_save_as) { + mContext.saveDocumentAs(); + return true; + } else if (itemId == R.id.action_parts) { + mContext.openDrawer(); + return true; + } else if (itemId == R.id.action_exportToPDF) { + mContext.exportToPDF(); + return true; + } else if (itemId == R.id.action_print) { + mContext.getTileProvider().printDocument(); + return true; + } else if (itemId == R.id.action_settings) { + mContext.showSettings(); + return true; + } else if (itemId == R.id.action_search) { + mContext.showSearchToolbar(); + return true; + } else if (itemId == R.id.action_undo) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Undo")); + return true; + } else if (itemId == R.id.action_redo) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Redo")); + return true; + } else if (itemId == R.id.action_presentation) { + mContext.preparePresentation(); + return true; + } else if (itemId == R.id.action_add_slide || itemId == R.id.action_add_worksheet) { + mContext.addPart(); + return true; + } else if (itemId == R.id.action_rename_worksheet || itemId == R.id.action_rename_slide) { + mContext.renamePart(); + return true; + } else if (itemId == R.id.action_delete_worksheet || itemId == R.id.action_delete_slide) { + mContext.deletePart(); + return true; + } else if (itemId == R.id.action_back) { + hideClipboardActions(); + return true; + } else if (itemId == R.id.action_copy) { + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Copy")); + clipData = ClipData.newPlainText("clipboard data", clipboardText); + clipboardManager.setPrimaryClip(clipData); + Toast.makeText(mContext, mContext.getResources().getString(R.string.action_text_copied), Toast.LENGTH_SHORT).show(); + return true; + } else if (itemId == R.id.action_paste) { + clipData = clipboardManager.getPrimaryClip(); + ClipData.Item clipItem = clipData.getItemAt(0); + mContext.setDocumentChanged(true); + return mContext.getTileProvider().paste("text/plain;charset=utf-16", clipItem.getText().toString()); + } else if (itemId == R.id.action_cut) { + clipData = ClipData.newPlainText("clipboard data", clipboardText); + clipboardManager.setPrimaryClip(clipData); + LOKitShell.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + mContext.setDocumentChanged(true); + return true; + } else if (itemId == R.id.action_UNO_commands) { + mContext.showUNOCommandsToolbar(); + return true; } return false; } void setupToolbars() { - if (mContext.usesTemporaryFile()) { - disableMenuItem(R.id.action_save, true); - Toast.makeText(mContext, mContext.getString(R.string.temp_file_saving_disabled), Toast.LENGTH_LONG).show(); + if (LibreOfficeMainActivity.isExperimentalMode()) { + boolean enableSaveEntry = !LibreOfficeMainActivity.isReadOnlyMode() && mContext.hasLocationForSave(); + enableMenuItem(R.id.action_save, enableSaveEntry); + if (LibreOfficeMainActivity.isReadOnlyMode()) { + // show message in case experimental mode is enabled (i.e. editing is supported in general), + // but current document is readonly + Toast.makeText(mContext, mContext.getString(R.string.readonly_file), Toast.LENGTH_LONG).show(); + } + } else { + hideItem(R.id.action_save); } mMainMenu.findItem(R.id.action_parts).setVisible(mContext.isDrawerEnabled()); } diff --git a/android/source/src/java/org/libreoffice/UNOCommandsController.java b/android/source/src/java/org/libreoffice/UNOCommandsController.java index 9453b3bd07b7..cba67732cce1 100644 --- a/android/source/src/java/org/libreoffice/UNOCommandsController.java +++ b/android/source/src/java/org/libreoffice/UNOCommandsController.java @@ -8,7 +8,7 @@ package org.libreoffice; import android.content.DialogInterface; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import android.text.method.ScrollingMovementMethod; import android.view.View; import android.widget.EditText; @@ -21,7 +21,7 @@ import org.json.JSONObject; import static org.libreoffice.SearchController.addProperty; class UNOCommandsController implements View.OnClickListener { - private LibreOfficeMainActivity mActivity; + private final LibreOfficeMainActivity mActivity; private JSONObject mRootJSON = new JSONObject(); @@ -72,7 +72,7 @@ class UNOCommandsController implements View.OnClickListener { }) .setIcon(android.R.drawable.ic_dialog_info) .show(); - TextView textView = (TextView) dialog.findViewById(android.R.id.message); + TextView textView = dialog.findViewById(android.R.id.message); if (textView != null) { textView.setScroller(new Scroller(mActivity)); textView.setVerticalScrollBarEnabled(true); @@ -82,4 +82,4 @@ class UNOCommandsController implements View.OnClickListener { e.printStackTrace(); } } -}
\ No newline at end of file +} diff --git a/android/source/src/java/org/libreoffice/canvas/BitmapHandle.java b/android/source/src/java/org/libreoffice/canvas/BitmapHandle.java index e46173db518f..51f6f7cf8611 100644 --- a/android/source/src/java/org/libreoffice/canvas/BitmapHandle.java +++ b/android/source/src/java/org/libreoffice/canvas/BitmapHandle.java @@ -5,7 +5,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.support.v4.content.ContextCompat; +import androidx.core.content.ContextCompat; /** * Bitmap handle canvas element is used to show a handle on the screen. diff --git a/android/source/src/java/org/libreoffice/canvas/CalcHeaderCell.java b/android/source/src/java/org/libreoffice/canvas/CalcHeaderCell.java index c1f8e74e7ba2..a285234bc8b0 100644 --- a/android/source/src/java/org/libreoffice/canvas/CalcHeaderCell.java +++ b/android/source/src/java/org/libreoffice/canvas/CalcHeaderCell.java @@ -4,29 +4,40 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; +import android.graphics.Rect; import android.graphics.RectF; import android.text.TextPaint; public class CalcHeaderCell extends CommonCanvasElement { - private TextPaint mTextPaint = new TextPaint(); - private Paint mBgPaint = new Paint(); - private RectF mBounds; - private String mText; + private final TextPaint mTextPaint = new TextPaint(); + + private final Paint mFramePaint = new Paint(); + private final Paint mBgPaint = new Paint(); + private final RectF mBounds; + private final Rect mTextBounds = new Rect(); + private final String mText; public CalcHeaderCell(float left, float top, float width, float height, String text, boolean selected) { mBounds = new RectF(left, top, left + width, top + height); + + mFramePaint.setStyle(Style.STROKE); + mFramePaint.setColor(Color.BLACK); + + mBgPaint.setStyle(Style.FILL); + mBgPaint.setColor(Color.GRAY); + // draw background more intensely when cell is selected if (selected) { - // if the cell is selected, display filled - mBgPaint.setStyle(Style.FILL_AND_STROKE); + mBgPaint.setAlpha(100); } else { - // if not, display only the frame - mBgPaint.setStyle(Style.STROKE); + mBgPaint.setAlpha(25); } - mBgPaint.setColor(Color.GRAY); - mBgPaint.setAlpha(100); // hard coded for now - mTextPaint.setColor(Color.GRAY); + + mTextPaint.setColor(Color.BLACK); mTextPaint.setTextSize(24f); // hard coded for now + mTextPaint.setTextAlign(Paint.Align.CENTER); mText = text; + + mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds); } /** @@ -49,6 +60,7 @@ public class CalcHeaderCell extends CommonCanvasElement { @Override public void onDraw(Canvas canvas) { canvas.drawRect(mBounds, mBgPaint); - canvas.drawText(mText, mBounds.left, mBounds.bottom, mTextPaint); + canvas.drawRect(mBounds, mFramePaint); + canvas.drawText(mText, mBounds.centerX(), mBounds.centerY() - mTextBounds.centerY(), mTextPaint); } -}
\ No newline at end of file +} diff --git a/android/source/src/java/org/libreoffice/overlay/CalcHeadersController.java b/android/source/src/java/org/libreoffice/overlay/CalcHeadersController.java index 40c9ddcd8cea..8b99c292cbc5 100644 --- a/android/source/src/java/org/libreoffice/overlay/CalcHeadersController.java +++ b/android/source/src/java/org/libreoffice/overlay/CalcHeadersController.java @@ -4,7 +4,7 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; -import android.support.design.widget.Snackbar; +import com.google.android.material.snackbar.Snackbar; import android.util.Log; import android.view.KeyEvent; import android.view.Gravity; diff --git a/android/source/src/java/org/libreoffice/overlay/CalcHeadersView.java b/android/source/src/java/org/libreoffice/overlay/CalcHeadersView.java index a8b2d2048409..98af7a9554e7 100644 --- a/android/source/src/java/org/libreoffice/overlay/CalcHeadersView.java +++ b/android/source/src/java/org/libreoffice/overlay/CalcHeadersView.java @@ -4,7 +4,7 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.PointF; import android.graphics.RectF; -import android.support.v4.view.GestureDetectorCompat; +import androidx.core.view.GestureDetectorCompat; import android.util.AttributeSet; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; @@ -101,12 +101,8 @@ public class CalcHeadersView extends View { bottom = -origin.y + zoom*mDimens.get(i); if (top <= getHeight() && bottom >= 0) { inRangeOfVisibleHeaders = true; - if (mCellCursorRect != null && bottom > mCellCursorRect.top - origin.y && top < mCellCursorRect.bottom - origin.y) { - // if cell is within current selected portion - new CalcHeaderCell(0f, top, getWidth(), bottom - top, mLabels.get(i), true).onDraw(canvas); - } else { - new CalcHeaderCell(0f, top, getWidth(), bottom - top, mLabels.get(i), false).onDraw(canvas); - } + boolean isSelected = mCellCursorRect != null && bottom > mCellCursorRect.top - origin.y && top < mCellCursorRect.bottom - origin.y; + new CalcHeaderCell(0f, top, getWidth(), bottom - top, mLabels.get(i), isSelected).onDraw(canvas); } else { if (inRangeOfVisibleHeaders) { break; @@ -116,12 +112,8 @@ public class CalcHeadersView extends View { left = -origin.x + zoom*mDimens.get(i-1); right = -origin.x + zoom*mDimens.get(i); if (left <= getWidth() && right >= 0) { - if (mCellCursorRect != null && right > mCellCursorRect.left - origin.x && left < mCellCursorRect.right - origin.x) { - // if cell is within current selected portion - new CalcHeaderCell(left, 0f, right - left, getHeight(), mLabels.get(i), true).onDraw(canvas); - } else { - new CalcHeaderCell(left, 0f, right - left, getHeight(), mLabels.get(i), false).onDraw(canvas); - } + boolean isSelected = mCellCursorRect != null && right > mCellCursorRect.left - origin.x && left < mCellCursorRect.right - origin.x; + new CalcHeaderCell(left, 0f, right - left, getHeight(), mLabels.get(i), isSelected).onDraw(canvas); } else { if (inRangeOfVisibleHeaders) { break; diff --git a/android/source/src/java/org/libreoffice/storage/DocumentProviderFactory.java b/android/source/src/java/org/libreoffice/storage/DocumentProviderFactory.java deleted file mode 100644 index acf5aebcd6c6..000000000000 --- a/android/source/src/java/org/libreoffice/storage/DocumentProviderFactory.java +++ /dev/null @@ -1,128 +0,0 @@ -/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.libreoffice.storage; - -import java.util.HashSet; -import java.util.Set; - -import org.libreoffice.storage.external.ExtsdDocumentsProvider; -import org.libreoffice.storage.external.OTGDocumentsProvider; -import org.libreoffice.storage.local.LocalDocumentsDirectoryProvider; -import org.libreoffice.storage.local.LocalDocumentsProvider; -import org.libreoffice.storage.owncloud.OwnCloudProvider; - -import android.content.Context; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; - -/** - * Keeps the instances of the available IDocumentProviders in the system. - * Instances are maintained in a sorted list and providers have to be - * accessed from their position. - * - * The factory follows the Singleton pattern, there is only one instance of it - * in the application and it must be retrieved with - * DocumentProviderFactory.getInstance(). - */ -public final class DocumentProviderFactory { - public static int EXTSD_PROVIDER_INDEX = 2; - public static int OTG_PROVIDER_INDEX = 3; - - /** - * Private factory instance for the Singleton pattern. - */ - private static DocumentProviderFactory instance = null; - - private IDocumentProvider[] providers; - - private String[] providerNames; - - private DocumentProviderFactory() { - // private to prevent external instances of the factory - } - - /** - * Initializes the factory with some context. If this method is called for - * twice or more times those calls will have no effect. - * - * @param context - * Application context for the factory. - */ - public static void initialize(Context context) { - if (instance == null) { - // initialize instance - instance = new DocumentProviderFactory(); - - // initialize document providers list - instance.providers = new IDocumentProvider[5]; - instance.providers[0] = new LocalDocumentsDirectoryProvider(0); - instance.providers[1] = new LocalDocumentsProvider(1); - instance.providers[OTG_PROVIDER_INDEX] = new OTGDocumentsProvider(OTG_PROVIDER_INDEX, context); - instance.providers[4] = new OwnCloudProvider(4, context); - - instance.providers[EXTSD_PROVIDER_INDEX] = new ExtsdDocumentsProvider(EXTSD_PROVIDER_INDEX, context); - - // initialize document provider names list - instance.providerNames = new String[instance.providers.length]; - for (int i = 0; i < instance.providers.length; i++) { - instance.providerNames[i] = context.getString(instance - .getProvider(i).getNameResource()); - } - } - } - - /** - * Retrieve the unique instance of the factory. - * - * @return the unique factory object or null if it is not yet initialized. - */ - public static DocumentProviderFactory getInstance() { - return instance; - } - - /** - * Retrieve the provider associated to a certain id. - * - * @param id - * @return document provider with that id. - */ - public IDocumentProvider getProvider(int id) { - // as for now, id == position in providers array - return providers[id]; - } - - /** - * Returns a sorted list of the names of the providers. Order is meaningful - * to retrieve the actual provider object with getProvider(). - * - * @return Array with the names of the available providers. - */ - public String[] getNames() { - return providerNames; - } - - /** - * Returns the default provider. - * - * @return default provider. - */ - public IDocumentProvider getDefaultProvider() { - return providers[0]; - } - - public Set<OnSharedPreferenceChangeListener> getChangeListeners() { - Set<OnSharedPreferenceChangeListener> listeners = - new HashSet<OnSharedPreferenceChangeListener>(); - for (IDocumentProvider provider : providers) { - if (provider instanceof OnSharedPreferenceChangeListener) - listeners.add((OnSharedPreferenceChangeListener) provider); - } - return listeners; - } -} diff --git a/android/source/src/java/org/libreoffice/storage/DocumentProviderSettingsActivity.java b/android/source/src/java/org/libreoffice/storage/DocumentProviderSettingsActivity.java deleted file mode 100644 index b842e79fafd6..000000000000 --- a/android/source/src/java/org/libreoffice/storage/DocumentProviderSettingsActivity.java +++ /dev/null @@ -1,102 +0,0 @@ -/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.libreoffice.storage; - -import java.util.Set; - -import org.libreoffice.R; -import org.libreoffice.storage.external.BrowserSelectorActivity; - -import android.content.Intent; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceManager; -import android.preference.PreferenceScreen; -import android.support.v7.app.AppCompatActivity; - -public class DocumentProviderSettingsActivity extends AppCompatActivity { - - public static final String KEY_PREF_OWNCLOUD_SERVER = "pref_server_url"; - public static final String KEY_PREF_OWNCLOUD_USER_NAME = "pref_user_name"; - public static final String KEY_PREF_OWNCLOUD_PASSWORD = "pref_password"; - public static final String KEY_PREF_EXTERNAL_SD_PATH_URI = "pref_extsd_path_uri"; - public static final String KEY_PREF_OTG_PATH_URI = "pref_otg_path_uri"; - - private Set<OnSharedPreferenceChangeListener> listeners; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // Display the fragment as the main content. - getFragmentManager().beginTransaction() - .replace(android.R.id.content, new SettingsFragment()).commit(); - } - - @Override - protected void onResume() { - super.onResume(); - listeners = DocumentProviderFactory.getInstance().getChangeListeners(); - for (OnSharedPreferenceChangeListener listener : listeners) { - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(listener); - } - } - - @Override - protected void onPause() { - super.onPause(); - for (OnSharedPreferenceChangeListener listener : listeners) { - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(listener); - } - } - - public static class SettingsFragment extends PreferenceFragment { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.documentprovider_preferences); - - PreferenceScreen extSDPreference = - (PreferenceScreen)findPreference(KEY_PREF_EXTERNAL_SD_PATH_URI); - PreferenceScreen otgPreference = - (PreferenceScreen)findPreference(KEY_PREF_OTG_PATH_URI); - - extSDPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startBrowserSelectorActivity(KEY_PREF_EXTERNAL_SD_PATH_URI, - BrowserSelectorActivity.MODE_EXT_SD); - return true; - } - }); - otgPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startBrowserSelectorActivity(KEY_PREF_OTG_PATH_URI, - BrowserSelectorActivity.MODE_OTG); - return true; - } - }); - - } - - private void startBrowserSelectorActivity(String prefKey, String mode) { - Intent i = new Intent(getActivity(), BrowserSelectorActivity.class); - i.putExtra(BrowserSelectorActivity.PREFERENCE_KEY_EXTRA, prefKey); - i.putExtra(BrowserSelectorActivity.MODE_EXTRA, mode); - startActivity(i); - } - - } -} diff --git a/android/source/src/java/org/libreoffice/storage/IDocumentProvider.java b/android/source/src/java/org/libreoffice/storage/IDocumentProvider.java deleted file mode 100644 index 044d7ddb422b..000000000000 --- a/android/source/src/java/org/libreoffice/storage/IDocumentProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.libreoffice.storage; - -import android.content.Context; - -import java.net.URI; - -/** - * Represents a Document Provider, an object able to provide documents from a - * certain source (e.g. local documents, DropBox, Google Docs). - */ -public interface IDocumentProvider { - - /** - * Provides the content root element for the Document Provider. - * - * @return Content root element. - * @throws RuntimeException in case of error. - * @param context - */ - IFile getRootDirectory(Context context); - - /** - * Transforms some URI into the IFile object that represents that content. - * - * - * @param context - * @param uri - * URI pointing to some content object that has been previously - * retrieved with IFile.getUri(). - * @return IFile object pointing to the content represented by uri. - * @throws RuntimeException in case of error. - */ - IFile createFromUri(Context context, URI uri); - - /** - * Get internationalized name for this provider. This name is intended to be - * shown in the UI. - * - * @return string resource pointing to the provider name. - */ - int getNameResource(); - - /** - * Provides the unique ID for a document provider instance in a program. - * - * This ID should be set when the instance is built. It could be used to - * tell two instances of the same document provider apart, e. g. two - * instances of OwnCloudProvider pointing to different servers. - * - * @return Unique ID for a document provider instance. - */ - int getId(); - - /** - * Checks if the Document Provider is available or not. - * - * @return A boolean value based on provider availability. - * @param context - */ - boolean checkProviderAvailability(Context context); -} diff --git a/android/source/src/java/org/libreoffice/storage/IFile.java b/android/source/src/java/org/libreoffice/storage/IFile.java deleted file mode 100644 index c9cfa7f1198d..000000000000 --- a/android/source/src/java/org/libreoffice/storage/IFile.java +++ /dev/null @@ -1,116 +0,0 @@ -/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.libreoffice.storage; - -import android.content.Context; - -import java.io.File; -import java.io.FileFilter; -import java.net.URI; -import java.util.Date; -import java.util.List; - -/** - * An abstraction of the File class, intended to be implemented by different - * Document Providers. - * - * It represents a file or a directory in the context of a certain Document - * Provider. It wraps the file-related operations and provides access to the - * final document as a local File, downloading it if necessary. - */ -public interface IFile { - - /** - * Provides a URI that represents this IFile object. - * - * @return URI that represents this IFile object in the context of the - * Document Provider that created it. The URI can be transformed - * back into an IFile object with IDocumentProvider.createFromUri(). - */ - URI getUri(); - - /** - * Returns the name of the file or directory represented by this file. - * - * @return This file's name. - */ - String getName(); - - /** - * Indicates if this file represents a directory in the context of the - * Document Provider which originated it. - * - * @return true if this file is a directory, false otherwise. - */ - boolean isDirectory(); - - /** - * Returns the file size in bytes. - * - * @return file size in bytes, 0 if the file does not exist. - */ - long getSize(); - - /** - * Returns the time when this file was last modified, measured in - * milliseconds since January 1st, 1970, midnight. - * - * @return time when this file was last modified, or 0 if the file does not - * exist. - */ - Date getLastModified(); - - /** - * Returns a list containing the files in the directory represented by this - * file. - * - * @return list of files contained by this directory, or an empty list if - * this is not a directory. - * @throws RuntimeException in case of error. - */ - List<IFile> listFiles(); - - /** - * Gets the list of files contained in the directory represented by this - * file, and filters it through some FilenameFilter. - * - * @param filter - * the filter to match names against. - * @return filtered list of files contained by this directory, or an empty - * list if this is not a directory. - * @throws RuntimeException in case of error. - */ - List<IFile> listFiles(FileFilter filter); - - /** - * Returns the pparent of this file. - * - * @return this file's parent or null if it does not have it. - * @param context - */ - IFile getParent(Context context); - - /** - * Returns the document wrapped by this IFile as a local file. The result - * for a directory is not defined. - * - * @return local file containing the document wrapped by this object. - * @throws RuntimeException in case of error. - */ - File getDocument(); - - /** - * Replaces the wrapped document with a new version of it. - * - * @param file - * A local file pointing to the new version of the document. - */ - void saveDocument(File file); -} diff --git a/android/source/src/java/org/libreoffice/storage/IOUtils.java b/android/source/src/java/org/libreoffice/storage/IOUtils.java deleted file mode 100644 index f345f5cbed3b..000000000000 --- a/android/source/src/java/org/libreoffice/storage/IOUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.libreoffice.storage; - -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.net.URISyntaxException; - -/** - * File IO related methods. - */ -public class IOUtils { - private static final int BUFFER_SIZE = 1024 * 8; - private static final String LOGTAG = IOUtils.class.getSimpleName(); - - public static File getFileFromURIString(String URIpath) throws IllegalArgumentException{ - try{ - return new File(new URI(URIpath)); - } catch (URISyntaxException e) { - //should not happen as all URIs are system generated - Log.wtf(LOGTAG, e.getReason()); - return null; - } - } - - public static boolean isInvalidFile(File f) { - return f == null || !f.exists() || f.getTotalSpace() == 0 - || !f.canRead() || !f.canWrite(); - } - - public static int copy(InputStream input, OutputStream output) throws Exception { - byte[] buffer = new byte[BUFFER_SIZE]; - - BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE); - BufferedOutputStream out = new BufferedOutputStream(output, BUFFER_SIZE); - - int count = 0, n = 0; - try { - while ((n = in.read(buffer, 0, BUFFER_SIZE)) != -1) { - out.write(buffer, 0, n); - count += n; - } - out.flush(); - } finally { - if (out != null) out.close(); - if (in != null) in.close(); - } - - return count; - } - -} diff --git a/android/source/src/java/org/libreoffice/storage/external/BrowserSelectorActivity.java b/android/source/src/java/org/libreoffice/storage/external/BrowserSelectorActivity.java deleted file mode 100644 index 07b64623b701..000000000000 --- a/android/source/src/java/org/libreoffice/storage/external/BrowserSelectorActivity.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.libreoffice.storage.external; - -import android.annotation.TargetApi; -import android.content.ContentResolver; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.UriPermission; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; - -import org.libreoffice.R; -import org.libreoffice.storage.DocumentProviderFactory; - -import java.util.Set; - -/** - * Activity to select which directory browser to use. - * Android 5+ will use the DocumentTree intent to locate a browser. - * Android 4+ & OTG will use the internal directory browser. - */ -public class BrowserSelectorActivity extends AppCompatActivity { - public static final String PREFERENCE_KEY_EXTRA = "org.libreoffice.pref_key_extra"; - public static final String MODE_EXTRA = "org.libreoffice.mode_extra"; - public static final String MODE_OTG = "OTG"; - public static final String MODE_EXT_SD = "EXT_SD"; - - private static final String LOGTAG = BrowserSelectorActivity.class.getSimpleName(); - private static final int REQUEST_DOCUMENT_TREE = 1; - private static final int REQUEST_INTERNAL_BROWSER = 2; - private Set<SharedPreferences.OnSharedPreferenceChangeListener> listeners; - private String preferenceKey; - private SharedPreferences preferences; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - preferenceKey = getIntent().getStringExtra(PREFERENCE_KEY_EXTRA); - preferences = PreferenceManager.getDefaultSharedPreferences(this); - String mode = getIntent().getStringExtra(MODE_EXTRA); - - if(mode.equals(MODE_EXT_SD)) { - findSDCard(); - } else if (mode.equals(MODE_OTG)) { - findOTGDevice(); - } - } - - private void findOTGDevice() { - useInternalBrowser(DocumentProviderFactory.OTG_PROVIDER_INDEX); - } - - private void findSDCard() { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - useDocumentTreeBrowser(); - } else { - useInternalBrowser(DocumentProviderFactory.EXTSD_PROVIDER_INDEX); - } - } - - private void useInternalBrowser(int providerIndex) { - IExternalDocumentProvider provider = - (IExternalDocumentProvider) DocumentProviderFactory.getInstance() - .getProvider(providerIndex); - String previousDirectoryPath = preferences.getString(preferenceKey, provider.guessRootURI(this)); - Intent i = new Intent(this, DirectoryBrowserActivity.class); - i.putExtra(DirectoryBrowserActivity.DIRECTORY_PATH_EXTRA, previousDirectoryPath); - startActivityForResult(i, REQUEST_INTERNAL_BROWSER); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void useDocumentTreeBrowser() { - Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - startActivityForResult(i, REQUEST_DOCUMENT_TREE); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - //listeners are registered here as onActivityResult is called before onResume - super.onActivityResult(requestCode, resultCode, data); - - registerListeners(); - if(resultCode == RESULT_OK) { - switch(requestCode) { - case REQUEST_DOCUMENT_TREE: - Uri treeUri = data.getData(); - preferences.edit() - .putString(preferenceKey, treeUri.toString()) - .apply(); - - updatePersistedUriPermission(treeUri); - getContentResolver().takePersistableUriPermission(treeUri, - Intent.FLAG_GRANT_READ_URI_PERMISSION | - Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - break; - - case REQUEST_INTERNAL_BROWSER: - Uri fileUri = data.getData(); - preferences.edit() - .putString(preferenceKey, fileUri.toString()) - .apply(); - break; - default: - } - } - unregisterListeners(); - Log.d(LOGTAG, "Preference saved: " + - preferences.getString(preferenceKey, getString(R.string.directory_not_saved))); - finish(); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void updatePersistedUriPermission(Uri treeUri) { - freePreviousUriPermissions(); - - //TODO: Use non-emulator Android 5+ device to check if needed - /*this.grantUriPermission(this.getPackageName(), - treeUri, - Intent.FLAG_GRANT_READ_URI_PERMISSION | - Intent.FLAG_GRANT_WRITE_URI_PERMISSION); */ - - getContentResolver().takePersistableUriPermission(treeUri, - Intent.FLAG_GRANT_READ_URI_PERMISSION | - Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void freePreviousUriPermissions() { - ContentResolver cr = getContentResolver(); - for (UriPermission uriPermission : cr.getPersistedUriPermissions()) { - cr.releasePersistableUriPermission(uriPermission.getUri(), 0); - } - } - - private void registerListeners() { - listeners = DocumentProviderFactory.getInstance().getChangeListeners(); - for (SharedPreferences.OnSharedPreferenceChangeListener listener : listeners) { - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(listener); - } - } - - private void unregisterListeners() { - for (SharedPreferences.OnSharedPreferenceChangeListener listener : listeners) { - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(listener); - } - } -} diff --git a/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserActivity.java b/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserActivity.java deleted file mode 100644 index 1cf9f52fa7c0..000000000000 --- a/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserActivity.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.libreoffice.storage.external; - - -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; - -import org.libreoffice.R; - -/** - * Container for DirectoryBrowserFragment - */ -public class DirectoryBrowserActivity extends AppCompatActivity { - public static final String DIRECTORY_PATH_EXTRA = "org.libreoffice.directory_path_extra"; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Intent data = getIntent(); - String initialPath = data.getStringExtra(DIRECTORY_PATH_EXTRA); - - setContentView(R.layout.activity_directory_browser); - FragmentManager fm = getFragmentManager(); - Fragment fragment = DirectoryBrowserFragment.newInstance(initialPath); - fm.beginTransaction() - .add(R.id.fragment_container, fragment) - .commit(); - } - - @Override - public void onBackPressed() { - FragmentManager fm = getFragmentManager(); - if(fm.getBackStackEntryCount() > 0) { - fm.popBackStack(); - } else { - super.onBackPressed(); - } - } -} diff --git a/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserFragment.java b/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserFragment.java deleted file mode 100644 index 18165650a617..000000000000 --- a/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserFragment.java +++ /dev/null @@ -1,199 +0,0 @@ -package org.libreoffice.storage.external; - -import android.app.Activity; -import android.app.Fragment; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.support.annotation.Nullable; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import org.libreoffice.R; -import org.libreoffice.storage.IOUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.Comparator; - -/** - * A simple directory browser. - */ -public class DirectoryBrowserFragment extends Fragment { - private static final String LOGTAG = DirectoryBrowserFragment.class.getSimpleName(); - private static final String INITIAL_PATH_URI_KEY = "initial_path"; - private File currentDirectory; - private FileArrayAdapter directoryAdapter; - - public static DirectoryBrowserFragment newInstance(String initialPathURI) { - Bundle args = new Bundle(); - args.putString(INITIAL_PATH_URI_KEY, initialPathURI); - DirectoryBrowserFragment fragment = new DirectoryBrowserFragment(); - fragment.setArguments(args); - Log.d(LOGTAG, "Saved path: " + initialPathURI); - - return fragment; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - String initialPathURI = getArguments().getString(INITIAL_PATH_URI_KEY); - setupCurrentDirectory(initialPathURI); - } - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.fragment_directory_browser, container, false); - - final EditText directoryHeader = v.findViewById(R.id.directory_header); - Button directorySearchButton = v.findViewById(R.id.directory_search_button); - Button positiveButton = v.findViewById(R.id.confirm_button); - Button negativeButton = v.findViewById(R.id.cancel_button); - ImageView upImage = v.findViewById(R.id.up_image); - ListView directoryListView = v.findViewById(R.id.directory_list); - - directoryHeader.setText(currentDirectory.getPath()); - directorySearchButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - String currentPath = currentDirectory.getAbsolutePath(); - String enteredPath = directoryHeader.getText().toString(); - File testDirectory = new File(enteredPath); - if(enteredPath.equals(currentPath)) ; - else if (isInvalidFileDirectory(testDirectory)) { - Toast.makeText(getActivity(), R.string.bad_directory, Toast.LENGTH_SHORT) - .show(); - } - else { - changeDirectory(testDirectory); - } - } - }); - - positiveButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent data = new Intent(); - data.setData(Uri.fromFile(currentDirectory)); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); - } - }); - - negativeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().setResult(Activity.RESULT_CANCELED, null); - getActivity().finish(); - } - }); - - upImage.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - changeDirectory(currentDirectory.getParentFile()); - } - }); - - directoryAdapter = new FileArrayAdapter(getActivity(), new ArrayList<File>()); - directoryAdapter.populateFileList(currentDirectory); - directoryListView.setAdapter(directoryAdapter); - directoryListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - changeDirectory(directoryAdapter.getItem(position)); - } - }); - - return v; - } - - private void changeDirectory(File destination) { - if(destination == null) { - Toast.makeText(getActivity(), R.string.unable_to_go_further, Toast.LENGTH_SHORT) - .show(); - } else { - Fragment fragment = DirectoryBrowserFragment.newInstance(destination.toURI().toString()); - getActivity().getFragmentManager().beginTransaction() - .replace(R.id.fragment_container, fragment) - .addToBackStack(null) - .commit(); - } - } - - private void setupCurrentDirectory(String initialPathURI) { - File initialDirectory = null; - if(initialPathURI != null && !initialPathURI.isEmpty()) { - initialDirectory = IOUtils.getFileFromURIString(initialPathURI); - } - - if(isInvalidFileDirectory(initialDirectory)) { - initialDirectory = Environment.getExternalStorageDirectory(); - } - currentDirectory = initialDirectory; - } - - private boolean isInvalidFileDirectory(File f) { - return f == null || !f.exists() || !f.isDirectory() ||!f.canRead(); - } - - private class FileArrayAdapter extends ArrayAdapter<File> { - private Comparator<File> caseInsensitiveNaturalOrderComparator; - - public FileArrayAdapter(Context context, ArrayList<File> files) { - super(context, 0, files); - caseInsensitiveNaturalOrderComparator = new AlphabeticalFileComparator(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = getActivity().getLayoutInflater() - .inflate(android.R.layout.simple_list_item_1, null); - } - - File f = this.getItem(position); - TextView tv = convertView.findViewById(android.R.id.text1); - tv.setText(f.getName()); - - return convertView; - } - - public void sortAlphabetically() { - this.sort(caseInsensitiveNaturalOrderComparator); - } - - public void populateFileList(File directory) { - for(File f : directory.listFiles()){ - if(f.isDirectory()){ - directoryAdapter.add(f); - } - } - directoryAdapter.sortAlphabetically(); - } - } - - private class AlphabeticalFileComparator implements Comparator<File> { - @Override - public int compare(File lhs, File rhs) { - String lhsName = lhs.getName(); - String rhsName = rhs.getName(); - - return lhsName.compareToIgnoreCase(rhsName); - } - } -} diff --git a/android/source/src/java/org/libreoffice/storage/external/ExternalFile.java b/android/source/src/java/org/libreoffice/storage/external/ExternalFile.java deleted file mode 100644 index aff33e4413ef..000000000000 --- a/android/source/src/java/org/libreoffice/storage/external/ExternalFile.java +++ /dev/null @@ -1,163 +0,0 @@ -package org.libreoffice.storage.external; - -import android.content.Context; -import android.support.v4.provider.DocumentFile; -import android.util.Log; - -import org.libreoffice.storage.IFile; -import org.libreoffice.storage.IOUtils; - -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * Implementation of IFile for the external file system, for Android 4.4+ - * - * Uses the DocumentFile class. - * - * The DocumentFile class obfuscates the path of the files it wraps, - * preventing usage of LOK's documentLoad method. A copy of the DocumentFile's contents - * will be created in the cache when files are opened, allowing use of documentLoad. - */ -public class ExternalFile implements IFile{ - private final static String LOGTAG = "ExternalFile"; - - private ExtsdDocumentsProvider provider; - private DocumentFile docFile; - private File duplicateFile; - private Context context; - - public ExternalFile(ExtsdDocumentsProvider provider, DocumentFile docFile, Context context) { - this.provider = provider; - this.context = context; - this.docFile = docFile; - } - - @Override - public URI getUri() { - try{ - return new URI(docFile.toString()); - } catch (URISyntaxException e) { - Log.e(LOGTAG, e.getMessage(), e.getCause()); - return null; - } - } - - @Override - public String getName() { - return docFile.getName(); - } - - @Override - public boolean isDirectory() { - return docFile.isDirectory(); - } - - @Override - public long getSize() { - return docFile.length(); - } - - @Override - public Date getLastModified() { - return new Date(docFile.lastModified()); - } - - @Override - public List<IFile> listFiles() { - List<IFile> children = new ArrayList<IFile>(); - for (DocumentFile child : docFile.listFiles()) { - children.add(new ExternalFile(provider, child, context)); - } - return children; - } - - @Override - public List<IFile> listFiles(FileFilter filter) { - File file; - try{ - List<IFile> children = new ArrayList<IFile>(); - for (DocumentFile child : docFile.listFiles()) { - file = new File(new URI(child.getUri().toString())); - if(filter.accept(file)) - children.add(new ExternalFile(provider, child, context)); - } - return children; - - }catch (Exception e){ - e.printStackTrace(); - } - /* if something goes wrong */ - return listFiles(); - - } - - @Override - public IFile getParent(Context context) { - // this is the root node - if(docFile.getParentFile() == null) return null; - - return new ExternalFile(provider, docFile.getParentFile(), this.context); - } - - @Override - public File getDocument() { - if(isDirectory()) { - return null; - } else { - duplicateFile = duplicateInCache(); - return duplicateFile; - } - } - - private File duplicateInCache() { - try{ - InputStream istream = context.getContentResolver(). - openInputStream(docFile.getUri()); - - File storageFolder = provider.getCacheDir(); - File fileCopy = new File(storageFolder, docFile.getName()); - OutputStream ostream = new FileOutputStream(fileCopy); - - IOUtils.copy(istream, ostream); - return fileCopy; - } catch (Exception e) { - Log.e(LOGTAG, e.getMessage(), e.getCause()); - return null; - } - } - - @Override - public void saveDocument(File file) { - try{ - OutputStream ostream = context.getContentResolver(). - openOutputStream(docFile.getUri()); - InputStream istream = new FileInputStream(file); - - IOUtils.copy(istream, ostream); - - } catch (Exception e) { - Log.e(LOGTAG, e.getMessage(), e.getCause()); - } - } - - @Override - public boolean equals(Object object) { - if (this == object) - return true; - if (!(object instanceof ExternalFile)) - return false; - ExternalFile file = (ExternalFile) object; - return file.getUri().equals(getUri()); - } - -} diff --git a/android/source/src/java/org/libreoffice/storage/external/ExtsdDocumentsProvider.java b/android/source/src/java/org/libreoffice/storage/external/ExtsdDocumentsProvider.java deleted file mode 100644 index e45929374bbd..000000000000 --- a/android/source/src/java/org/libreoffice/storage/external/ExtsdDocumentsProvider.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.libreoffice.storage.external; - -import android.Manifest; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.preference.PreferenceManager; -import android.support.v4.content.ContextCompat; -import android.support.v4.provider.DocumentFile; -import android.util.Log; - -import org.libreoffice.R; -import org.libreoffice.storage.DocumentProviderSettingsActivity; -import org.libreoffice.storage.IFile; - -import java.io.File; -import java.net.URI; - -/** - * Implementation of IDocumentProvider for the external file system, for android 4.4+ - * - * The DocumentFile class is required when accessing files in external storage - * for Android 4.4+. The ExternalFile class is used to handle this. - * - * Android 4.4 & 5+ use different types of root directory paths, - * 5 using a DirectoryTree Uri and 4.4 using a normal File path. - * As such, different methods are required to obtain the rootDirectory IFile. - * 4.4 has to guess the location of the rootDirectory as well. - */ -public class ExtsdDocumentsProvider implements IExternalDocumentProvider, - OnSharedPreferenceChangeListener{ - private static final String LOGTAG = ExtsdDocumentsProvider.class.getSimpleName(); - - private int id; - private File cacheDir; - private String rootPathURI; - - public ExtsdDocumentsProvider(int id, Context context) { - this.id = id; - setupRootPathUri(context); - setupCache(context); - } - - private void setupRootPathUri(Context context) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - rootPathURI = preferences.getString( - DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI, guessRootURI(context)); - } - - public String guessRootURI(Context context) { - // TODO: unfortunately the getExternalFilesDirs function relies on devices to actually - // follow guidelines re external storage. Of course device manufacturers don't and as such - // you cannot rely on it returning the actual paths (neither the compat, nor the native variant) - File[] possibleRemovables = ContextCompat.getExternalFilesDirs(context,null); - // the primary dir that is already covered by the "LocalDocumentsProvider" - // might be emulated/part of internal memory or actual SD card - // TODO: change to not confuse android's "external storage" with "expandable storage" - String primaryExternal = Environment.getExternalStorageDirectory().getAbsolutePath(); - - for (File option: possibleRemovables) { - // Returned paths may be null if a storage device is unavailable. - if (null == option) { - Log.w(LOGTAG,"path was a null option :-/"); continue; } - String optionPath = option.getAbsolutePath(); - if(optionPath.contains(primaryExternal)) { - Log.v(LOGTAG, "did get file path - but is same as primary storage ("+ primaryExternal +")"); - continue; - } - - return option.toURI().toString(); - } - - // TODO: do some manual probing of possible directories (/storage/sdcard1 and similar) - Log.i(LOGTAG, "no secondary storage reported"); - return null; - } - - private void setupCache(Context context) { - // TODO: probably we should do smarter cache management - cacheDir = new File(context.getExternalCacheDir(), "externalFiles"); - if (cacheDir.exists()) { - deleteRecursive(cacheDir); - } - cacheDir.mkdirs(); - } - - private static void deleteRecursive(File file) { - if (file.isDirectory()) { - for (File child : file.listFiles()) - deleteRecursive(child); - } - file.delete(); - } - - public File getCacheDir() { - return cacheDir; - } - - @Override - public IFile getRootDirectory(Context context) { - if(android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - return android4RootDirectory(context); - } else { - return android5RootDirectory(context); - } - } - - private ExternalFile android4RootDirectory(Context context) { - try{ - File f = new File(new URI(rootPathURI)); - return new ExternalFile(this, DocumentFile.fromFile(f), context); - } catch (Exception e) { - //invalid rootPathURI - throw buildRuntimeExceptionForInvalidFileURI(context); - } - } - - private ExternalFile android5RootDirectory(Context context) { - try { - return new ExternalFile(this, - DocumentFile.fromTreeUri(context, Uri.parse(rootPathURI)), - context); - } catch (Exception e) { - //invalid rootPathURI - throw buildRuntimeExceptionForInvalidFileURI(context); - } - } - - private RuntimeException buildRuntimeExceptionForInvalidFileURI(Context context) { - // ToDo: discarding the original exception / catch-all handling is bad style - return new RuntimeException(context.getString(R.string.ext_document_provider_error)); - } - - @Override - public IFile createFromUri(Context context, URI javaURI) { - //TODO: refactor when new DocumentFile API exist - //uri must be of a DocumentFile file, not directory. - Uri androidUri = Uri.parse(javaURI.toString()); - return new ExternalFile(this, - DocumentFile.fromSingleUri(context, androidUri), - context); - } - - @Override - public int getNameResource() { - return R.string.external_sd_file_system; - } - - @Override - public int getId() { - return id; - } - - @Override - public boolean checkProviderAvailability(Context context) { - // too many devices (or I am just unlucky) don't report the mounted state properly, and other - // devices also consider dedicated part of internal storage to be "mounted" so cannot use - // getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && isExternalStorageRemovable() - // but they refer to the primary external storage anyway, so what currently is covered by the - // "LocalDocumentsProvider" - return rootPathURI!=null && ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { - if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI)) { - rootPathURI = preferences.getString(key, ""); - } - } - -} diff --git a/android/source/src/java/org/libreoffice/storage/external/IExternalDocumentProvider.java b/android/source/src/java/org/libreoffice/storage/external/IExternalDocumentProvider.java deleted file mode 100644 index a439417b60cd..000000000000 --- a/android/source/src/java/org/libreoffice/storage/external/IExternalDocumentProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.libreoffice.storage.external; - -import android.content.Context; - -import org.libreoffice.storage.IDocumentProvider; - - -/** - * Interface for external document providers. - */ -public interface IExternalDocumentProvider extends IDocumentProvider { - - /** - * Used to obtain the default directory to display when - * browsing using the internal DirectoryBrowser. - * - * @return a guess of the root file's URI. - * @param context - */ - String guessRootURI(Context context); - -} diff --git a/android/source/src/java/org/libreoffice/storage/external/OTGDocumentsProvider.java b/android/source/src/java/org/libreoffice/storage/external/OTGDocumentsProvider.java deleted file mode 100644 index 4341bc3541e6..000000000000 --- a/android/source/src/java/org/libreoffice/storage/external/OTGDocumentsProvider.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.libreoffice.storage.external; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.preference.PreferenceManager; -import android.util.Log; - -import org.libreoffice.R; -import org.libreoffice.storage.DocumentProviderSettingsActivity; -import org.libreoffice.storage.IFile; -import org.libreoffice.storage.IOUtils; -import org.libreoffice.storage.local.LocalFile; - -import java.io.File; -import java.net.URI; - -/** - * TODO: OTG currently uses LocalFile. Change to an IFile that handles abrupt OTG unmounting - */ -public class OTGDocumentsProvider implements IExternalDocumentProvider, - SharedPreferences.OnSharedPreferenceChangeListener { - - private static final String LOGTAG = OTGDocumentsProvider.class.getSimpleName(); - - private String rootPathURI; - private int id; - - public OTGDocumentsProvider(int id, Context context) { - this.id = id; - setupRootPath(context); - } - - private void setupRootPath(Context context) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - rootPathURI = preferences.getString( - DocumentProviderSettingsActivity.KEY_PREF_OTG_PATH_URI, ""); - } - - @Override - public IFile createFromUri(Context context, URI uri) { - return new LocalFile(uri); - } - - @Override - public int getNameResource() { - return R.string.otg_file_system; - } - - @Override - public int getId() { - return id; - } - - @Override - public IFile getRootDirectory(Context context) { - // TODO: handle this with more fine-grained exceptions - if(rootPathURI.equals("")) { - Log.e(LOGTAG, "rootPathURI is empty"); - throw new RuntimeException(context.getString(R.string.ext_document_provider_error)); - } - - File f = IOUtils.getFileFromURIString(rootPathURI); - if(IOUtils.isInvalidFile(f)) { - Log.e(LOGTAG, "rootPathURI is invalid - missing device?"); - throw new RuntimeException(context.getString(R.string.otg_missing_error)); - } - - return new LocalFile(f); - } - - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_OTG_PATH_URI)) { - rootPathURI = sharedPreferences.getString(key, ""); - } - } - - @Override - public String guessRootURI(Context context) { - return ""; - } - - @Override - public boolean checkProviderAvailability(Context context) { - // check if system supports USB Host - return rootPathURI.length()>0 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_HOST); - } -} diff --git a/android/source/src/java/org/libreoffice/storage/local/LocalDocumentsDirectoryProvider.java b/android/source/src/java/org/libreoffice/storage/local/LocalDocumentsDirectoryProvider.java deleted file mode 100644 index 15522e93a45e..000000000000 --- a/android/source/src/java/org/libreoffice/storage/local/LocalDocumentsDirectoryProvider.java +++ /dev/null @@ -1,73 +0,0 @@ -/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.libreoffice.storage.local; - -import java.io.File; - -import org.libreoffice.storage.IFile; -import org.libreoffice.R; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Environment; -import android.support.v4.content.ContextCompat; -import android.util.Log; - -/** - * A convenience IDocumentProvider to browse the /sdcard/Documents directory. - * - * Extends LocalDocumentsProvider to overwrite getRootDirectory and set it to - * /sdcard/Documents. Most documents will probably be stored there so there is - * no need for the user to browse the filesystem from the root every time. - */ -public class LocalDocumentsDirectoryProvider extends LocalDocumentsProvider { - - public LocalDocumentsDirectoryProvider(int id) { - super(id); - } - - private static File getDocumentsDir() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - // DIRECTORY_DOCUMENTS is 19 or later only - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); - } else { - return new File(Environment.getExternalStorageDirectory() + "/Documents"); - } - } - - @Override - public IFile getRootDirectory(Context context) { - File documentsDirectory = getDocumentsDir(); - if (!documentsDirectory.exists()) { - if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - if(!documentsDirectory.mkdirs()) { - // fallback to the toplevel dir - might be due to the dir not mounted/used as USB-Mass-Storage or similar - // TODO: handle unavailability of the storage/failure of the mkdir properly - Log.e("LocalDocumentsProvider", "not sure how we ended up here - if we have read permissions to use it in the first place, we also should have the write-permissions.."); - documentsDirectory = Environment.getExternalStorageDirectory(); - } - } - } - return new LocalFile(documentsDirectory); - } - - @Override - public int getNameResource() { - return R.string.local_documents; - } - - @Override - public boolean checkProviderAvailability(Context context) { - File documentsDirectory = getDocumentsDir(); - return documentsDirectory.exists() && ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; - } -} diff --git a/android/source/src/java/org/libreoffice/storage/local/LocalDocumentsProvider.java b/android/source/src/java/org/libreoffice/storage/local/LocalDocumentsProvider.java deleted file mode 100644 index 1a10fad424db..000000000000 --- a/android/source/src/java/org/libreoffice/storage/local/LocalDocumentsProvider.java +++ /dev/null @@ -1,60 +0,0 @@ -/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.libreoffice.storage.local; - -import java.net.URI; - -import org.libreoffice.storage.IDocumentProvider; -import org.libreoffice.storage.IFile; - -import org.libreoffice.R; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Environment; -import android.support.v4.content.ContextCompat; - -/** - * Implementation of IDocumentProvider for the local file system. - */ -public class LocalDocumentsProvider implements IDocumentProvider { - - private int id; - - public LocalDocumentsProvider(int id) { - this.id = id; - } - - @Override - public IFile getRootDirectory(Context context) { - return new LocalFile(Environment.getExternalStorageDirectory()); - } - - @Override - public IFile createFromUri(Context context, URI uri) { - return new LocalFile(uri); - } - - @Override - public int getNameResource() { - return R.string.local_file_system; - } - - @Override - public int getId() { - return id; - } - - @Override - public boolean checkProviderAvailability(Context context) { - return ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; - } -} diff --git a/android/source/src/java/org/libreoffice/storage/local/LocalFile.java b/android/source/src/java/org/libreoffice/storage/local/LocalFile.java deleted file mode 100644 index 4ff5bbf119f4..000000000000 --- a/android/source/src/java/org/libreoffice/storage/local/LocalFile.java +++ /dev/null @@ -1,103 +0,0 @@ -/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.libreoffice.storage.local; - -import android.content.Context; - -import java.io.File; -import java.io.FileFilter; -import java.net.URI; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.libreoffice.storage.IFile; - -/** - * Implementation of IFile for the local file system. - */ -public class LocalFile implements IFile { - - private File file; - - public LocalFile(File file) { - this.file = file; - } - - public LocalFile(URI uri) { - this.file = new File(uri); - } - - public URI getUri() { - return file.toURI(); - } - - public String getName() { - return file.getName(); - } - - @Override - public boolean isDirectory() { - return file.isDirectory(); - } - - @Override - public long getSize() { - return file.length(); - } - - @Override - public Date getLastModified() { - return new Date(file.lastModified()); - } - - @Override - public List<IFile> listFiles() { - List<IFile> children = new ArrayList<IFile>(); - for (File child : file.listFiles()) { - children.add(new LocalFile(child)); - } - return children; - } - - @Override - public List<IFile> listFiles(FileFilter filter) { - List<IFile> children = new ArrayList<IFile>(); - for (File child : file.listFiles(filter)) { - children.add(new LocalFile(child)); - } - return children; - } - - @Override - public IFile getParent(Context context) { - return new LocalFile(file.getParentFile()); - } - - @Override - public File getDocument() { - return file; - } - - @Override - public boolean equals(Object object) { - if (this == object) - return true; - if (!(object instanceof LocalFile)) - return false; - LocalFile file = (LocalFile) object; - return file.getUri().equals(getUri()); - } - - @Override - public void saveDocument(File file) { - // do nothing; file is local - } -} diff --git a/android/source/src/java/org/libreoffice/storage/owncloud/OwnCloudFile.java b/android/source/src/java/org/libreoffice/storage/owncloud/OwnCloudFile.java deleted file mode 100644 index fa74a54b08e2..000000000000 --- a/android/source/src/java/org/libreoffice/storage/owncloud/OwnCloudFile.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.libreoffice.storage.owncloud; - -import android.content.Context; - -import java.io.File; -import java.io.FileFilter; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.libreoffice.storage.IFile; - -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.files.ChunkedUploadRemoteFileOperation; -import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation; -import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation; -import com.owncloud.android.lib.resources.files.RemoteFile; -import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation; - -/** - * Implementation of IFile for ownCloud servers. - */ -public class OwnCloudFile implements IFile { - - private OwnCloudProvider provider; - private RemoteFile file; - - private String name; - private String parentPath; - - protected OwnCloudFile(OwnCloudProvider provider, RemoteFile file) { - this.provider = provider; - this.file = file; - - // get name and parent from path - File localFile = new File(file.getRemotePath()); - this.name = localFile.getName(); - this.parentPath = localFile.getParent(); - } - - @Override - public URI getUri(){ - - try{ - return URI.create(URLEncoder.encode(file.getRemotePath(),"UTF-8")); - }catch(UnsupportedEncodingException e){ - e.printStackTrace(); - } - - return null; - } - - @Override - public String getName() { - return name; - } - - @Override - public boolean isDirectory() { - return file.getMimeType().equals("DIR"); - } - - @Override - public long getSize() { - return file.getLength(); - } - - @Override - public Date getLastModified() { - return new Date(file.getModifiedTimestamp()); - } - - @Override - public List<IFile> listFiles() { - List<IFile> children = new ArrayList<IFile>(); - if (isDirectory()) { - ReadRemoteFolderOperation refreshOperation = new ReadRemoteFolderOperation( - file.getRemotePath()); - RemoteOperationResult result = refreshOperation.execute(provider - .getClient()); - if (!result.isSuccess()) { - throw provider.buildRuntimeExceptionForResultCode(result.getCode()); - } - for (Object obj : result.getData()) { - RemoteFile child = (RemoteFile) obj; - if (!child.getRemotePath().equals(file.getRemotePath())) - children.add(new OwnCloudFile(provider, child)); - } - } - return children; - } - - @Override - public List<IFile> listFiles(FileFilter filter) { - List<IFile> children = new ArrayList<IFile>(); - if (isDirectory()) { - ReadRemoteFolderOperation refreshOperation = new ReadRemoteFolderOperation( - file.getRemotePath()); - RemoteOperationResult result = refreshOperation.execute(provider - .getClient()); - if (!result.isSuccess()) { - throw provider.buildRuntimeExceptionForResultCode(result.getCode()); - } - - for (Object obj : result.getData()) { - RemoteFile child = (RemoteFile) obj; - if (!child.getRemotePath().equals(file.getRemotePath())){ - OwnCloudFile ownCloudFile = new OwnCloudFile(provider, child); - if(!ownCloudFile.isDirectory()){ - File f = new File(provider.getCacheDir().getAbsolutePath(), - ownCloudFile.getName()); - if(filter.accept(f)) - children.add(ownCloudFile); - f.delete(); - }else{ - children.add(ownCloudFile); - } - } - } - } - return children; - } - - @Override - public IFile getParent(Context context) { - if (parentPath == null) - // this is the root node - return null; - - return provider.createFromUri(context, URI.create(parentPath)); - } - - @Override - public File getDocument() { - if (isDirectory()) { - return null; - } - File downFolder = provider.getCacheDir(); - DownloadRemoteFileOperation operation = new DownloadRemoteFileOperation( - file.getRemotePath(), downFolder.getAbsolutePath()); - RemoteOperationResult result = operation.execute(provider.getClient()); - if (!result.isSuccess()) { - throw provider.buildRuntimeExceptionForResultCode(result.getCode()); - } - return new File(downFolder.getAbsolutePath() + file.getRemotePath()); - } - - @Override - public boolean equals(Object object) { - if (this == object) - return true; - if (!(object instanceof OwnCloudFile)) - return false; - OwnCloudFile file = (OwnCloudFile) object; - return file.getUri().equals(getUri()); - } - - @Override - public void saveDocument(File newFile) { - UploadRemoteFileOperation uploadOperation; - if (newFile.length() > ChunkedUploadRemoteFileOperation.CHUNK_SIZE) { - uploadOperation = new ChunkedUploadRemoteFileOperation( - newFile.getPath(), file.getRemotePath(), file.getMimeType()); - } else { - uploadOperation = new UploadRemoteFileOperation(newFile.getPath(), - file.getRemotePath(), file.getMimeType()); - } - - RemoteOperationResult result = uploadOperation.execute(provider - .getClient()); - if (!result.isSuccess()) { - throw provider.buildRuntimeExceptionForResultCode(result.getCode()); - } - } -} diff --git a/android/source/src/java/org/libreoffice/storage/owncloud/OwnCloudProvider.java b/android/source/src/java/org/libreoffice/storage/owncloud/OwnCloudProvider.java deleted file mode 100644 index 0852ab617660..000000000000 --- a/android/source/src/java/org/libreoffice/storage/owncloud/OwnCloudProvider.java +++ /dev/null @@ -1,192 +0,0 @@ -package org.libreoffice.storage.owncloud; - -import java.io.File; -import java.net.URI; - -import org.libreoffice.R; -import org.libreoffice.storage.DocumentProviderSettingsActivity; -import org.libreoffice.storage.IDocumentProvider; -import org.libreoffice.storage.IFile; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.net.Uri; -import android.preference.PreferenceManager; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.lib.resources.files.FileUtils; -import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; -import com.owncloud.android.lib.resources.files.RemoteFile; - - -/** - * Implementation of IDocumentProvider for ownCloud servers. - */ -public class OwnCloudProvider implements IDocumentProvider, - OnSharedPreferenceChangeListener { - - private int id; - - private Context context; - private OwnCloudClient client; - private File cacheDir; - - private String serverUrl; - private String userName; - private String password; - private RemoteOperationResult result; - - public OwnCloudProvider(int id, Context context) { - this.id = id; - this.context = context; - - // read preferences - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - serverUrl = preferences.getString( - DocumentProviderSettingsActivity.KEY_PREF_OWNCLOUD_SERVER, ""); - userName = preferences.getString( - DocumentProviderSettingsActivity.KEY_PREF_OWNCLOUD_USER_NAME, ""); - password = preferences.getString( - DocumentProviderSettingsActivity.KEY_PREF_OWNCLOUD_PASSWORD, ""); - - setupClient(); - - // make sure cache directory exists, and clear it - // TODO: probably we should do smarter cache management - cacheDir = new File(context.getCacheDir(), "ownCloud"); - if (cacheDir.exists()) { - deleteRecursive(cacheDir); - } - cacheDir.mkdirs(); - } - - private void setupClient() { - Uri serverUri = Uri.parse(serverUrl); - client = OwnCloudClientFactory.createOwnCloudClient(serverUri, context, - true); - client.setCredentials(OwnCloudCredentialsFactory.newBasicCredentials( - userName, password)); - } - - @Override - public IFile getRootDirectory(Context context) { - return createFromUri(context, URI.create(FileUtils.PATH_SEPARATOR)); - } - - @Override - public IFile createFromUri(Context context, URI uri) { - if(serverUrl != "" || userName != "" || password != ""){ - ReadRemoteFileOperation refreshOperation = new ReadRemoteFileOperation( - uri.getPath()); - this.result = refreshOperation.execute(client); - if (!result.isSuccess()) { - throw buildRuntimeExceptionForResultCode(result.getCode()); - } - if (result.getData().size() > 0) { - return new OwnCloudFile(this, (RemoteFile) result.getData().get(0)); - } - } else { - throw buildRuntimeExceptionForResultCode(ResultCode.WRONG_CONNECTION); - } - - return null; - } - - @Override - public int getNameResource() { - return R.string.owncloud; - } - - /** - * Used by OwnCloudFiles to get a configured client to run their own - * operations. - * - * @return configured OwnCloudClient. - */ - protected OwnCloudClient getClient() { - return client; - } - - /** - * Used by OwnCloudFiles to get the cache directory they should download - * files to. - * - * @return cache directory. - */ - protected File getCacheDir() { - return cacheDir; - } - - /** - * Build the proper RuntimeException for some error result. - * - * @param code Result code got from some RemoteOperationResult. - * @return exception with the proper internationalized error message. - */ - protected RuntimeException buildRuntimeExceptionForResultCode(ResultCode code) { - int errorMessage; - switch (code) { - case WRONG_CONNECTION: // SocketException - case FILE_NOT_FOUND: // HTTP 404 - errorMessage = R.string.owncloud_wrong_connection; - break; - case UNAUTHORIZED: // wrong user/pass - errorMessage = R.string.owncloud_unauthorized; - break; - default: - errorMessage = R.string.owncloud_unspecified_error; - break; - } - return new RuntimeException(context.getString(errorMessage)); - } - - /** - * Deletes files and recursively deletes directories. - * - * @param file - * File or directory to be deleted. - */ - private static void deleteRecursive(File file) { - if (file.isDirectory()) { - for (File child : file.listFiles()) - deleteRecursive(child); - } - file.delete(); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences preferences, - String key) { - boolean changed = false; - if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_OWNCLOUD_SERVER)) { - serverUrl = preferences.getString(key, ""); - changed = true; - } - else if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_OWNCLOUD_USER_NAME)) { - userName = preferences.getString(key, ""); - changed = true; - } - else if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_OWNCLOUD_PASSWORD)) { - password = preferences.getString(key, ""); - changed = true; - } - - if (changed) - setupClient(); - } - - @Override - public int getId() { - return id; - } - - @Override - public boolean checkProviderAvailability(Context context) { - return client != null; - } -} diff --git a/android/source/src/java/org/libreoffice/ui/FileUtilities.java b/android/source/src/java/org/libreoffice/ui/FileUtilities.java index 7a58486004cd..7fc8c3c84eb1 100644 --- a/android/source/src/java/org/libreoffice/ui/FileUtilities.java +++ b/android/source/src/java/org/libreoffice/ui/FileUtilities.java @@ -8,25 +8,18 @@ */ package org.libreoffice.ui; -import org.libreoffice.storage.IFile; - -import java.io.File; -import java.io.FileFilter; -import java.io.FilenameFilter; -import java.text.Collator; import java.util.Map; -import java.util.Collections; -import java.util.List; import java.util.HashMap; -import java.util.Comparator; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.provider.OpenableColumns; import android.util.Log; -import android.webkit.MimeTypeMap; public class FileUtilities { - private static String LOGTAG = FileUtilities.class.getSimpleName(); - - static final int ALL = -1; + private static final String LOGTAG = FileUtilities.class.getSimpleName(); // These have to be in sync with the file_view_modes resource. static final int DOC = 0; @@ -36,26 +29,16 @@ public class FileUtilities { static final int UNKNOWN = 10; - static final int SORT_AZ = 0; - static final int SORT_ZA = 1; - /** Oldest Files First*/ - static final int SORT_OLDEST = 2; - /** Newest Files First*/ - static final int SORT_NEWEST = 3; - /** Largest Files First */ - static final int SORT_LARGEST = 4; - /** Smallest Files First */ - static final int SORT_SMALLEST = 5; - - public static final String DEFAULT_WRITER_EXTENSION = ".odt"; - public static final String DEFAULT_IMPRESS_EXTENSION = ".odp"; - public static final String DEFAULT_SPREADSHEET_EXTENSION = ".ods"; - public static final String DEFAULT_DRAWING_EXTENSION = ".odg"; + public static final String MIMETYPE_OPENDOCUMENT_TEXT = "application/vnd.oasis.opendocument.text"; + public static final String MIMETYPE_OPENDOCUMENT_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet"; + public static final String MIMETYPE_OPENDOCUMENT_PRESENTATION = "application/vnd.oasis.opendocument.presentation"; + public static final String MIMETYPE_OPENDOCUMENT_GRAPHICS = "application/vnd.oasis.opendocument.graphics"; + public static final String MIMETYPE_PDF = "application/pdf"; private static final Map<String, Integer> mExtnMap = new HashMap<String, Integer>(); - private static final Map<String, String> extensionToMimeTypeMap = new HashMap<String, String>(); static { // Please keep this in sync with AndroidManifest.xml + // and 'SUPPORTED_MIME_TYPES' in LibreOfficeUIActivity.java // ODF mExtnMap.put(".odt", DOC); @@ -80,7 +63,7 @@ public class FileUtilities { mExtnMap.put(".vsdx", DRAWING); mExtnMap.put(".pub", DRAWING); mExtnMap.put(".ppt", IMPRESS); - // mExtnMap.put(".pps", IMPRESS); + mExtnMap.put(".pps", IMPRESS); mExtnMap.put(".xls", CALC); // MS templates @@ -91,7 +74,7 @@ public class FileUtilities { // OOXML mExtnMap.put(".docx", DOC); mExtnMap.put(".pptx", IMPRESS); - // mExtnMap.put(".ppsx", IMPRESS); + mExtnMap.put(".ppsx", IMPRESS); mExtnMap.put(".xlsx", CALC); // OOXML templates @@ -109,22 +92,6 @@ public class FileUtilities { mExtnMap.put(".svm", DRAWING); mExtnMap.put(".wmf", DRAWING); mExtnMap.put(".svg", DRAWING); - - // Some basic MIME types - // Android's MimeTypeMap lacks some types that we need - extensionToMimeTypeMap.put("odb", "application/vnd.oasis.opendocument.database"); - extensionToMimeTypeMap.put("odf", "application/vnd.oasis.opendocument.formula"); - extensionToMimeTypeMap.put("odg", "application/vnd.oasis.opendocument.graphics"); - extensionToMimeTypeMap.put("otg", "application/vnd.oasis.opendocument.graphics-template"); - extensionToMimeTypeMap.put("odi", "application/vnd.oasis.opendocument.image"); - extensionToMimeTypeMap.put("odp", "application/vnd.oasis.opendocument.presentation"); - extensionToMimeTypeMap.put("otp", "application/vnd.oasis.opendocument.presentation-template"); - extensionToMimeTypeMap.put("ods", "application/vnd.oasis.opendocument.spreadsheet"); - extensionToMimeTypeMap.put("ots", "application/vnd.oasis.opendocument.spreadsheet-template"); - extensionToMimeTypeMap.put("odt", "application/vnd.oasis.opendocument.text"); - extensionToMimeTypeMap.put("odm", "application/vnd.oasis.opendocument.text-master"); - extensionToMimeTypeMap.put("ott", "application/vnd.oasis.opendocument.text-template"); - extensionToMimeTypeMap.put("oth", "application/vnd.oasis.opendocument.text-web"); } public static String getExtension(String filename) { @@ -149,129 +116,42 @@ public class FileUtilities { return type; } - static String getMimeType(String filename) { - String extension = MimeTypeMap.getFileExtensionFromUrl(filename); - String mime = extensionToMimeTypeMap.get(extension); - if (mime == null) { - //fallback to Android's MimeTypeMap - mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension( - extension); - } - return mime; + /** + * Returns whether the passed MIME type is one for a document template. + */ + public static boolean isTemplateMimeType(final String mimeType) { + // this works for ODF and OOXML template MIME types + return mimeType != null && mimeType.endsWith("template"); } - // Filter by mode, and/or in future by filename/wildcard - private static boolean doAccept(String filename, int byMode, String byFilename) { - Log.d(LOGTAG, "doAccept : " + filename + " mode " + byMode + " byFilename " + byFilename); - if (filename == null) - return false; - - // check extension - if (byMode != ALL) { - if (mExtnMap.get (getExtension (filename)) != byMode) - return false; - } - if (!byFilename.equals("")) { - // FIXME return false on a non-match - } - return true; + public static String stripExtensionFromFileName(final String fileName) + { + return fileName.split("\\.[A-Za-z0-9]*$")[0]; } - static FileFilter getFileFilter(final int mode) { - return new FileFilter() { - public boolean accept(File pathname) { - if (pathname.isDirectory()) - return true; - if (lookupExtension(pathname.getName()) == UNKNOWN) - return false; - return doAccept(pathname.getName(), mode, ""); + /** + * Tries to retrieve the display (which should be the document name) + * for the given URI using the given resolver. + */ + public static String retrieveDisplayNameForDocumentUri(ContentResolver resolver, Uri docUri) { + String displayName = ""; + // try to retrieve original file name + Cursor cursor = null; + try { + String[] columns = {OpenableColumns.DISPLAY_NAME}; + cursor = resolver.query(docUri, columns, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + displayName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); } - }; - } - - static FilenameFilter getFilenameFilter(final int mode) { - return new FilenameFilter() { - public boolean accept(File dir, String filename) { - if (new File(dir , filename).isDirectory()) - return true; - return doAccept(filename, mode, ""); + } catch (SecurityException e) { + // thrown e.g. when Uri has become invalid, e.g. corresponding file has been deleted + Log.i(LOGTAG, "SecurityException when trying to receive display name for Uri " + docUri); + } finally { + if (cursor != null) { + cursor.close(); } - }; - } - - static void sortFiles(List<IFile> files, int sortMode) { - if (files == null) - return; - // Compare filenames in the default locale - final Collator mCollator = Collator.getInstance(); - switch (sortMode) { - case SORT_AZ: - Collections.sort(files , new Comparator<IFile>() { - public int compare(IFile lhs, IFile rhs) { - return mCollator.compare(lhs.getName(), rhs.getName()); - } - }); - break; - case SORT_ZA: - Collections.sort(files , new Comparator<IFile>() { - public int compare(IFile lhs, IFile rhs) { - return mCollator.compare(rhs.getName(), lhs.getName()); - } - }); - break; - case SORT_OLDEST: - Collections.sort(files , new Comparator<IFile>() { - public int compare(IFile lhs, IFile rhs) { - return lhs.getLastModified().compareTo(rhs.getLastModified()); - } - }); - break; - case SORT_NEWEST: - Collections.sort(files , new Comparator<IFile>() { - public int compare(IFile lhs, IFile rhs) { - return rhs.getLastModified().compareTo(lhs.getLastModified()); - } - }); - break; - case SORT_LARGEST: - Collections.sort(files , new Comparator<IFile>() { - public int compare(IFile lhs, IFile rhs) { - return Long.valueOf(rhs.getSize()).compareTo(lhs.getSize()); - } - }); - break; - case SORT_SMALLEST: - Collections.sort(files , new Comparator<IFile>() { - public int compare(IFile lhs, IFile rhs) { - return Long.valueOf(lhs.getSize()).compareTo(rhs.getSize()); - } - }); - break; - default: - Log.e(LOGTAG, "uncatched sortMode: " + sortMode); } - } - - static boolean isHidden(File file) { - return file.getName().startsWith("."); - } - - static boolean isThumbnail(File file) { - return isHidden(file) && file.getName().endsWith(".png"); - } - - static boolean hasThumbnail(File file) { - String filename = file.getName(); - if (lookupExtension(filename) == DOC) // only do this for docs for now - { - // Will need another method to check if Thumb is up-to-date - or extend this one? - return new File(file.getParent(), getThumbnailName(file)).isFile(); - } - return true; - } - - static String getThumbnailName(File file) { - return "." + file.getName().split("[.]")[0] + ".png" ; + return displayName; } } diff --git a/android/source/src/java/org/libreoffice/ui/FolderIconView.java b/android/source/src/java/org/libreoffice/ui/FolderIconView.java deleted file mode 100644 index cde6cd27af4c..000000000000 --- a/android/source/src/java/org/libreoffice/ui/FolderIconView.java +++ /dev/null @@ -1,204 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.libreoffice.ui; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.graphics.Color; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; - -import java.io.File; -import java.util.Stack; - -public class FolderIconView extends View{ - private String LOGTAG = "FolderIconView"; - - private Paint mPaintBlack; - private Paint mPaintGray; - private Paint mPaintShadow; - - private File dir; - - public FolderIconView(Context context) { - super(context); - initialisePaints(); - } - public FolderIconView(Context context, AttributeSet attrs) { - super(context, attrs); - initialisePaints(); - } - public FolderIconView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initialisePaints(); - } - - private void initialisePaints() { - mPaintBlack = new Paint(); - mPaintBlack.setColor(Color.DKGRAY);//Can also use parseColor(String "#aarrggbb") - mPaintBlack.setAntiAlias(true); - - mPaintGray = new Paint(); - mPaintGray.setColor(Color.GRAY);//Can also use parseColor(String "#aarrggbb") - mPaintGray.setAntiAlias(true); - - mPaintShadow = new Paint(); - mPaintShadow.setColor(Color.parseColor("#88888888")); - mPaintShadow.setAntiAlias(true); - } - - public void setDir(File dir) { - this.dir = dir; - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - Log.d(LOGTAG, "onDraw"); - //float width = (float)canvas.getWidth(); - //float height = (float)canvas.getHeight(); - float width = (float) this.getWidth(); - float height = (float) this.getHeight(); - float centerX = width*0.5f;// centered on horz axis - float centerY = height*0.5f; - float outerRadius = 0.8f*0.5f*width; - float innerRadius = 0.7f*0.5f*width; - float thumbHeight = outerRadius*1.25f; - float thumbWidth = thumbHeight*(float)(1/Math.sqrt(2)); - float DZx = 0.2f*outerRadius; - float DZy = 0.2f*outerRadius; - //Bitmap blankPage = BitmapFactory.decodeResource(getResources(), R.drawable.page); - Log.i(LOGTAG, Float.toString(width) + "x" + Float.toString(height)); - canvas.drawCircle(centerX, centerY, outerRadius, mPaintGray); - canvas.drawCircle(centerX, centerY, innerRadius, mPaintBlack); - //Either get thumbs from directory or use generic page images - //For now just get the first 4 thumbs -> add some checks later - if (dir == null) - return;//TODO - File[] contents = dir.listFiles();//TODO consider filtering thumbs to match grid. - if (contents == null) - // dir is not a directory, - // or user does not have permissions to read it - return; - Stack<Bitmap> thumbs = new Stack<Bitmap>(); - BitmapFactory factory = new BitmapFactory(); - for (File file : contents) { - if (!FileUtilities.isThumbnail(file)) - continue; - thumbs.push(BitmapFactory.decodeFile(file.getAbsolutePath()));//TODO switch to push for semantics - if (thumbs.size() > 3) - break; - } - /*while(thumbs.size() < 4) {// padd out with blanks? - thumbs.push(blankPage); - }*/ - Log.i(LOGTAG, Integer.toString(thumbs.size())); - //should handle empty folders better - // options: - // don't show? - // show generic LO icons for writer etc - // Show a generic blank page icon - if (thumbs.isEmpty()) - return; - /*float left = centerX ;//+ 0.25f*outerRadius; - float top = centerY - 0.5f*outerRadius; - float right = left + thumbs.get(0).getWidth()*0.4f; - float bottom = top + thumbs.get(0).getHeight()*0.4f; - RectF dest = new RectF(left, top, right, bottom); - RectF shadowBox = new RectF(dest); - shadowBox.inset(-1, -1); - int size = thumbs.size(); - for (int i = 1; i <= size; i++) { - canvas.drawRect(shadowBox, mPaintShadow); - canvas.drawBitmap(thumbs.pop(), null, dest, null); - dest.offset(-outerRadius*0.2f, outerRadius*0.1f); - shadowBox.offset(-outerRadius*0.2f, outerRadius*0.1f); - }*/ - float left; - float top; - float right; - float bottom; - RectF dest; - RectF shadowBox; - int size; - switch(thumbs.size()) { - case 0: - break; - case 1: - left = centerX - 0.5f*thumbWidth; - top = centerY - 0.5f*thumbHeight; - right = left + thumbWidth; - bottom = top + thumbHeight; - dest = new RectF(left, top, right, bottom); - shadowBox = new RectF(dest); - shadowBox.inset(-1, -1); - canvas.drawRect(shadowBox, mPaintShadow); - canvas.drawBitmap(thumbs.pop(), null, dest, null); - break; - case 2: - left = centerX - 0.5f*thumbWidth + 0.5f*DZx; - top = centerY - 0.5f*thumbHeight - 0.5f*DZy; - right = left + thumbWidth; - bottom = top + thumbHeight; - dest = new RectF(left, top, right, bottom); - shadowBox = new RectF(dest); - shadowBox.inset(-1, -1); - size = thumbs.size(); - for (int i = 1; i <= size; i++) { - canvas.drawRect(shadowBox, mPaintShadow); - canvas.drawBitmap(thumbs.pop(), null, dest, null); - dest.offset(-DZx, DZy); - shadowBox.offset(-DZx, DZy); - } - break; - case 3: - left = centerX - 0.5f*thumbWidth + DZx; - top = centerY - 0.5f*thumbHeight - DZy; - right = left + thumbWidth; - bottom = top + thumbHeight; - dest = new RectF(left, top, right, bottom); - shadowBox = new RectF(dest); - shadowBox.inset(-1, -1); - size = thumbs.size(); - for (int i = 1; i <= size; i++) { - canvas.drawRect(shadowBox, mPaintShadow); - canvas.drawBitmap(thumbs.pop(), null, dest, null); - dest.offset(-DZx, DZy); - shadowBox.offset(-DZx, DZy); - } - break; - case 4: - left = centerX - 0.5f*thumbWidth + 1.5f*DZx; - top = centerY - 0.5f*thumbHeight - 1.5f*DZy; - right = left + thumbWidth; - bottom = top + thumbHeight; - dest = new RectF(left, top, right, bottom); - shadowBox = new RectF(dest); - shadowBox.inset(-1, -1); - size = thumbs.size(); - for (int i = 1; i <= size; i++) { - canvas.drawRect(shadowBox, mPaintShadow); - canvas.drawBitmap(thumbs.pop(), null, dest, null); - dest.offset(-DZx, DZy); - shadowBox.offset(-DZx, DZy); - } - break; - default: - break; - } - } - -} - -/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java b/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java index a9d797c4bf28..bc5203d9c6eb 100644 --- a/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java +++ b/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java @@ -10,134 +10,119 @@ package org.libreoffice.ui; import android.Manifest; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.BroadcastReceiver; +import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.drawable.Icon; -import android.hardware.usb.UsbManager; import android.net.Uri; -import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.NavigationView; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.text.InputType; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.Toolbar; +import android.text.TextUtils; import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.OvershootInterpolator; -import android.widget.EditText; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import org.libreoffice.AboutDialogFragment; -import org.libreoffice.LOKitShell; +import org.libreoffice.BuildConfig; import org.libreoffice.LibreOfficeMainActivity; import org.libreoffice.LocaleHelper; import org.libreoffice.R; import org.libreoffice.SettingsActivity; import org.libreoffice.SettingsListenerModel; -import org.libreoffice.storage.DocumentProviderFactory; -import org.libreoffice.storage.DocumentProviderSettingsActivity; -import org.libreoffice.storage.IDocumentProvider; -import org.libreoffice.storage.IFile; -import java.io.File; -import java.io.FileFilter; -import java.io.FilenameFilter; -import java.net.URI; -import java.net.URISyntaxException; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class LibreOfficeUIActivity extends AppCompatActivity implements SettingsListenerModel.OnSettingsPreferenceChangedListener, View.OnClickListener{ - private String LOGTAG = LibreOfficeUIActivity.class.getSimpleName(); - private SharedPreferences prefs; - private int filterMode = FileUtilities.ALL; - private int viewMode; - private int sortMode; - private boolean showHiddenFiles; - private String displayLanguage; - - // dynamic permissions IDs - private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0; + public enum DocumentType { + WRITER, + CALC, + IMPRESS, + DRAW, + INVALID + } - FileFilter fileFilter; - FilenameFilter filenameFilter; - private List<IFile> filePaths = new ArrayList<IFile>(); - private DocumentProviderFactory documentProviderFactory; - private IDocumentProvider documentProvider; - private IFile homeDirectory; - private IFile currentDirectory; - private int currentlySelectedFile; + private static final String LOGTAG = LibreOfficeUIActivity.class.getSimpleName(); - private static final String CURRENT_DIRECTORY_KEY = "CURRENT_DIRECTORY"; - private static final String DOC_PROVIDER_KEY = "CURRENT_DOCUMENT_PROVIDER"; - private static final String FILTER_MODE_KEY = "FILTER_MODE"; - public static final String EXPLORER_VIEW_TYPE_KEY = "EXPLORER_VIEW_TYPE"; public static final String EXPLORER_PREFS_KEY = "EXPLORER_PREFS"; - public static final String SORT_MODE_KEY = "SORT_MODE"; - private static final String RECENT_DOCUMENTS_KEY = "RECENT_DOCUMENTS"; - private static final String ENABLE_SHOW_HIDDEN_FILES_KEY = "ENABLE_SHOW_HIDDEN_FILES"; + private static final String RECENT_DOCUMENTS_KEY = "RECENT_DOCUMENT_URIS"; + // delimiter used for storing multiple URIs in a string + private static final String RECENT_DOCUMENTS_DELIMITER = " "; private static final String DISPLAY_LANGUAGE = "DISPLAY_LANGUAGE"; - public static final String NEW_FILE_PATH_KEY = "NEW_FILE_PATH_KEY"; public static final String NEW_DOC_TYPE_KEY = "NEW_DOC_TYPE_KEY"; public static final String NEW_WRITER_STRING_KEY = "private:factory/swriter"; public static final String NEW_IMPRESS_STRING_KEY = "private:factory/simpress"; public static final String NEW_CALC_STRING_KEY = "private:factory/scalc"; public static final String NEW_DRAW_STRING_KEY = "private:factory/sdraw"; - public static final int GRID_VIEW = 0; - public static final int LIST_VIEW = 1; + // keep this in sync with 'AndroidManifext.xml' + private static final String[] SUPPORTED_MIME_TYPES = { + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.graphics", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.text-flat-xml", + "application/vnd.oasis.opendocument.graphics-flat-xml", + "application/vnd.oasis.opendocument.presentation-flat-xml", + "application/vnd.oasis.opendocument.spreadsheet-flat-xml", + "application/vnd.oasis.opendocument.text-template", + "application/vnd.oasis.opendocument.spreadsheet-template", + "application/vnd.oasis.opendocument.graphics-template", + "application/vnd.oasis.opendocument.presentation-template", + "application/rtf", + "text/rtf", + "application/msword", + "application/vnd.ms-powerpoint", + "application/vnd.ms-excel", + "application/vnd.visio", + "application/vnd.visio.xml", + "application/x-mspublisher", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + "application/vnd.openxmlformats-officedocument.presentationml.template", + "text/csv", + "text/comma-separated-values", + "application/vnd.ms-works", + "application/vnd.apple.keynote", + "application/x-abiword", + "application/x-pagemaker", + "image/x-emf", + "image/x-svm", + "image/x-wmf", + "image/svg+xml", + }; - private DrawerLayout drawerLayout; - private NavigationView navigationDrawer; - private ActionBar actionBar; - private ActionBarDrawerToggle drawerToggle; - private RecyclerView fileRecyclerView; - private RecyclerView recentRecyclerView; + private static final int REQUEST_CODE_OPEN_FILECHOOSER = 12345; - private boolean canQuit = false; + private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0; private Animation fabOpenAnimation; private Animation fabCloseAnimation; @@ -156,45 +141,49 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // initialize document provider factory - DocumentProviderFactory.initialize(this); - documentProviderFactory = DocumentProviderFactory.getInstance(); - - PreferenceManager.setDefaultValues(this, R.xml.documentprovider_preferences, false); readPreferences(); SettingsListenerModel.getInstance().setListener(this); - // Registering the USB detect broadcast receiver - IntentFilter filter = new IntentFilter(); - filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); - filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); - registerReceiver(mUSBReceiver, filter); - // init UI and populate with contents from the provider - + // init UI createUI(); fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open); fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close); } @Override + protected void onStart() { + super.onStart(); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + Log.i(LOGTAG, "no permission to read external storage - asking for permission"); + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSION_WRITE_EXTERNAL_STORAGE); + } + } + + @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(LocaleHelper.onAttach(newBase)); } public void createUI() { - setContentView(R.layout.activity_document_browser); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - actionBar = getSupportActionBar(); + ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setIcon(R.mipmap.ic_launcher); } editFAB = findViewById(R.id.editFAB); editFAB.setOnClickListener(this); + // allow creating new docs only when experimental editing is enabled + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + final boolean bEditingEnabled = BuildConfig.ALLOW_EDITING && preferences.getBoolean(LibreOfficeMainActivity.ENABLE_EXPERIMENTAL_PREFS_KEY, false); + editFAB.setVisibility(bEditingEnabled ? View.VISIBLE : View.INVISIBLE); + impressFAB = findViewById(R.id.newImpressFAB); impressFAB.setOnClickListener(this); writerFAB = findViewById(R.id.newWriterFAB); @@ -207,110 +196,27 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings impressLayout = findViewById(R.id.impressLayout); calcLayout = findViewById(R.id.calcLayout); drawLayout = findViewById(R.id.drawLayout); + TextView openFileView = findViewById(R.id.open_file_button); + openFileView.setOnClickListener(this); - recentRecyclerView = findViewById(R.id.list_recent); - Set<String> recentFileStrings = prefs.getStringSet(RECENT_DOCUMENTS_KEY, new HashSet<String>()); + RecyclerView recentRecyclerView = findViewById(R.id.list_recent); - final ArrayList<IFile> recentFiles = new ArrayList<IFile>(); + SharedPreferences prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE); + String recentPref = prefs.getString(RECENT_DOCUMENTS_KEY, ""); + String[] recentFileStrings = recentPref.split(RECENT_DOCUMENTS_DELIMITER); + + final List<RecentFile> recentFiles = new ArrayList<>(); for (String recentFileString : recentFileStrings) { - try { - if(documentProvider != null) - recentFiles.add(documentProvider.createFromUri(this, new URI(recentFileString))); - } catch (URISyntaxException e) { - e.printStackTrace(); - } catch (RuntimeException e){ - e.printStackTrace(); + Uri uri = Uri.parse(recentFileString); + String filename = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), uri); + if (!filename.isEmpty()) { + recentFiles.add(new RecentFile(uri, filename)); } } recentRecyclerView.setLayoutManager(new GridLayoutManager(this, 2)); recentRecyclerView.setAdapter(new RecentFilesAdapter(this, recentFiles)); - - fileRecyclerView = findViewById(R.id.file_recycler_view); - //This should be tested because it possibly disables view recycling - fileRecyclerView.setNestedScrollingEnabled(false); - openDirectory(currentDirectory); - registerForContextMenu(fileRecyclerView); - - //Setting up navigation drawer - drawerLayout = findViewById(R.id.drawer_layout); - navigationDrawer = findViewById(R.id.navigation_drawer); - - final ArrayList<CharSequence> providerNames = new ArrayList<CharSequence>( - Arrays.asList(documentProviderFactory.getNames()) - ); - - // Loop through the document providers menu items and check if they are available or not - for (int index = 0; index < providerNames.size(); index++) { - MenuItem item = navigationDrawer.getMenu().getItem(index); - item.setEnabled(documentProviderFactory.getProvider(index).checkProviderAvailability(this)); - } - - navigationDrawer.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - - switch (item.getItemId()) { - case R.id.menu_storage_preferences: { - startActivity(new Intent(LibreOfficeUIActivity.this, DocumentProviderSettingsActivity.class)); - return true; - } - - case R.id.menu_provider_documents: { - switchToDocumentProvider(documentProviderFactory.getProvider(0)); - return true; - } - - case R.id.menu_provider_filesystem: { - switchToDocumentProvider(documentProviderFactory.getProvider(1)); - return true; - } - - case R.id.menu_provider_extsd: { - switchToDocumentProvider(documentProviderFactory.getProvider(2)); - return true; - } - - case R.id.menu_provider_otg: { - switchToDocumentProvider(documentProviderFactory.getProvider(3)); - return true; - } - - case R.id.menu_provider_owncloud: { - switchToDocumentProvider(documentProviderFactory.getProvider(4)); - return true; - } - - default: - return false; - } - - - } - }); - drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, - R.string.document_locations, R.string.close_document_locations) { - - @Override - public void onDrawerOpened(View drawerView) { - super.onDrawerOpened(drawerView); - supportInvalidateOptionsMenu(); - navigationDrawer.requestFocus(); // Make keypad navigation easier - if (isFabMenuOpen) { - collapseFabMenu(); //Collapse FAB Menu when drawer is opened - } - } - - @Override - public void onDrawerClosed(View drawerView) { - super.onDrawerClosed(drawerView); - supportInvalidateOptionsMenu(); - } - }; - drawerToggle.setDrawerIndicatorEnabled(true); - drawerLayout.addDrawerListener(drawerToggle); - drawerToggle.syncState(); } private void expandFabMenu() { @@ -340,658 +246,126 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings } @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - - drawerToggle.syncState(); - } - - private void refreshView() { - // enable home icon as "up" if required - if (currentDirectory != null && homeDirectory != null && !currentDirectory.equals(homeDirectory)) { - drawerToggle.setDrawerIndicatorEnabled(false); - } else { - drawerToggle.setDrawerIndicatorEnabled(true); - } - - FileUtilities.sortFiles(filePaths, sortMode); - // refresh view - fileRecyclerView.setLayoutManager(isViewModeList() ? new LinearLayoutManager(this) : new GridLayoutManager(this, 3)); - fileRecyclerView.setAdapter(new ExplorerItemAdapter(this, filePaths)); - // close drawer if it was open - drawerLayout.closeDrawer(navigationDrawer); + public void onBackPressed() { if (isFabMenuOpen) { collapseFabMenu(); + } else { + super.onBackPressed(); } } @Override - public void onBackPressed() { - if (drawerLayout.isDrawerOpen(navigationDrawer)) { - drawerLayout.closeDrawer(navigationDrawer); - if (isFabMenuOpen) { - collapseFabMenu(); - } - } else if (currentDirectory != null && homeDirectory != null && !currentDirectory.equals(homeDirectory)) { - // navigate upwards in directory hierarchy - openParentDirectory(); - } else if (isFabMenuOpen) { - collapseFabMenu(); - } else { - // only exit if warning has been shown - if (canQuit) { - super.onBackPressed(); - return; - } - - // show warning about leaving the app and set a timer - Toast.makeText(this, R.string.back_again_to_quit, - Toast.LENGTH_SHORT).show(); - canQuit = true; - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - canQuit = false; - } - }, 3000); + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_OPEN_FILECHOOSER && resultCode == RESULT_OK) { + final Uri fileUri = data.getData(); + openDocument(fileUri); } } - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.context_menu, menu); - } + private void showSystemFilePickerAndOpenFile() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_MIME_TYPES, SUPPORTED_MIME_TYPES); - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.context_menu_open: - open(currentlySelectedFile); - return true; - case R.id.context_menu_share: - share(currentlySelectedFile); - return true; - default: - return super.onContextItemSelected(item); + try { + startActivityForResult(intent, REQUEST_CODE_OPEN_FILECHOOSER); + } catch (ActivityNotFoundException e) { + Log.w(LOGTAG, "No activity available that can handle the intent to open a document."); } } - private boolean isViewModeList(){ - return viewMode == LIST_VIEW; - } - - private void switchToDocumentProvider(IDocumentProvider provider) { + public void openDocument(final Uri documentUri) { + // "forward" to LibreOfficeMainActivity to open the file + Intent intent = new Intent(Intent.ACTION_VIEW, documentUri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - new AsyncTask<IDocumentProvider, Void, Void>() { - @Override - protected Void doInBackground(IDocumentProvider... provider) { - // switch document provider: - // these operations may imply network access and must be run in - // a different thread - try { - homeDirectory = provider[0].getRootDirectory(LibreOfficeUIActivity.this); - List<IFile> paths = homeDirectory.listFiles(FileUtilities - .getFileFilter(filterMode)); - filePaths = new ArrayList<IFile>(); - for(IFile file: paths) { - if(showHiddenFiles){ - filePaths.add(file); - } else { - if(!file.getName().startsWith(".")){ - filePaths.add(file); - } - } - } - } - catch (final RuntimeException e) { - final Activity activity = LibreOfficeUIActivity.this; - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, e.getMessage(), - Toast.LENGTH_SHORT).show(); - } - }); - startActivity(new Intent(activity, DocumentProviderSettingsActivity.class)); - Log.e(LOGTAG, "failed to switch document provider "+ e.getMessage(), e.getCause()); - return null; - } - //no exception - documentProvider = provider[0]; - currentDirectory = homeDirectory; - return null; - } + addDocumentToRecents(documentUri); - @Override - protected void onPostExecute(Void result) { - refreshView(); - } - }.execute(provider); + String packageName = getApplicationContext().getPackageName(); + ComponentName componentName = new ComponentName(packageName, + LibreOfficeMainActivity.class.getName()); + intent.setComponent(componentName); + startActivity(intent); } - public void openDirectory(IFile dir) { - if (dir == null) - return; - - //show recent files if in home directory - if (dir.equals(homeDirectory)) { - recentRecyclerView.setVisibility(View.VISIBLE); - findViewById(R.id.header_browser).setVisibility((View.VISIBLE)); - findViewById(R.id.header_recents).setVisibility((View.VISIBLE)); - actionBar.setTitle(R.string.app_name); - findViewById(R.id.text_directory_path).setVisibility(View.GONE); + private void loadNewDocument(DocumentType docType) { + final String newDocumentType; + if (docType == DocumentType.WRITER) { + newDocumentType = NEW_WRITER_STRING_KEY; + } else if (docType == DocumentType.CALC) { + newDocumentType = NEW_CALC_STRING_KEY; + } else if (docType == DocumentType.IMPRESS) { + newDocumentType = NEW_IMPRESS_STRING_KEY; + } else if (docType == DocumentType.DRAW) { + newDocumentType = NEW_DRAW_STRING_KEY; } else { - recentRecyclerView.setVisibility(View.GONE); - findViewById(R.id.header_browser).setVisibility((View.GONE)); - findViewById(R.id.header_recents).setVisibility((View.GONE)); - actionBar.setTitle(dir.getName()); - findViewById(R.id.text_directory_path).setVisibility(View.VISIBLE); - ((TextView)findViewById(R.id.text_directory_path)).setText(getString(R.string.current_dir, - dir.getUri().getPath())); + Log.w(LOGTAG, "invalid document type passed to loadNewDocument method. Ignoring request"); + return; } - new AsyncTask<IFile, Void, Void>() { - @Override - protected Void doInBackground(IFile... dir) { - // get list of files: - // this operation may imply network access and must be run in - // a different thread - currentDirectory = dir[0]; - try { - List<IFile> paths = currentDirectory.listFiles(FileUtilities - .getFileFilter(filterMode)); - filePaths = new ArrayList<IFile>(); - for(IFile file: paths) { - if(showHiddenFiles){ - filePaths.add(file); - } else { - if(!file.getName().startsWith(".")){ - filePaths.add(file); - } - } - } - } - catch (final RuntimeException e) { - final Activity activity = LibreOfficeUIActivity.this; - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, e.getMessage(), - Toast.LENGTH_SHORT).show(); - } - }); - Log.e(LOGTAG, e.getMessage(), e.getCause()); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - refreshView(); - } - }.execute(dir); - } - - public void open(final IFile document) { - addDocumentToRecents(document); - new AsyncTask<IFile, Void, File>() { - @Override - protected File doInBackground(IFile... document) { - // this operation may imply network access and must be run in - // a different thread - try { - return document[0].getDocument(); - } - catch (final RuntimeException e) { - final Activity activity = LibreOfficeUIActivity.this; - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, e.getMessage(), - Toast.LENGTH_SHORT).show(); - } - }); - Log.e(LOGTAG, e.getMessage(), e.getCause()); - return null; - } - } - - @Override - protected void onPostExecute(File file) { - if (file != null) { - Intent i = new Intent(Intent.ACTION_VIEW, Uri.fromFile(file)); - String packageName = getApplicationContext().getPackageName(); - ComponentName componentName = new ComponentName(packageName, - LibreOfficeMainActivity.class.getName()); - i.setComponent(componentName); - - // these extras allow to rebuild the IFile object in LOMainActivity - i.putExtra("org.libreoffice.document_provider_id", - documentProvider.getId()); - i.putExtra("org.libreoffice.document_uri", - document.getUri()); - - startActivity(i); - } - } - }.execute(document); - } - - // Opens an Input dialog to get the name of new file - private void createNewFileInputDialog(final String defaultFileName, final String newDocumentType) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.create_new_document_title); - final EditText input = new EditText(this); - input.setInputType(InputType.TYPE_CLASS_TEXT); - input.setText(defaultFileName); - builder.setView(input); - - builder.setPositiveButton(R.string.action_create, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final String newFilePath = currentDirectory.getUri().getPath() + input.getText().toString(); - loadNewDocument(newDocumentType, newFilePath); - } - }); - - builder.setNegativeButton(R.string.action_cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); - - builder.show(); - } - - private void loadNewDocument(String newDocumentType, String newFilePath) { Intent intent = new Intent(LibreOfficeUIActivity.this, LibreOfficeMainActivity.class); intent.putExtra(NEW_DOC_TYPE_KEY, newDocumentType); - intent.putExtra(NEW_FILE_PATH_KEY, newFilePath); startActivity(intent); } - private void open(int position) { - IFile file = filePaths.get(position); - if (!file.isDirectory()) { - open(file); - } else { - openDirectory(file); - } - } - - private void openParentDirectory() { - new AsyncTask<Void, Void, IFile>() { - @Override - protected IFile doInBackground(Void... dir) { - // this operation may imply network access and must be run in - // a different thread - return currentDirectory.getParent(LibreOfficeUIActivity.this); - } - - @Override - protected void onPostExecute(IFile result) { - openDirectory(result); - } - }.execute(); - } - - private void share(int position) { - - new AsyncTask<IFile, Void, File>() { - @Override - protected File doInBackground(IFile... document) { - // this operation may imply network access and must be run in - // a different thread - try { - return document[0].getDocument(); - } catch (final RuntimeException e) { - final Activity activity = LibreOfficeUIActivity.this; - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, e.getMessage(), - Toast.LENGTH_SHORT).show(); - } - }); - Log.e(LOGTAG, e.getMessage(), e.getCause()); - return null; - } - } - - @Override - protected void onPostExecute(File file) { - if (file != null) { - Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); - Uri uri = Uri.fromFile(file); - sharingIntent.setType(FileUtilities.getMimeType(file.getName())); - sharingIntent.putExtra(android.content.Intent.EXTRA_STREAM, uri); - sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, - file.getName()); - startActivity(Intent.createChooser(sharingIntent, - getString(R.string.share_via))); - } - } - }.execute(filePaths.get(position)); - } - @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.view_menu, menu); - switch (sortMode) { - case FileUtilities.SORT_SMALLEST: { - menu.findItem(R.id.menu_sort_size_asc).setChecked(true); - } - break; - - case FileUtilities.SORT_LARGEST: { - menu.findItem(R.id.menu_sort_size_desc).setChecked(true); - } - break; - - case FileUtilities.SORT_AZ: { - menu.findItem(R.id.menu_sort_az).setChecked(true); - } - break; - - case FileUtilities.SORT_ZA: { - menu.findItem(R.id.menu_sort_za).setChecked(true); - } - break; - - case FileUtilities.SORT_NEWEST: { - menu.findItem(R.id.menu_sort_modified_newest).setChecked(true); - } - break; - - case FileUtilities.SORT_OLDEST: { - menu.findItem(R.id.menu_sort_modified_oldest).setChecked(true); - } - break; - } - - switch (filterMode) { - case FileUtilities.ALL: - menu.findItem(R.id.menu_filter_everything).setChecked(true); - break; - - case FileUtilities.DOC: - menu.findItem(R.id.menu_filter_documents).setChecked(true); - break; - - case FileUtilities.CALC: - menu.findItem(R.id.menu_filter_presentations).setChecked(true); - break; - - case FileUtilities.IMPRESS: - menu.findItem(R.id.menu_filter_presentations).setChecked(true); - break; - - case FileUtilities.DRAWING: - menu.findItem(R.id.menu_filter_drawings).setChecked(true); - break; - } - return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { - // Will close the drawer if the home button is pressed - if (drawerToggle.onOptionsItemSelected(item)) { + final int itemId = item.getItemId(); + if (itemId == R.id.action_about) { + AboutDialogFragment aboutDialogFragment = new AboutDialogFragment(); + aboutDialogFragment.show(getSupportFragmentManager(), "AboutDialogFragment"); return true; } - - switch (item.getItemId()) { - case android.R.id.home: - if (!currentDirectory.equals(homeDirectory)){ - openParentDirectory(); - } - break; - - case R.id.menu_filter_everything: - item.setChecked(true); - filterMode = FileUtilities.ALL; - openDirectory(currentDirectory); - break; - - case R.id.menu_filter_documents: - item.setChecked(true); - filterMode = FileUtilities.DOC; - openDirectory(currentDirectory); - break; - - case R.id.menu_filter_spreadsheets: - item.setChecked(true); - filterMode = FileUtilities.CALC; - openDirectory(currentDirectory); - break; - - case R.id.menu_filter_presentations: - item.setChecked(true); - filterMode = FileUtilities.IMPRESS; - openDirectory(currentDirectory); - break; - - case R.id.menu_filter_drawings: - item.setChecked(true); - filterMode = FileUtilities.DRAWING; - openDirectory(currentDirectory); - break; - - case R.id.menu_sort_size_asc: { - sortMode = FileUtilities.SORT_SMALLEST; - this.onResume(); - } - break; - - case R.id.menu_sort_size_desc: { - sortMode = FileUtilities.SORT_LARGEST; - this.onResume(); - } - break; - - case R.id.menu_sort_az: { - sortMode = FileUtilities.SORT_AZ; - this.onResume(); - } - break; - - case R.id.menu_sort_za: { - sortMode = FileUtilities.SORT_ZA; - this.onResume(); - } - break; - - case R.id.menu_sort_modified_newest: { - sortMode = FileUtilities.SORT_NEWEST; - this.onResume(); - } - break; - - case R.id.menu_sort_modified_oldest: { - sortMode = FileUtilities.SORT_OLDEST; - this.onResume(); - } - break; - - case R.id.action_about: { - AboutDialogFragment aboutDialogFragment = new AboutDialogFragment(); - aboutDialogFragment.show(getSupportFragmentManager(), "AboutDialogFragment"); - } - return true; - case R.id.action_settings: - startActivity(new Intent(getApplicationContext(), SettingsActivity.class)); - return true; - - default: - return super.onOptionsItemSelected(item); + if (itemId == R.id.action_settings) { + startActivity(new Intent(getApplicationContext(), SettingsActivity.class)); + return true; } - return true; + + return super.onOptionsItemSelected(item); } public void readPreferences(){ - prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE); - sortMode = prefs.getInt(SORT_MODE_KEY, FileUtilities.SORT_AZ); SharedPreferences defaultPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); - viewMode = Integer.valueOf(defaultPrefs.getString(EXPLORER_VIEW_TYPE_KEY, ""+ GRID_VIEW)); - filterMode = Integer.valueOf(defaultPrefs.getString(FILTER_MODE_KEY , "-1")); - showHiddenFiles = defaultPrefs.getBoolean(ENABLE_SHOW_HIDDEN_FILES_KEY, false); - displayLanguage = defaultPrefs.getString(DISPLAY_LANGUAGE, LocaleHelper.SYSTEM_DEFAULT_LANGUAGE); - - Intent i = this.getIntent(); - if (i.hasExtra(CURRENT_DIRECTORY_KEY)) { - try { - currentDirectory = documentProvider.createFromUri(this, new URI( - i.getStringExtra(CURRENT_DIRECTORY_KEY))); - } catch (URISyntaxException e) { - currentDirectory = documentProvider.getRootDirectory(this); - } - Log.d(LOGTAG, CURRENT_DIRECTORY_KEY); - } - - if (i.hasExtra(FILTER_MODE_KEY)) { - filterMode = i.getIntExtra( FILTER_MODE_KEY, FileUtilities.ALL); - Log.d(LOGTAG, FILTER_MODE_KEY); - } - - if (i.hasExtra(EXPLORER_VIEW_TYPE_KEY)) { - viewMode = i.getIntExtra( EXPLORER_VIEW_TYPE_KEY, GRID_VIEW); - Log.d(LOGTAG, EXPLORER_VIEW_TYPE_KEY); - } - + final String displayLanguage = defaultPrefs.getString(DISPLAY_LANGUAGE, LocaleHelper.SYSTEM_DEFAULT_LANGUAGE); LocaleHelper.setLocale(this, displayLanguage); } @Override public void settingsPreferenceChanged(SharedPreferences sharedPreferences, String key) { readPreferences(); - refreshView(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - // TODO Auto-generated method stub - super.onSaveInstanceState(outState); - - if(currentDirectory != null) { - outState.putString(CURRENT_DIRECTORY_KEY, currentDirectory.getUri().toString()); - Log.d(LOGTAG, currentDirectory.toString() + Integer.toString(filterMode) + Integer.toString(viewMode)); - } - outState.putInt(FILTER_MODE_KEY, filterMode); - outState.putInt(EXPLORER_VIEW_TYPE_KEY , viewMode); - if(documentProvider != null) - outState.putInt(DOC_PROVIDER_KEY, documentProvider.getId()); - - outState.putBoolean(ENABLE_SHOW_HIDDEN_FILES_KEY , showHiddenFiles); - - //prefs.edit().putInt(EXPLORER_VIEW_TYPE, viewType).commit(); - Log.d(LOGTAG, "savedInstanceState"); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - // TODO Auto-generated method stub - super.onRestoreInstanceState(savedInstanceState); - if (savedInstanceState.isEmpty()){ - return; - } - if (documentProvider == null) { - Log.d(LOGTAG, "onRestoreInstanceState - documentProvider is null"); - documentProvider = DocumentProviderFactory.getInstance() - .getProvider(savedInstanceState.getInt(DOC_PROVIDER_KEY)); - } - try { - currentDirectory = documentProvider.createFromUri(this, new URI( - savedInstanceState.getString(CURRENT_DIRECTORY_KEY))); - } catch (URISyntaxException e) { - currentDirectory = documentProvider.getRootDirectory(this); - } - filterMode = savedInstanceState.getInt(FILTER_MODE_KEY, FileUtilities.ALL); - viewMode = savedInstanceState.getInt(EXPLORER_VIEW_TYPE_KEY, GRID_VIEW); - showHiddenFiles = savedInstanceState.getBoolean(ENABLE_SHOW_HIDDEN_FILES_KEY, false); - //openDirectory(currentDirectory); - Log.d(LOGTAG, "onRestoreInstanceState"); - Log.d(LOGTAG, currentDirectory.toString() + Integer.toString(filterMode) + Integer.toString(viewMode)); - } - - private final BroadcastReceiver mUSBReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { - Toast.makeText(context, R.string.usb_connected_configure, Toast.LENGTH_SHORT).show(); - startActivity(new Intent(context, DocumentProviderSettingsActivity.class)); - Log.d(LOGTAG, "USB device attached"); - } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { - Log.d(LOGTAG, "USB device detached"); - } - } - }; - @Override - protected void onPause() { - super.onPause(); - Log.d(LOGTAG, "onPause"); } @Override protected void onResume() { super.onResume(); Log.d(LOGTAG, "onResume"); - Log.d(LOGTAG, "sortMode="+ sortMode + " filterMode=" + filterMode); createUI(); } - @Override - protected void onStart() { - super.onStart(); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - Log.i(LOGTAG, "no permission to read external storage - asking for permission"); - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSION_WRITE_EXTERNAL_STORAGE); - } else { - switchToDocumentProvider(documentProviderFactory.getDefaultProvider()); - setEditFABVisibility(View.VISIBLE); - } - Log.d(LOGTAG, "onStart"); - } - - @Override - protected void onStop() { - super.onStop(); - Log.d(LOGTAG, "onStop"); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - unregisterReceiver(mUSBReceiver); - Log.d(LOGTAG, "onDestroy"); - } - - private int dpToPx(int dp){ - final float scale = getApplicationContext().getResources().getDisplayMetrics().density; - return (int) (dp * scale + 0.5f); - } + private void addDocumentToRecents(Uri fileUri) { + SharedPreferences prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE); - private void addDocumentToRecents(IFile iFile) { - String newRecent = iFile.getUri().toString(); - Set<String> recentsSet = prefs.getStringSet(RECENT_DOCUMENTS_KEY, new HashSet<String>()); + // preserve permissions across device reboots, + // s. https://developer.android.com/training/data-storage/shared/documents-files#persist-permissions + getContentResolver().takePersistableUriPermission(fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - //create array to work with - ArrayList<String> recentsArrayList = new ArrayList<String>(recentsSet); + String newRecent = fileUri.toString(); + List<String> recentsList = new ArrayList<>(Arrays.asList(prefs.getString(RECENT_DOCUMENTS_KEY, "").split(RECENT_DOCUMENTS_DELIMITER))); - //remove string if present, so that it doesn't appear multiple times - recentsSet.remove(newRecent); - - //put the new value in the first place - recentsArrayList.add(0, newRecent); + // remove string if present, so that it doesn't appear multiple times + recentsList.remove(newRecent); + // put the new value in the first place + recentsList.add(0, newRecent); /* * 4 because the number of recommended items in App Shortcuts is 4, and also @@ -999,15 +373,13 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings */ final int RECENTS_SIZE = 4; - while (recentsArrayList.size() > RECENTS_SIZE) { - recentsArrayList.remove(RECENTS_SIZE); + while (recentsList.size() > RECENTS_SIZE) { + recentsList.remove(RECENTS_SIZE); } - //switch to Set, so that it could be inserted into prefs - recentsSet = new HashSet<String>(recentsArrayList); - - prefs.edit().putStringSet(RECENT_DOCUMENTS_KEY, recentsSet).apply(); - + // serialize to String that can be set for pref + String value = TextUtils.join(RECENT_DOCUMENTS_DELIMITER, recentsList); + prefs.edit().putString(RECENT_DOCUMENTS_KEY, value).apply(); //update app shortcuts (7.0 and above) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) { @@ -1016,12 +388,17 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings //Remove all shortcuts, and apply new ones. shortcutManager.removeAllDynamicShortcuts(); - ArrayList<ShortcutInfo> shortcuts = new ArrayList<ShortcutInfo>(); - for (String pathString : recentsArrayList) { + ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + for (String recentDoc : recentsList) { + Uri docUri = Uri.parse(recentDoc); + String filename = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri); + if (filename.isEmpty()) { + continue; + } //find the appropriate drawable int drawable = 0; - switch (FileUtilities.getType(pathString)) { + switch (FileUtilities.getType(filename)) { case FileUtilities.DOC: drawable = R.drawable.writer; break; @@ -1036,12 +413,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings break; } - File file = new File(pathString); - - //for some reason, getName uses %20 instead of space - String filename = file.getName().replace("%20", " "); - - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.fromFile(file)); + Intent intent = new Intent(Intent.ACTION_VIEW, docUri); String packageName = this.getApplicationContext().getPackageName(); ComponentName componentName = new ComponentName(packageName, LibreOfficeMainActivity.class.getName()); intent.setComponent(componentName); @@ -1062,161 +434,22 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings @Override public void onClick(View v) { int id = v.getId(); - switch (id){ - case R.id.editFAB: - if (isFabMenuOpen) { - collapseFabMenu(); - } else { - expandFabMenu(); - } - break; - case R.id.newWriterFAB: - createNewFileInputDialog(getString(R.string.default_document_name) + FileUtilities.DEFAULT_WRITER_EXTENSION, NEW_WRITER_STRING_KEY); - break; - case R.id.newImpressFAB: - createNewFileInputDialog(getString(R.string.default_document_name) + FileUtilities.DEFAULT_IMPRESS_EXTENSION, NEW_IMPRESS_STRING_KEY); - break; - case R.id.newCalcFAB: - createNewFileInputDialog(getString(R.string.default_document_name) + FileUtilities.DEFAULT_SPREADSHEET_EXTENSION, NEW_CALC_STRING_KEY); - break; - case R.id.newDrawFAB: - createNewFileInputDialog(getString(R.string.default_document_name) + FileUtilities.DEFAULT_DRAWING_EXTENSION, NEW_DRAW_STRING_KEY); - break; - } - } - - - class ExplorerItemAdapter extends RecyclerView.Adapter<ExplorerItemAdapter.ViewHolder> { - - private Activity mActivity; - private List<IFile> filePaths; - private final long KB = 1024; - private final long MB = 1048576; - - ExplorerItemAdapter(Activity activity, List<IFile> filePaths) { - this.mActivity = activity; - this.filePaths = filePaths; - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View item = LayoutInflater.from(parent.getContext()) - .inflate(isViewModeList() ? R.layout.file_list_item : R.layout.file_explorer_grid_item, parent, false); - return new ViewHolder(item); - } - - @Override - public void onBindViewHolder(final ViewHolder holder, final int position) { - final IFile file = filePaths.get(position); - - holder.itemView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - open(holder.getAdapterPosition()); - } - }); - holder.itemView.setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View view) { - //to be picked out by floating context menu (workaround-ish) - currentlySelectedFile = holder.getAdapterPosition(); - //must return false so the click is not consumed - return false; - } - }); - - holder.filenameView.setText(file.getName()); - switch (FileUtilities.getType(file.getName())) { - case FileUtilities.DOC: - holder.iconView.setImageResource(R.drawable.writer); - break; - case FileUtilities.CALC: - holder.iconView.setImageResource(R.drawable.calc); - break; - case FileUtilities.DRAWING: - holder.iconView.setImageResource(R.drawable.draw); - break; - case FileUtilities.IMPRESS: - holder.iconView.setImageResource(R.drawable.impress); - break; - } - - if (file.isDirectory()) { - //Eventually have thumbnails of each sub file on a black circle - //For now just a folder icon - holder.iconView.setImageResource(R.drawable.ic_folder_black_24dp); - holder.iconView.setColorFilter(ContextCompat.getColor(mActivity, R.color.text_color_secondary)); - } - - // Date and Size field only exist when we are displaying items in a list. - if(isViewModeList()) { - if (!file.isDirectory()) { - String size; - long length = filePaths.get(position).getSize(); - if (length < KB) { - size = Long.toString(length) + "B"; - } else if (length < MB) { - size = Long.toString(length / KB) + "KB"; - } else { - size = Long.toString(length / MB) + "MB"; - } - holder.fileSizeView.setText(size); - } - SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy hh:ss"); - Date date = file.getLastModified(); - //TODO format date - holder.fileDateView.setText(df.format(date)); - } - } - - @Override - public int getItemCount() { - return filePaths.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - View itemView; - TextView filenameView, fileSizeView, fileDateView; - ImageView iconView; - - ViewHolder(View itemView) { - super(itemView); - this.itemView = itemView; - filenameView = itemView.findViewById(R.id.file_item_name); - iconView = itemView.findViewById(R.id.file_item_icon); - // Check if view mode is List, only then initialise Size and Date field - if (isViewModeList()) { - fileSizeView = itemView.findViewById(R.id.file_item_size); - fileDateView = itemView.findViewById(R.id.file_item_date); - } - } - } - } - - private void setEditFABVisibility(final int visibility){ - LOKitShell.getMainHandler().post(new Runnable() { - @Override - public void run() { - editFAB.setVisibility(visibility); - } - }); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - switch(requestCode){ - case PERMISSION_WRITE_EXTERNAL_STORAGE: - if(permissions.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ - switchToDocumentProvider(documentProviderFactory.getDefaultProvider()); - setEditFABVisibility(View.VISIBLE); - } else { - setEditFABVisibility(View.INVISIBLE); - } - break; - default: - super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (id == R.id.editFAB) { + if (isFabMenuOpen) { + collapseFabMenu(); + } else { + expandFabMenu(); + } + } else if (id == R.id.open_file_button) { + showSystemFilePickerAndOpenFile(); + } else if (id == R.id.newWriterFAB) { + loadNewDocument(DocumentType.WRITER); + } else if (id == R.id.newImpressFAB) { + loadNewDocument(DocumentType.IMPRESS); + } else if (id == R.id.newCalcFAB) { + loadNewDocument(DocumentType.CALC); + } else if (id == R.id.newDrawFAB) { + loadNewDocument(DocumentType.DRAW); } } } diff --git a/android/source/src/java/org/libreoffice/ui/PageView.java b/android/source/src/java/org/libreoffice/ui/PageView.java index 1d32a7de7e80..4c3f69562250 100644 --- a/android/source/src/java/org/libreoffice/ui/PageView.java +++ b/android/source/src/java/org/libreoffice/ui/PageView.java @@ -17,29 +17,29 @@ import android.view.View; public class PageView extends View{ private Bitmap bmp; private Paint mPaintBlack; - private String tag = "PageView"; + private static final String LOGTAG = "PageView"; public PageView(Context context ) { super(context); bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page); - intialise(); + initialise(); } public PageView(Context context, AttributeSet attrs) { super(context, attrs); bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page); - Log.d( tag , bmp.toString()); - intialise(); + Log.d(LOGTAG, bmp.toString()); + initialise(); } public PageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); bmp = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_page);//load a "page" - intialise(); + initialise(); } - private void intialise(){ + private void initialise(){ mPaintBlack = new Paint(); mPaintBlack.setARGB(255, 0, 0, 0); - Log.d(tag, " Doing some set-up"); + Log.d(LOGTAG, " Doing some set-up"); } public void setBitmap(Bitmap bmp){ @@ -49,8 +49,8 @@ public class PageView extends View{ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - Log.d(tag, "Draw"); - Log.d(tag, Integer.toString(bmp.getHeight())); + Log.d(LOGTAG, "Draw"); + Log.d(LOGTAG, Integer.toString(bmp.getHeight())); if( bmp != null ){ int horizontalMargin = (int) (canvas.getWidth()*0.1); //int verticalMargin = (int) (canvas.getHeight()*0.1); diff --git a/android/source/src/java/org/libreoffice/ui/RecentFile.java b/android/source/src/java/org/libreoffice/ui/RecentFile.java new file mode 100644 index 000000000000..fdcc688aa140 --- /dev/null +++ b/android/source/src/java/org/libreoffice/ui/RecentFile.java @@ -0,0 +1,25 @@ +package org.libreoffice.ui; + +import android.net.Uri; + +/** + * An entry for a recently used file in the RecentFilesAdapter. + */ +public class RecentFile { + + private final Uri uri; + private final String displayName; + + public RecentFile(Uri docUri, String name) { + uri = docUri; + displayName = name; + } + + public Uri getUri() { + return uri; + } + + public String getDisplayName() { + return displayName; + } +} diff --git a/android/source/src/java/org/libreoffice/ui/RecentFilesAdapter.java b/android/source/src/java/org/libreoffice/ui/RecentFilesAdapter.java index fc16d06a48d7..ef00b9fb6cfd 100644 --- a/android/source/src/java/org/libreoffice/ui/RecentFilesAdapter.java +++ b/android/source/src/java/org/libreoffice/ui/RecentFilesAdapter.java @@ -9,8 +9,8 @@ package org.libreoffice.ui; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -18,16 +18,15 @@ import android.widget.ImageView; import android.widget.TextView; import org.libreoffice.R; -import org.libreoffice.storage.IFile; import java.util.List; class RecentFilesAdapter extends RecyclerView.Adapter<RecentFilesAdapter.ViewHolder> { - private LibreOfficeUIActivity mActivity; - private List<IFile> recentFiles; + private final LibreOfficeUIActivity mActivity; + private final List<RecentFile> recentFiles; - RecentFilesAdapter(LibreOfficeUIActivity activity, List<IFile> recentFiles) { + RecentFilesAdapter(LibreOfficeUIActivity activity, List<RecentFile> recentFiles) { this.mActivity = activity; this.recentFiles = recentFiles; } @@ -41,17 +40,16 @@ class RecentFilesAdapter extends RecyclerView.Adapter<RecentFilesAdapter.ViewHol @Override public void onBindViewHolder(ViewHolder holder, int position) { - final IFile iFile = recentFiles.get(position); + final RecentFile entry = recentFiles.get(position); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - mActivity.open(iFile); + mActivity.openDocument(entry.getUri()); } }); - String filename = iFile.getName(); - + final String filename = entry.getDisplayName(); holder.textView.setText(filename); int compoundDrawableInt = 0; diff --git a/android/source/src/java/org/mozilla/gecko/ZoomConstraints.java b/android/source/src/java/org/mozilla/gecko/ZoomConstraints.java index f1672ba3dd76..dbe278827279 100644 --- a/android/source/src/java/org/mozilla/gecko/ZoomConstraints.java +++ b/android/source/src/java/org/mozilla/gecko/ZoomConstraints.java @@ -6,28 +6,16 @@ package org.mozilla.gecko; public final class ZoomConstraints { - private final boolean mAllowZoom; - private final boolean mAllowDoubleTapZoom; private final float mDefaultZoom; private final float mMinZoom; private final float mMaxZoom; - public ZoomConstraints(boolean allowZoom, float defaultZoom, float minZoom, float maxZoom) { - mAllowZoom = allowZoom; - mAllowDoubleTapZoom = allowZoom; + public ZoomConstraints(float defaultZoom, float minZoom, float maxZoom) { mDefaultZoom = defaultZoom; mMinZoom = minZoom; mMaxZoom = maxZoom; } - public final boolean getAllowZoom() { - return mAllowZoom; - } - - public final boolean getAllowDoubleTapZoom() { - return mAllowDoubleTapZoom; - } - public final float getDefaultZoom() { return mDefaultZoom; } diff --git a/android/source/src/java/org/mozilla/gecko/gfx/GLController.java b/android/source/src/java/org/mozilla/gecko/gfx/GLController.java index e296f4760f68..6a43dd6a87db 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/GLController.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/GLController.java @@ -20,7 +20,6 @@ public class GLController { private LayerView mView; private int mGLVersion; - private boolean mSurfaceValid; private int mWidth, mHeight; private EGL10 mEGL; @@ -29,8 +28,6 @@ public class GLController { private EGLContext mEGLContext; private EGLSurface mEGLSurface; - private GL mGL; - private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4; private static final int[] CONFIG_SPEC = { @@ -45,7 +42,6 @@ public class GLController { public GLController(LayerView view) { mView = view; mGLVersion = 2; - mSurfaceValid = false; } public void setGLVersion(int version) { @@ -84,12 +80,11 @@ public class GLController { getEGLError()); } - mGL = null; mEGLContext = null; } } - public GL getGL() { return mEGLContext.getGL(); } + public GL10 getGL() { return (GL10) mEGLContext.getGL(); } public EGLDisplay getEGLDisplay() { return mEGLDisplay; } public EGLConfig getEGLConfig() { return mEGLConfig; } public EGLContext getEGLContext() { return mEGLContext; } @@ -104,38 +99,6 @@ public class GLController { return mEGL.eglSwapBuffers(mEGLDisplay, mEGLSurface); } - public boolean checkForLostContext() { - if (mEGL.eglGetError() != EGL11.EGL_CONTEXT_LOST) { - return false; - } - - mEGLDisplay = null; - mEGLConfig = null; - mEGLContext = null; - mEGLSurface = null; - mGL = null; - return true; - } - - // This function is invoked by JNI - public synchronized void resumeCompositorIfValid() { - if (mSurfaceValid) { - mView.getListener().compositionResumeRequested(mWidth, mHeight); - } - } - - // Wait until we are allowed to use EGL functions on the Surface backing - // this window. This function is invoked by JNI - public synchronized void waitForValidSurface() { - while (!mSurfaceValid) { - try { - wait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - public synchronized int getWidth() { return mWidth; } @@ -145,14 +108,12 @@ public class GLController { } synchronized void surfaceDestroyed() { - mSurfaceValid = false; notifyAll(); } synchronized void surfaceChanged(int newWidth, int newHeight) { mWidth = newWidth; mHeight = newHeight; - mSurfaceValid = true; notifyAll(); } @@ -183,11 +144,10 @@ public class GLController { getEGLError()); } - mGL = mEGLContext.getGL(); - if (mView.getRenderer() != null) { - mView.getRenderer().onSurfaceCreated((GL10)mGL, mEGLConfig); - mView.getRenderer().onSurfaceChanged((GL10)mGL, mWidth, mHeight); + GL10 gl = (GL10) mEGLContext.getGL(); + mView.getRenderer().onSurfaceCreated(gl, mEGLConfig); + mView.getRenderer().onSurfaceChanged(gl, mWidth, mHeight); } } @@ -216,7 +176,8 @@ public class GLController { } } - throw new GLControllerException("No suitable EGL configuration found"); + // if there's no 565 RGB configuration, select another one that fulfils the specification + return configs[0]; } private void createEGLSurface() { @@ -232,32 +193,11 @@ public class GLController { "surface! " + getEGLError()); } - mGL = mEGLContext.getGL(); - if (mView.getRenderer() != null) { - mView.getRenderer().onSurfaceCreated((GL10)mGL, mEGLConfig); - mView.getRenderer().onSurfaceChanged((GL10)mGL, mView.getWidth(), mView.getHeight()); - } - } - - /** - * Provides an EGLSurface without assuming ownership of this surface. - * This class does not keep a reference to the provided EGL surface; the - * caller assumes ownership of the surface once it is returned. - */ - private EGLSurface provideEGLSurface() { - if (mEGL == null) { - initEGL(); + GL10 gl = (GL10) mEGLContext.getGL(); + mView.getRenderer().onSurfaceCreated(gl, mEGLConfig); + mView.getRenderer().onSurfaceChanged(gl, mView.getWidth(), mView.getHeight()); } - - Object window = mView.getNativeWindow(); - EGLSurface surface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, window, null); - if (surface == null || surface == EGL10.EGL_NO_SURFACE) { - throw new GLControllerException("EGL window surface could not be created! " + - getEGLError()); - } - - return surface; } private String getEGLError() { diff --git a/android/source/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/source/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java index 681fb6fd6019..72a96f0bb00f 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java @@ -80,7 +80,7 @@ public class GeckoLayerClient implements PanZoomTarget { mView.setLayerRenderer(mLayerRenderer); - sendResizeEventIfNecessary(); + sendResizeEventIfNecessary(false); mView.requestRender(); } @@ -124,21 +124,23 @@ public class GeckoLayerClient implements PanZoomTarget { * to the layer client. That way, the layer client won't be tempted to call this, which might * result in an infinite loop. */ - void setViewportSize(FloatSize size) { + void setViewportSize(FloatSize size, boolean forceResizeEvent) { mViewportMetrics = mViewportMetrics.setViewportSize(size.width, size.height); - sendResizeEventIfNecessary(); + sendResizeEventIfNecessary(forceResizeEvent); } PanZoomController getPanZoomController() { return mPanZoomController; } - /* Informs Gecko that the screen size has changed. */ - private void sendResizeEventIfNecessary() { + /* Informs Gecko that the screen size has changed. + * @param force: If true, a resize event will always be sent, otherwise + * it is only sent if size has changed. */ + private void sendResizeEventIfNecessary(boolean force) { DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels); - if (mScreenSize.equals(newScreenSize)) { + if (!force && mScreenSize.equals(newScreenSize)) { return; } @@ -233,7 +235,7 @@ public class GeckoLayerClient implements PanZoomTarget { } private void geometryChanged() { - sendResizeEventIfNecessary(); + sendResizeEventIfNecessary(false); if (getRedrawHint()) { adjustViewport(null); } diff --git a/android/source/src/java/org/mozilla/gecko/gfx/JavaPanZoomController.java b/android/source/src/java/org/mozilla/gecko/gfx/JavaPanZoomController.java index db2fcc03c5b3..b20d602a21cb 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/JavaPanZoomController.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/JavaPanZoomController.java @@ -21,6 +21,7 @@ import org.mozilla.gecko.util.FloatUtils; import java.util.Timer; import java.util.TimerTask; +import java.lang.StrictMath; /* * Handles the kinetic scrolling and zooming physics for a layer controller. @@ -143,16 +144,9 @@ class JavaPanZoomController /** This function MUST be called on the UI thread */ public boolean onMotionEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT <= 11) { - return false; - } - - switch (event.getSource() & InputDevice.SOURCE_CLASS_MASK) { - case InputDevice.SOURCE_CLASS_POINTER: - switch (event.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_SCROLL: return handlePointerScroll(event); - } - break; + if ((event.getSource() & InputDevice.SOURCE_CLASS_MASK) == InputDevice.SOURCE_CLASS_POINTER + && (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_SCROLL) { + return handlePointerScroll(event); } return false; } @@ -433,7 +427,7 @@ class JavaPanZoomController private float panDistance(MotionEvent move) { float dx = mX.panDistance(move.getX(0)); float dy = mY.panDistance(move.getY(0)); - return (float) Math.sqrt(dx * dx + dy * dy); + return (float) Math.hypot(dx , dy); } private void track(float x, float y, long time) { @@ -550,7 +544,7 @@ class JavaPanZoomController private float getVelocity() { float xvel = mX.getRealVelocity(); float yvel = mY.getRealVelocity(); - return (float) Math.sqrt(xvel * xvel + yvel * yvel); + return (float) StrictMath.hypot(xvel, yvel); } public PointF getVelocityVector() { @@ -751,11 +745,6 @@ class JavaPanZoomController if (constraints.getMaxZoom() > 0) maxZoomFactor = constraints.getMaxZoom(); - if (!constraints.getAllowZoom()) { - // If allowZoom is false, clamp to the default zoom level. - maxZoomFactor = minZoomFactor = constraints.getDefaultZoom(); - } - maxZoomFactor = Math.max(maxZoomFactor, minZoomFactor); if (zoomFactor < minZoomFactor) { @@ -828,7 +817,7 @@ class JavaPanZoomController if (mState == PanZoomState.ANIMATED_ZOOM) return false; - if (null == mTarget.getZoomConstraints() || !mTarget.getZoomConstraints().getAllowZoom()) + if (null == mTarget.getZoomConstraints()) return false; setState(PanZoomState.PINCHING); @@ -934,10 +923,7 @@ class JavaPanZoomController @Override public boolean onDown(MotionEvent motionEvent) { - if (mTarget.getZoomConstraints() != null) - mWaitForDoubleTap = mTarget.getZoomConstraints().getAllowDoubleTapZoom(); - else - mWaitForDoubleTap = false; + mWaitForDoubleTap = mTarget.getZoomConstraints() != null; return false; } @@ -991,14 +977,14 @@ class JavaPanZoomController @Override public boolean onDoubleTap(MotionEvent motionEvent) { - if (null == mTarget.getZoomConstraints() || !mTarget.getZoomConstraints().getAllowDoubleTapZoom()) { + if (null == mTarget.getZoomConstraints()) { return true; } // Double tap zooms in or out depending on the current zoom factor PointF pointOfTap = getMotionInDocumentCoordinates(motionEvent); ImmutableViewportMetrics metrics = getMetrics(); float newZoom = metrics.getZoomFactor() >= - DOUBLE_TAP_THRESHOLD ? mTarget.getZoomConstraints().getMinZoom() : DOUBLE_TAP_THRESHOLD; + DOUBLE_TAP_THRESHOLD ? mTarget.getZoomConstraints().getDefaultZoom() : DOUBLE_TAP_THRESHOLD; // calculate new top_left point from the point of tap float ratio = newZoom/metrics.getZoomFactor(); float newLeft = pointOfTap.x - 1/ratio * (pointOfTap.x - metrics.getOrigin().x / metrics.getZoomFactor()); diff --git a/android/source/src/java/org/mozilla/gecko/gfx/LayerRenderer.java b/android/source/src/java/org/mozilla/gecko/gfx/LayerRenderer.java index b1aea3616d6c..6ea7dd0edc10 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/LayerRenderer.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/LayerRenderer.java @@ -48,13 +48,9 @@ public class LayerRenderer implements GLSurfaceView.Renderer { private FloatBuffer mCoordBuffer; private RenderContext mLastPageContext; private int mMaxTextureSize; - private int mBackgroundColor; private CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>(); - /* Used by robocop for testing purposes */ - private IntBuffer mPixelBuffer; - // Used by GLES 2.0 private int mProgram; private int mPositionHandle; @@ -146,6 +142,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer { mVertScrollLayer.destroy(); } + @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { createDefaultProgram(); activateDefaultProgram(); @@ -184,9 +181,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer { GLES20.glEnableVertexAttribArray(mTextureHandle); GLES20.glUniform1i(mSampleHandle, 0); - - // TODO: Move these calls into a separate deactivate() call that is called after the - // underlay and overlay are rendered. } // Deactivates the shader program. This must be done to avoid crashes after returning to the @@ -220,8 +214,9 @@ public class LayerRenderer implements GLSurfaceView.Renderer { /** * Called whenever a new frame is about to be drawn. */ + @Override public void onDrawFrame(GL10 gl) { - Frame frame = createFrame(mView.getLayerClient().getViewportMetrics()); + Frame frame = new Frame(mView.getLayerClient().getViewportMetrics()); synchronized (mView.getLayerClient()) { frame.beginDrawing(); frame.drawBackground(); @@ -249,6 +244,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer { mCoordBuffer); } + @Override public void onSurfaceChanged(GL10 gl, final int width, final int height) { GLES20.glViewport(0, 0, width, height); } @@ -264,10 +260,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer { return shader; } - public Frame createFrame(ImmutableViewportMetrics metrics) { - return new Frame(metrics); - } - class FadeRunnable implements Runnable { private boolean mStarted; private long mRunAt; @@ -305,8 +297,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer { } public class Frame { - // The timestamp recording the start of this frame. - private long mFrameStartTime; // A fixed snapshot of the viewport metrics that this frame is using to render content. private ImmutableViewportMetrics mFrameMetrics; // A rendering context for page-positioned layers, and one for screen-positioned layers. @@ -348,10 +338,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer { return pageRect; } - /** This function is invoked via JNI; be careful when modifying signature. */ public void beginDrawing() { - mFrameStartTime = SystemClock.uptimeMillis(); - TextureReaper.get().reap(); TextureGenerator.get().fill(); @@ -386,58 +373,18 @@ public class LayerRenderer implements GLSurfaceView.Renderer { mUpdated &= layer.update(mPageContext); // called on compositor thread } - /** Retrieves the bounds for the layer, rounded in such a way that it - * can be used as a mask for something that will render underneath it. - * This will round the bounds inwards, but stretch the mask towards any - * near page edge, where near is considered to be 'within 2 pixels'. - * Returns null if the given layer is null. - */ - private Rect getMaskForLayer(Layer layer) { - if (layer == null) { - return null; - } - - RectF bounds = RectUtils.contract(layer.getBounds(mPageContext), 1.0f, 1.0f); - Rect mask = RectUtils.roundIn(bounds); - - // If the mask is within two pixels of any page edge, stretch it over - // that edge. This is to avoid drawing thin slivers when masking - // layers. - if (mask.top <= 2) { - mask.top = -1; - } - if (mask.left <= 2) { - mask.left = -1; - } - - // Because we're drawing relative to the page-rect, we only need to - // take into account its width and height (and not its origin) - int pageRight = mPageRect.width(); - int pageBottom = mPageRect.height(); - - if (mask.right >= pageRight - 2) { - mask.right = pageRight + 1; - } - if (mask.bottom >= pageBottom - 2) { - mask.bottom = pageBottom + 1; - } - - return mask; - } - - /** This function is invoked via JNI; be careful when modifying signature. */ public void drawBackground() { GLES20.glDisable(GLES20.GL_SCISSOR_TEST); /* Update background color. */ - mBackgroundColor = Color.WHITE; + final int backgroundColor = Color.WHITE; /* Clear to the page background colour. The bits set here need to * match up with those used in gfx/layers/opengl/LayerManagerOGL.cpp. */ - GLES20.glClearColor(((mBackgroundColor>>16)&0xFF) / 255.0f, - ((mBackgroundColor>>8)&0xFF) / 255.0f, - (mBackgroundColor&0xFF) / 255.0f, + GLES20.glClearColor(((backgroundColor >> 16) & 0xFF) / 255.0f, + ((backgroundColor >> 8) & 0xFF) / 255.0f, + (backgroundColor & 0xFF) / 255.0f, 0.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); @@ -474,7 +421,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer { rootLayer.draw(mPageContext); } - /** This function is invoked via JNI; be careful when modifying signature. */ public void drawForeground() { /* Draw any extra layers that were added (likely plugins) */ if (mExtraLayers.size() > 0) { @@ -498,23 +444,10 @@ public class LayerRenderer implements GLSurfaceView.Renderer { mHorizScrollLayer.draw(mPageContext); } - /** This function is invoked via JNI; be careful when modifying signature. */ public void endDrawing() { // If a layer update requires further work, schedule another redraw if (!mUpdated) mView.requestRender(); - - /* Used by robocop for testing purposes */ - IntBuffer pixelBuffer = mPixelBuffer; - if (mUpdated && pixelBuffer != null) { - synchronized (pixelBuffer) { - pixelBuffer.position(0); - GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(), - (int)mScreenContext.viewport.height(), GLES20.GL_RGBA, - GLES20.GL_UNSIGNED_BYTE, pixelBuffer); - pixelBuffer.notify(); - } - } } } } diff --git a/android/source/src/java/org/mozilla/gecko/gfx/LayerView.java b/android/source/src/java/org/mozilla/gecko/gfx/LayerView.java index 05f2118114c8..29049f92912d 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/LayerView.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/LayerView.java @@ -11,14 +11,12 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.PixelFormat; -import android.graphics.SurfaceTexture; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; -import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; @@ -37,8 +35,6 @@ import org.mozilla.gecko.OnSlideSwipeListener; * * This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a * mediator between the LayerRenderer and the LayerController. - * - * Note that LayerView is accessed by Robocop via reflection. */ public class LayerView extends FrameLayout { private static String LOGTAG = LayerView.class.getName(); @@ -49,59 +45,22 @@ public class LayerView extends FrameLayout { private InputConnectionHandler mInputConnectionHandler; private LayerRenderer mRenderer; - /* Must be a PAINT_xxx constant */ - private int mPaintState = PAINT_NONE; - private boolean mFullScreen = false; - private SurfaceView mSurfaceView; - private TextureView mTextureView; private Listener mListener; private OnInterceptTouchListener mTouchIntercepter; - //TODO static because of registerCxxCompositor() function, should be fixed in the future - private static LibreOfficeMainActivity mContext; - - /* Flags used to determine when to show the painted surface. The integer - * order must correspond to the order in which these states occur. */ - public static final int PAINT_NONE = 0; - public static final int PAINT_BEFORE_FIRST = 1; - public static final int PAINT_AFTER_FIRST = 2; - - boolean shouldUseTextureView() { - // we can only use TextureView on ICS or higher - /*if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - Log.i(LOGTAG, "Not using TextureView: not on ICS+"); - return false; - } - - try { - // and then we can only use it if we have a hardware accelerated window - Method m = View.class.getMethod("isHardwareAccelerated", new Class[0]); - return (Boolean) m.invoke(this); - } catch (Exception e) { - Log.i(LOGTAG, "Not using TextureView: caught exception checking for hw accel: " + e.toString()); - return false; - }*/ - return false; - } + private LibreOfficeMainActivity mContext; public LayerView(Context context, AttributeSet attrs) { super(context, attrs); mContext = (LibreOfficeMainActivity) context; - if (shouldUseTextureView()) { - mTextureView = new TextureView(context); - mTextureView.setSurfaceTextureListener(new SurfaceTextureListener()); + mSurfaceView = new SurfaceView(context); + addView(mSurfaceView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - addView(mTextureView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - } else { - mSurfaceView = new SurfaceView(context); - addView(mSurfaceView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - - SurfaceHolder holder = mSurfaceView.getHolder(); - holder.addCallback(new SurfaceListener()); + SurfaceHolder holder = mSurfaceView.getHolder(); + holder.addCallback(new SurfaceListener()); holder.setFormat(PixelFormat.RGB_565); - } mGLController = new GLController(this); } @@ -183,10 +142,6 @@ public class LayerView extends FrameLayout { public GeckoLayerClient getLayerClient() { return mLayerClient; } public PanZoomController getPanZoomController() { return mPanZoomController; } - public void setViewportSize(IntSize size) { - mLayerClient.setViewportSize(new FloatSize(size)); - } - public ImmutableViewportMetrics getViewportMetrics() { return mLayerClient.getViewportMetrics(); } @@ -223,13 +178,6 @@ public class LayerView extends FrameLayout { return mInputConnectionHandler != null && mInputConnectionHandler.onKeyUp(keyCode, event); } - public boolean isIMEEnabled() { - /*if (mInputConnectionHandler != null) { - return mInputConnectionHandler.isIMEEnabled(); - }*/ - return false; - } - public void requestRender() { if (mListener != null) { mListener.renderRequested(); @@ -256,19 +204,6 @@ public class LayerView extends FrameLayout { return mRenderer; } - /* paintState must be a PAINT_xxx constant. The state will only be changed - * if paintState represents a state that occurs after the current state. */ - public void setPaintState(int paintState) { - if (paintState > mPaintState) { - Log.d(LOGTAG, "LayerView paint state set to " + paintState); - mPaintState = paintState; - } - } - - public int getPaintState() { - return mPaintState; - } - public LayerRenderer getRenderer() { return mRenderer; } @@ -306,7 +241,7 @@ public class LayerView extends FrameLayout { private void onSizeChanged(int width, int height) { mGLController.surfaceChanged(width, height); - mLayerClient.setViewportSize(new FloatSize(width, height)); + mLayerClient.setViewportSize(new FloatSize(width, height), false); if (mListener != null) { mListener.surfaceChanged(width, height); @@ -324,29 +259,13 @@ public class LayerView extends FrameLayout { } public Object getNativeWindow() { - if (mSurfaceView != null) - return mSurfaceView.getHolder(); - - return mTextureView.getSurfaceTexture(); - } - - /** This function is invoked by Gecko (compositor thread) via JNI; be careful when modifying signature. */ - public static GLController registerCxxCompositor() { - try { - LayerView layerView = mContext.getLayerClient().getView(); - layerView.mListener.compositorCreated(); - return layerView.getGLController(); - } catch (Exception e) { - Log.e(LOGTAG, "Error registering compositor!", e); - return null; - } + return mSurfaceView.getHolder(); } public interface Listener { void compositorCreated(); void renderRequested(); void compositionPauseRequested(); - void compositionResumeRequested(int width, int height); void surfaceChanged(int width, int height); } @@ -371,30 +290,7 @@ public class LayerView extends FrameLayout { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { - setViewportSize(new IntSize(right - left, bottom - top)); - } - } - - private class SurfaceTextureListener implements TextureView.SurfaceTextureListener { - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - // We don't do this for surfaceCreated above because it is always followed by a surfaceChanged, - // but that is not the case here. - if (mRenderControllerThread != null) { - mRenderControllerThread.surfaceCreated(); - } - onSizeChanged(width, height); - } - - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - onDestroyed(); - return true; // allow Android to call release() on the SurfaceTexture, we are done drawing to it - } - - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - onSizeChanged(width, height); - } - - public void onSurfaceTextureUpdated(SurfaceTexture surface) { + mLayerClient.setViewportSize(new FloatSize(right - left, bottom - top), true); } } @@ -438,12 +334,4 @@ public class LayerView extends FrameLayout { super(e); } } - - public void setFullScreen(boolean fullScreen) { - mFullScreen = fullScreen; - } - - public boolean isFullScreen() { - return mFullScreen; - } } diff --git a/android/source/src/java/org/mozilla/gecko/gfx/PointUtils.java b/android/source/src/java/org/mozilla/gecko/gfx/PointUtils.java index 4eb07a31f147..4eff380527d2 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/PointUtils.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/PointUtils.java @@ -11,6 +11,8 @@ import android.graphics.PointF; import org.json.JSONException; import org.json.JSONObject; +import java.lang.StrictMath; + public final class PointUtils { public static PointF add(PointF one, PointF two) { return new PointF(one.x + two.x, one.y + two.y); @@ -30,7 +32,7 @@ public final class PointUtils { /* Computes the magnitude of the given vector. */ public static float distance(PointF point) { - return (float)Math.sqrt(point.x * point.x + point.y * point.y); + return (float)StrictMath.hypot(point.x, point.y); } /** Computes the scalar distance between two points. */ diff --git a/android/source/src/java/org/mozilla/gecko/gfx/RenderControllerThread.java b/android/source/src/java/org/mozilla/gecko/gfx/RenderControllerThread.java index 06f82f158366..5c74d56a004b 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/RenderControllerThread.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/RenderControllerThread.java @@ -82,11 +82,6 @@ public class RenderControllerThread extends Thread implements LayerView.Listener } @Override - public void compositionResumeRequested(int width, int height) { - - } - - @Override public void surfaceChanged(int width, int height) { this.width = width; this.height = height; @@ -115,7 +110,7 @@ public class RenderControllerThread extends Thread implements LayerView.Listener } GLSurfaceView.Renderer renderer = getRenderer(); if (renderer != null) { - renderer.onDrawFrame((GL10) controller.getGL()); + renderer.onDrawFrame(controller.getGL()); } controller.swapBuffers(); } @@ -123,7 +118,7 @@ public class RenderControllerThread extends Thread implements LayerView.Listener private void doSizeChanged() { GLSurfaceView.Renderer renderer = getRenderer(); if (renderer != null) { - renderer.onSurfaceChanged((GL10) controller.getGL(), width, height); + renderer.onSurfaceChanged(controller.getGL(), width, height); } } diff --git a/android/source/src/java/org/mozilla/gecko/gfx/SimpleScaleGestureDetector.java b/android/source/src/java/org/mozilla/gecko/gfx/SimpleScaleGestureDetector.java index 1d901a02a14b..e89015b5ed8c 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/SimpleScaleGestureDetector.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/SimpleScaleGestureDetector.java @@ -34,7 +34,7 @@ import java.util.Stack; * - It doesn't take pressure into account, which results in smoother scaling. */ public class SimpleScaleGestureDetector { - private static final String LOGTAG = "GeckoSimpleScaleGestureDetector"; + private static final String LOGTAG = "ScaleGestureDetector"; private SimpleScaleGestureListener mListener; private long mLastEventTime; diff --git a/android/source/src/java/org/mozilla/gecko/gfx/SubTile.java b/android/source/src/java/org/mozilla/gecko/gfx/SubTile.java index 42750df62838..bdad37195d90 100644 --- a/android/source/src/java/org/mozilla/gecko/gfx/SubTile.java +++ b/android/source/src/java/org/mozilla/gecko/gfx/SubTile.java @@ -86,26 +86,24 @@ public class SubTile extends Layer { protected void finalize() throws Throwable { try { destroyImage(); - cleanTexture(false); + cleanTexture(); } finally { super.finalize(); } } - private void cleanTexture(boolean immediately) { + private void cleanTexture() { if (mTextureIDs != null) { TextureReaper.get().add(mTextureIDs); mTextureIDs = null; - if (immediately) { - TextureReaper.get().reap(); - } + TextureReaper.get().reap(); } } public void destroy() { try { destroyImage(); - cleanTexture(false); + cleanTexture(); } catch (Exception ex) { Log.e(LOGTAG, "Error clearing buffers: ", ex); } @@ -140,7 +138,7 @@ public class SubTile extends Layer { if (!textureSize.equals(mSize)) { mSize = textureSize; - cleanTexture(true); + cleanTexture(); } } @@ -253,4 +251,4 @@ public class SubTile extends Layer { GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } } -}
\ No newline at end of file +} |