diff --git a/src/androidTest/java/com/owncloud/android/ui/helpers/FileOperationsHelperTest.java b/src/androidTest/java/com/owncloud/android/ui/helpers/FileOperationsHelperTest.java new file mode 100644 index 000000000000..0137fbc9a10e --- /dev/null +++ b/src/androidTest/java/com/owncloud/android/ui/helpers/FileOperationsHelperTest.java @@ -0,0 +1,4 @@ +package com.owncloud.android.ui.helpers; + +public class FileOperationsHelperTest { +} diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 4edba5c5e1c1..fd2b53f214f7 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -111,12 +111,14 @@ android:theme="@style/Theme.ownCloud.Launcher"> - + + + diff --git a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index 7815fa40e2e4..cb9619ccbf38 100644 --- a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -163,6 +163,7 @@ public class FileDisplayActivity extends FileActivity public static final String RESTART = "RESTART"; public static final String ALL_FILES = "ALL_FILES"; public static final String PHOTO_SEARCH = "PHOTO_SEARCH"; + public static final String OPEN_FILE = "NC_OPEN_FILE"; private SyncBroadcastReceiver mSyncBroadcastReceiver; private UploadFinishReceiver mUploadFinishReceiver; @@ -329,6 +330,11 @@ public void onClick(View v) { upgradeNotificationForInstantUpload(); checkOutdatedServer(); + + if (OPEN_FILE.equals(getIntent().getAction())) { + getSupportFragmentManager().executePendingTransactions(); + onOpenFileIntent(getIntent()); + } } private Activity getActivity() { @@ -511,6 +517,8 @@ protected void onNewIntent(Intent intent) { showDetails(file); } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { handleOpenFileViaIntent(intent); + } else if (OPEN_FILE.equals(intent.getAction())) { + onOpenFileIntent(intent); } else if (RESTART.equals(intent.getAction())) { finish(); startActivity(intent); @@ -568,6 +576,14 @@ protected void onNewIntent(Intent intent) { } } + private void onOpenFileIntent(Intent intent) { + String extra = intent.getStringExtra(EXTRA_FILE); + OCFile file = getStorageManager().getFileByPath(extra); + + OCFileListFragment fileListFragment = getListOfFilesFragment(); + fileListFragment.onItemClicked(file); + } + private void doShareWith(String shareeName, ShareType shareType) { getFileOperationsHelper().shareFileWithSharee(getFile(), shareeName, shareType, getAppropriatePermissions(shareType)); diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 5d255e9bf659..420ae3ba31b2 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -90,6 +90,7 @@ import com.owncloud.android.ui.events.FavoriteEvent; import com.owncloud.android.ui.events.SearchEvent; import com.owncloud.android.ui.helpers.FileOperationsHelper; +import com.owncloud.android.ui.helpers.MenuHelper; import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface; import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.ui.preview.PreviewMediaFragment; @@ -494,7 +495,7 @@ public void showActivityDetailView(OCFile file) { @Override public void onOverflowIconClicked(OCFile file, View view) { PopupMenu popup = new PopupMenu(getActivity(), view); - popup.inflate(R.menu.item_file); + MenuHelper.inflateItemFileMenu(popup.getMenuInflater(), popup.getMenu()); Account currentAccount = ((FileActivity) getActivity()).getAccount(); FileMenuFilter mf = new FileMenuFilter(mAdapter.getFiles().size(), Collections.singleton(file), @@ -630,7 +631,7 @@ public boolean onCreateActionMode(ActionMode mode, Menu menu) { mActiveActionMode = mode; MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.item_file, menu); + MenuHelper.inflateItemFileMenu(inflater, menu); mode.invalidate(); //set gray color @@ -1109,6 +1110,12 @@ public boolean onFileActionChosen(MenuItem item, Set checkedFiles) { mContainerActivity.getFileOperationsHelper().toggleEncryption(singleFile, false); return true; } + case R.id.action_add_shortcut_to_homescreen: { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + mContainerActivity.getFileOperationsHelper().addShortcutToHomescreen(singleFile); + } + return true; + } } } diff --git a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 79097e48add7..a900cf75c118 100755 --- a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -29,12 +29,20 @@ import android.Manifest; import android.accounts.Account; import android.app.Activity; +import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Build; import android.os.Environment; @@ -45,6 +53,13 @@ import android.view.View; import android.webkit.MimeTypeMap; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.content.FileProvider; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + import com.evernote.android.job.JobRequest; import com.nextcloud.client.account.CurrentAccountProvider; import com.nextcloud.client.account.User; @@ -53,6 +68,7 @@ import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.files.StreamMediaFileOperation; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; @@ -68,6 +84,7 @@ import com.owncloud.android.ui.activity.ConflictsResolveActivity; import com.owncloud.android.ui.activity.ExternalSiteWebView; import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.RichDocumentsEditorWebView; import com.owncloud.android.ui.activity.ShareActivity; import com.owncloud.android.ui.activity.TextEditorWebView; @@ -77,6 +94,7 @@ import com.owncloud.android.ui.events.SyncEventFinished; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.PermissionUtil; import com.owncloud.android.utils.UriUtils; @@ -98,13 +116,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.core.content.FileProvider; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - /** * Helper implementation for file operations locally and remote. */ @@ -1028,6 +1039,73 @@ private void deleteOldFiles(Activity activity) { } } + /** + * Adds a pinned shortcut to the home screen that points to the passed file/folder. + * + * @param file The file/folder to which a pinned shortcut should be added to the home screen. + */ + public void addShortcutToHomescreen(OCFile file) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final ShortcutManager shortcutManager = fileActivity.getSystemService(ShortcutManager.class); + + if (shortcutManager.isRequestPinShortcutSupported()) { + final Intent intent = new Intent(fileActivity, FileDisplayActivity.class); + intent.setAction(FileDisplayActivity.OPEN_FILE); + intent.putExtra(FileActivity.EXTRA_FILE, file.getRemotePath()); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + final String shortcutId = "nextcloud_shortcut_" + file.getRemoteId(); + + Icon icon; + + Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( + ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId()); + if (thumbnail != null) { + thumbnail = bitmapToAdaptiveBitmap(thumbnail, fileActivity); + icon = Icon.createWithAdaptiveBitmap(thumbnail); + } else if (file.isFolder()) { + icon = Icon.createWithResource(fileActivity, + MimeTypeUtil.getFolderTypeIconId(file.isSharedWithMe() || + file.isSharedWithSharee(), file.isSharedViaLink(), file.isEncrypted(), file.getMountType())); + } else { + icon = Icon.createWithResource(fileActivity, + MimeTypeUtil.getFileTypeIconId(file.getMimeType(), file.getFileName())); + } + + final ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(fileActivity, shortcutId) + .setShortLabel(file.getFileName()) + .setLongLabel("Open " + file.getFileName()) + .setIcon(icon) + .setIntent(intent) + .build(); + + final Intent pinnedShortcutCallbackIntent = + shortcutManager.createShortcutResultIntent(pinShortcutInfo); + + final PendingIntent successCallback = PendingIntent.getBroadcast(fileActivity, 0, + pinnedShortcutCallbackIntent, 0); + + shortcutManager.requestPinShortcut(pinShortcutInfo, + successCallback.getIntentSender()); + } + } + } + + private static Bitmap bitmapToAdaptiveBitmap(final Bitmap orig, final Context context) { + final float screenDensity = context.getResources().getDisplayMetrics().density; + final int adaptiveIconSize = Math.round(108 * screenDensity); + final int adaptiveIconOuterSides = Math.round(18 * screenDensity); + + Drawable drawable = new BitmapDrawable(context.getResources(), orig); + + final Bitmap bitmap = Bitmap.createBitmap(adaptiveIconSize, adaptiveIconSize, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(adaptiveIconOuterSides, adaptiveIconOuterSides, adaptiveIconSize - adaptiveIconOuterSides, + adaptiveIconSize - adaptiveIconOuterSides); + drawable.draw(canvas); + return bitmap; + } + public static File createImageFile(Activity activity) { File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES); diff --git a/src/main/java/com/owncloud/android/ui/helpers/MenuHelper.java b/src/main/java/com/owncloud/android/ui/helpers/MenuHelper.java new file mode 100644 index 000000000000..3ce7771198a5 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/helpers/MenuHelper.java @@ -0,0 +1,28 @@ +package com.owncloud.android.ui.helpers; + +import android.os.Build; +import android.view.Menu; +import android.view.MenuInflater; + +import com.owncloud.android.R; + +/** + * Collection of helper functions for handling menus. + */ +public final class MenuHelper { + + private MenuHelper() { + + } + + /** + * Inflates the item file menu and handles menu items according to SDK version. + * + * @param inflater The inflater to use for inflation of the item file menu. + * @param menu The menu into which to inflate. + */ + public static void inflateItemFileMenu(final MenuInflater inflater, final Menu menu) { + inflater.inflate(R.menu.item_file, menu); + menu.findItem(R.id.action_add_shortcut_to_homescreen).setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O); + } +} diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index 9269460f4d79..69d058f77761 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -69,6 +69,7 @@ import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.ui.helpers.MenuHelper; import com.owncloud.android.utils.BitmapUtils; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeType; @@ -322,7 +323,7 @@ public void onStop() { @Override public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.item_file, menu); + MenuHelper.inflateItemFileMenu(inflater, menu); int nightModeFlag = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java index 5484f18895c8..ef4aaade88fe 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -72,6 +72,7 @@ import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.ui.helpers.MenuHelper; import com.owncloud.android.utils.MimeTypeUtil; import java.lang.ref.WeakReference; @@ -340,7 +341,7 @@ private void stopAudio() { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.removeItem(R.id.action_search); - inflater.inflate(R.menu.item_file, menu); + MenuHelper.inflateItemFileMenu(inflater, menu); } @Override diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java index cb899fb76bb6..5b8c9b480100 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java @@ -40,6 +40,7 @@ import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; +import com.owncloud.android.ui.helpers.MenuHelper; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; @@ -235,7 +236,7 @@ protected void onPostExecute(final StringWriter stringWriter) { @Override public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.item_file, menu); + MenuHelper.inflateItemFileMenu(inflater, menu); MenuItem menuItem = menu.findItem(R.id.action_search); menuItem.setVisible(true); diff --git a/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java b/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java index 969ed3058921..5cc9f46a2583 100644 --- a/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java +++ b/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java @@ -161,6 +161,13 @@ public static Drawable getFolderTypeIcon(boolean isSharedViaUsers, boolean isSha public static Drawable getFolderTypeIcon(boolean isSharedViaUsers, boolean isSharedViaLink, boolean isEncrypted, Account account, WebdavEntry.MountType mountType, Context context) { + int drawableId = getFolderTypeIconId(isSharedViaUsers, isSharedViaLink, isEncrypted, mountType); + + return ThemeUtils.tintDrawable(drawableId, ThemeUtils.elementColor(account, context)); + } + + public static int getFolderTypeIconId(boolean isSharedViaUsers, boolean isSharedViaLink, + boolean isEncrypted, WebdavEntry.MountType mountType) { int drawableId; if (isSharedViaLink) { @@ -177,7 +184,7 @@ public static Drawable getFolderTypeIcon(boolean isSharedViaUsers, boolean isSha drawableId = R.drawable.folder; } - return ThemeUtils.tintDrawable(drawableId, ThemeUtils.elementColor(account, context)); + return drawableId; } public static Drawable getDefaultFolderIcon(Context context) { diff --git a/src/main/res/menu/item_file.xml b/src/main/res/menu/item_file.xml index 26a5e3ee7675..04068ade273c 100644 --- a/src/main/res/menu/item_file.xml +++ b/src/main/res/menu/item_file.xml @@ -137,4 +137,10 @@ app:showAsAction="never" android:showAsAction="never" /> + + diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 59d32acaca5a..81909b13a371 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -803,6 +803,7 @@ Was-gibt\'s-Neues-Bild Überspringen Neu in %1$s + Zum Startbildschirm hinzufügen Inhalte von %1$d Datei konnten nicht synchronisiert werden (Konflikte: %2$d) Inhalte von %1$d Dateien konnten nicht synchronisiert werden (Konflikte: %2$d) diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index a8f3d431c62f..bd75ce3f2df0 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -920,6 +920,7 @@ Delete Create new %1$s %2$s + Add to home screen Choose what to sync Free up space