2017-10-16 13:11:42 -07:00
/ *
2014-08-12 12:11:23 -07:00
* Copyright ( C ) 2014 Open Whisper Systems
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
package org.thoughtcrime.securesms ;
2017-11-24 22:00:30 -08:00
import android.Manifest ;
2018-01-04 11:11:49 -08:00
import android.annotation.SuppressLint ;
2020-11-04 15:51:30 -04:00
import android.content.ActivityNotFoundException ;
2018-01-04 11:11:49 -08:00
import android.content.Context ;
2015-02-10 01:46:43 -08:00
import android.content.Intent ;
2018-01-04 11:11:49 -08:00
import android.database.Cursor ;
2014-08-12 12:11:23 -07:00
import android.net.Uri ;
2017-10-23 13:03:32 -07:00
import android.os.AsyncTask ;
2020-11-04 15:51:30 -04:00
import android.os.Build ;
2014-08-12 12:11:23 -07:00
import android.os.Bundle ;
2014-06-28 20:40:57 -07:00
import android.view.Menu ;
import android.view.MenuInflater ;
import android.view.MenuItem ;
2014-08-12 12:11:23 -07:00
import android.view.View ;
2018-01-04 11:11:49 -08:00
import android.view.ViewGroup ;
2018-11-08 23:33:37 -08:00
import android.widget.TextView ;
2014-08-12 12:11:23 -07:00
import android.widget.Toast ;
2019-07-19 11:43:55 -04:00
import androidx.annotation.NonNull ;
import androidx.annotation.Nullable ;
import androidx.appcompat.app.AlertDialog ;
2020-11-10 10:20:54 -05:00
import androidx.appcompat.app.AppCompatDelegate ;
2020-11-04 15:51:30 -04:00
import androidx.core.app.ShareCompat ;
2019-07-19 11:43:55 -04:00
import androidx.core.util.Pair ;
2019-08-30 17:31:59 -04:00
import androidx.core.view.ViewCompat ;
2019-07-19 11:43:55 -04:00
import androidx.fragment.app.Fragment ;
import androidx.fragment.app.FragmentManager ;
import androidx.fragment.app.FragmentStatePagerAdapter ;
import androidx.lifecycle.ViewModelProviders ;
import androidx.loader.app.LoaderManager ;
import androidx.loader.content.Loader ;
import androidx.recyclerview.widget.LinearLayoutManager ;
import androidx.recyclerview.widget.RecyclerView ;
import androidx.viewpager.widget.ViewPager ;
2020-12-04 18:31:58 -05:00
import org.signal.core.util.logging.Log ;
2019-07-30 14:25:35 -04:00
import org.thoughtcrime.securesms.animation.DepthPageTransformer ;
2018-03-13 18:24:42 -07:00
import org.thoughtcrime.securesms.attachments.DatabaseAttachment ;
2018-01-04 11:11:49 -08:00
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener ;
2019-07-19 11:43:55 -04:00
import org.thoughtcrime.securesms.database.MediaDatabase ;
2018-01-04 11:11:49 -08:00
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord ;
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader ;
2019-12-03 11:05:03 -05:00
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity ;
2019-07-19 11:43:55 -04:00
import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment ;
2018-11-08 23:33:37 -08:00
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel ;
2019-05-02 11:36:20 -03:00
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter ;
2017-10-16 13:11:42 -07:00
import org.thoughtcrime.securesms.mms.GlideApp ;
2020-11-04 15:51:30 -04:00
import org.thoughtcrime.securesms.mms.PartAuthority ;
2017-11-24 22:00:30 -08:00
import org.thoughtcrime.securesms.permissions.Permissions ;
2014-08-12 12:11:23 -07:00
import org.thoughtcrime.securesms.recipients.Recipient ;
2019-08-07 14:22:51 -04:00
import org.thoughtcrime.securesms.recipients.RecipientId ;
2020-02-05 16:34:54 -05:00
import org.thoughtcrime.securesms.sharing.ShareActivity ;
2018-03-15 11:17:40 -07:00
import org.thoughtcrime.securesms.util.AttachmentUtil ;
2014-08-12 12:11:23 -07:00
import org.thoughtcrime.securesms.util.DateUtils ;
2020-09-18 15:55:15 -04:00
import org.thoughtcrime.securesms.util.FullscreenHelper ;
2021-12-13 12:51:53 -05:00
import org.thoughtcrime.securesms.util.MediaUtil ;
2014-08-12 12:11:23 -07:00
import org.thoughtcrime.securesms.util.SaveAttachmentTask ;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment ;
2020-10-19 18:16:29 -03:00
import org.thoughtcrime.securesms.util.StorageUtil ;
2016-11-26 12:10:14 -08:00
2019-07-19 11:43:55 -04:00
import java.util.HashMap ;
import java.util.Locale ;
import java.util.Map ;
2020-07-09 12:19:58 -04:00
import java.util.Objects ;
2014-08-12 12:11:23 -07:00
/ * *
* Activity for displaying media attachments in - app
* /
2020-06-22 17:01:40 -07:00
public final class MediaPreviewActivity extends PassphraseRequiredActivity
2019-08-07 14:22:51 -04:00
implements LoaderManager . LoaderCallbacks < Pair < Cursor , Integer > > ,
2019-07-19 11:43:55 -04:00
MediaRailAdapter . RailItemListener ,
MediaPreviewFragment . Events
2018-11-08 23:33:37 -08:00
{
2018-01-04 11:11:49 -08:00
2021-03-29 18:37:22 -04:00
private final static String TAG = Log . tag ( MediaPreviewActivity . class ) ;
2014-08-12 12:11:23 -07:00
2019-12-03 11:05:03 -05:00
private static final int NOT_IN_A_THREAD = - 2 ;
public static final String THREAD_ID_EXTRA = " thread_id " ;
2018-01-23 12:39:30 -08:00
public static final String DATE_EXTRA = " date " ;
public static final String SIZE_EXTRA = " size " ;
2018-11-08 23:33:37 -08:00
public static final String CAPTION_EXTRA = " caption " ;
2018-01-23 12:39:30 -08:00
public static final String LEFT_IS_RECENT_EXTRA = " left_is_recent " ;
2019-12-03 11:05:03 -05:00
public static final String HIDE_ALL_MEDIA_EXTRA = " came_from_all_media " ;
public static final String SHOW_THREAD_EXTRA = " show_thread " ;
public static final String SORTING_EXTRA = " sorting " ;
2021-06-09 10:23:41 -03:00
public static final String IS_VIDEO_GIF = " is_video_gif " ;
2014-08-12 12:11:23 -07:00
2018-11-08 23:33:37 -08:00
private ViewPager mediaPager ;
private View detailsContainer ;
private TextView caption ;
private View captionContainer ;
private RecyclerView albumRail ;
2018-11-20 09:59:23 -08:00
private MediaRailAdapter albumRailAdapter ;
2018-11-08 23:33:37 -08:00
private ViewGroup playbackControlsContainer ;
private Uri initialMediaUri ;
private String initialMediaType ;
private long initialMediaSize ;
private String initialCaption ;
2021-06-09 10:23:41 -03:00
private boolean initialMediaIsVideoGif ;
2018-11-08 23:33:37 -08:00
private boolean leftIsRecent ;
private MediaPreviewViewModel viewModel ;
2019-05-02 11:36:20 -03:00
private ViewPagerListener viewPagerListener ;
2014-08-12 12:11:23 -07:00
2019-12-03 11:05:03 -05:00
private int restartItem = - 1 ;
private long threadId = NOT_IN_A_THREAD ;
private boolean cameFromAllMedia ;
private boolean showThread ;
private MediaDatabase . Sorting sorting ;
2020-09-18 15:55:15 -04:00
private FullscreenHelper fullscreenHelper ;
2018-01-16 12:06:55 -08:00
2021-06-21 09:55:40 -04:00
private @Nullable Cursor cursor = null ;
2020-07-09 12:19:58 -04:00
2020-04-09 18:09:29 -03:00
public static @NonNull Intent intentFromMediaRecord ( @NonNull Context context ,
@NonNull MediaRecord mediaRecord ,
boolean leftIsRecent )
{
2020-07-09 12:19:58 -04:00
DatabaseAttachment attachment = Objects . requireNonNull ( mediaRecord . getAttachment ( ) ) ;
2020-04-09 18:09:29 -03:00
Intent intent = new Intent ( context , MediaPreviewActivity . class ) ;
intent . putExtra ( MediaPreviewActivity . THREAD_ID_EXTRA , mediaRecord . getThreadId ( ) ) ;
intent . putExtra ( MediaPreviewActivity . DATE_EXTRA , mediaRecord . getDate ( ) ) ;
2020-07-09 12:19:58 -04:00
intent . putExtra ( MediaPreviewActivity . SIZE_EXTRA , attachment . getSize ( ) ) ;
intent . putExtra ( MediaPreviewActivity . CAPTION_EXTRA , attachment . getCaption ( ) ) ;
2020-04-09 18:09:29 -03:00
intent . putExtra ( MediaPreviewActivity . LEFT_IS_RECENT_EXTRA , leftIsRecent ) ;
2021-06-09 10:23:41 -03:00
intent . putExtra ( MediaPreviewActivity . IS_VIDEO_GIF , attachment . isVideoGif ( ) ) ;
2020-09-15 09:27:34 -04:00
intent . setDataAndType ( attachment . getUri ( ) , mediaRecord . getContentType ( ) ) ;
2020-04-09 18:09:29 -03:00
return intent ;
}
2020-11-10 10:20:54 -05:00
@Override
protected void attachBaseContext ( @NonNull Context newBase ) {
getDelegate ( ) . setLocalNightMode ( AppCompatDelegate . MODE_NIGHT_YES ) ;
super . attachBaseContext ( newBase ) ;
}
2018-01-04 11:11:49 -08:00
@SuppressWarnings ( " ConstantConditions " )
2014-08-12 12:11:23 -07:00
@Override
2018-02-01 19:22:48 -08:00
protected void onCreate ( Bundle bundle , boolean ready ) {
2019-08-30 17:31:59 -04:00
this . setTheme ( R . style . TextSecure_MediaPreview ) ;
setContentView ( R . layout . media_preview_activity ) ;
setSupportActionBar ( findViewById ( R . id . toolbar ) ) ;
2014-11-25 08:55:10 +02:00
2018-11-08 23:33:37 -08:00
viewModel = ViewModelProviders . of ( this ) . get ( MediaPreviewViewModel . class ) ;
2021-01-30 11:28:54 -05:00
fullscreenHelper = new FullscreenHelper ( this ) ;
2019-08-30 17:31:59 -04:00
2014-08-12 12:11:23 -07:00
getSupportActionBar ( ) . setDisplayHomeAsUpEnabled ( true ) ;
2014-11-25 08:55:10 +02:00
2015-02-10 01:46:43 -08:00
initializeViews ( ) ;
2014-08-12 12:11:23 -07:00
initializeResources ( ) ;
2018-11-08 23:33:37 -08:00
initializeObservers ( ) ;
}
2021-07-20 13:08:52 -03:00
@SuppressLint ( " MissingSuperCall " )
2017-11-24 22:00:30 -08:00
@Override
public void onRequestPermissionsResult ( int requestCode , @NonNull String [ ] permissions , @NonNull int [ ] grantResults ) {
Permissions . onRequestPermissionsResult ( this , requestCode , permissions , grantResults ) ;
}
2018-11-08 23:33:37 -08:00
@Override
public void onRailItemClicked ( int distanceFromActive ) {
mediaPager . setCurrentItem ( mediaPager . getCurrentItem ( ) + distanceFromActive ) ;
}
2018-11-20 09:59:23 -08:00
@Override
public void onRailItemDeleteClicked ( int distanceFromActive ) {
throw new UnsupportedOperationException ( " Callback unsupported. " ) ;
}
2018-01-04 11:11:49 -08:00
@SuppressWarnings ( " ConstantConditions " )
2015-02-10 01:46:43 -08:00
private void initializeActionBar ( ) {
2018-01-04 11:11:49 -08:00
MediaItem mediaItem = getCurrentMediaItem ( ) ;
2017-10-04 11:35:16 -07:00
2018-01-04 11:11:49 -08:00
if ( mediaItem ! = null ) {
2019-12-03 11:05:03 -05:00
getSupportActionBar ( ) . setTitle ( getTitleText ( mediaItem ) ) ;
getSupportActionBar ( ) . setSubtitle ( getSubTitleText ( mediaItem ) ) ;
}
}
private @NonNull String getTitleText ( @NonNull MediaItem mediaItem ) {
String from ;
if ( mediaItem . outgoing ) from = getString ( R . string . MediaPreviewActivity_you ) ;
2020-06-16 07:37:27 -07:00
else if ( mediaItem . recipient ! = null ) from = mediaItem . recipient . getDisplayName ( this ) ;
2019-12-03 11:05:03 -05:00
else from = " " ;
if ( showThread ) {
String to = null ;
Recipient threadRecipient = mediaItem . threadRecipient ;
if ( threadRecipient ! = null ) {
if ( mediaItem . outgoing | | threadRecipient . isGroup ( ) ) {
2020-10-19 16:27:49 -04:00
if ( threadRecipient . isSelf ( ) ) {
2019-12-03 11:05:03 -05:00
from = getString ( R . string . note_to_self ) ;
} else {
2020-06-16 07:37:27 -07:00
to = threadRecipient . getDisplayName ( this ) ;
2019-12-03 11:05:03 -05:00
}
} else {
to = getString ( R . string . MediaPreviewActivity_you ) ;
}
2018-01-04 11:11:49 -08:00
}
2019-12-03 11:05:03 -05:00
return to ! = null ? getString ( R . string . MediaPreviewActivity_s_to_s , from , to )
: from ;
} else {
return from ;
}
}
2017-10-04 11:35:16 -07:00
2019-12-03 11:05:03 -05:00
private @NonNull String getSubTitleText ( @NonNull MediaItem mediaItem ) {
if ( mediaItem . date > 0 ) {
return DateUtils . getExtendedRelativeTimeSpanString ( this , Locale . getDefault ( ) , mediaItem . date ) ;
} else {
return getString ( R . string . MediaPreviewActivity_draft ) ;
2018-01-04 11:11:49 -08:00
}
2015-02-10 01:46:43 -08:00
}
2014-08-12 12:11:23 -07:00
@Override
public void onResume ( ) {
super . onResume ( ) ;
2018-01-04 11:11:49 -08:00
2015-02-10 01:46:43 -08:00
initializeMedia ( ) ;
}
@Override
public void onPause ( ) {
super . onPause ( ) ;
2018-01-16 12:06:55 -08:00
restartItem = cleanupMedia ( ) ;
2015-02-10 01:46:43 -08:00
}
2020-07-09 12:19:58 -04:00
@Override
protected void onDestroy ( ) {
2021-06-21 09:55:40 -04:00
if ( cursor ! = null ) {
cursor . close ( ) ;
cursor = null ;
}
2020-07-09 12:19:58 -04:00
super . onDestroy ( ) ;
}
2015-02-10 01:46:43 -08:00
@Override
protected void onNewIntent ( Intent intent ) {
super . onNewIntent ( intent ) ;
setIntent ( intent ) ;
initializeResources ( ) ;
}
private void initializeViews ( ) {
2018-01-04 11:11:49 -08:00
mediaPager = findViewById ( R . id . media_pager ) ;
mediaPager . setOffscreenPageLimit ( 1 ) ;
2019-07-30 14:25:35 -04:00
mediaPager . setPageTransformer ( true , new DepthPageTransformer ( ) ) ;
2019-05-02 11:36:20 -03:00
viewPagerListener = new ViewPagerListener ( ) ;
mediaPager . addOnPageChangeListener ( viewPagerListener ) ;
2018-11-08 23:33:37 -08:00
albumRail = findViewById ( R . id . media_preview_album_rail ) ;
2018-11-20 09:59:23 -08:00
albumRailAdapter = new MediaRailAdapter ( GlideApp . with ( this ) , this , false ) ;
2018-11-08 23:33:37 -08:00
2020-09-28 09:45:06 -03:00
albumRail . setItemAnimator ( null ) ; // Or can crash when set to INVISIBLE while animating by FullscreenHelper https://issuetracker.google.com/issues/148720682
2018-11-08 23:33:37 -08:00
albumRail . setLayoutManager ( new LinearLayoutManager ( this , LinearLayoutManager . HORIZONTAL , false ) ) ;
albumRail . setAdapter ( albumRailAdapter ) ;
detailsContainer = findViewById ( R . id . media_preview_details_container ) ;
caption = findViewById ( R . id . media_preview_caption ) ;
captionContainer = findViewById ( R . id . media_preview_caption_container ) ;
playbackControlsContainer = findViewById ( R . id . media_preview_playback_controls_container ) ;
2019-08-30 17:31:59 -04:00
View toolbarLayout = findViewById ( R . id . toolbar_layout ) ;
anchorMarginsToBottomInsets ( detailsContainer ) ;
2020-09-18 15:55:15 -04:00
fullscreenHelper . configureToolbarSpacer ( findViewById ( R . id . toolbar_cutout_spacer ) ) ;
2019-08-30 17:31:59 -04:00
2020-09-18 15:55:15 -04:00
fullscreenHelper . showAndHideWithSystemUI ( getWindow ( ) , detailsContainer , toolbarLayout ) ;
2015-02-10 01:46:43 -08:00
}
2014-08-12 12:11:23 -07:00
2015-02-10 01:46:43 -08:00
private void initializeResources ( ) {
2019-12-03 11:05:03 -05:00
Intent intent = getIntent ( ) ;
threadId = intent . getLongExtra ( THREAD_ID_EXTRA , NOT_IN_A_THREAD ) ;
cameFromAllMedia = intent . getBooleanExtra ( HIDE_ALL_MEDIA_EXTRA , false ) ;
showThread = intent . getBooleanExtra ( SHOW_THREAD_EXTRA , false ) ;
sorting = MediaDatabase . Sorting . values ( ) [ intent . getIntExtra ( SORTING_EXTRA , 0 ) ] ;
2021-06-09 10:23:41 -03:00
initialMediaUri = intent . getData ( ) ;
initialMediaType = intent . getType ( ) ;
initialMediaSize = intent . getLongExtra ( SIZE_EXTRA , 0 ) ;
initialCaption = intent . getStringExtra ( CAPTION_EXTRA ) ;
leftIsRecent = intent . getBooleanExtra ( LEFT_IS_RECENT_EXTRA , false ) ;
initialMediaIsVideoGif = intent . getBooleanExtra ( IS_VIDEO_GIF , false ) ;
restartItem = - 1 ;
2015-02-10 01:46:43 -08:00
}
2014-12-24 14:48:04 -08:00
2018-11-08 23:33:37 -08:00
private void initializeObservers ( ) {
viewModel . getPreviewData ( ) . observe ( this , previewData - > {
2018-11-27 09:32:56 -08:00
if ( previewData = = null | | mediaPager = = null | | mediaPager . getAdapter ( ) = = null ) {
2018-11-08 23:33:37 -08:00
return ;
}
2019-10-23 14:36:50 -07:00
if ( ! ( ( MediaItemAdapter ) mediaPager . getAdapter ( ) ) . hasFragmentFor ( mediaPager . getCurrentItem ( ) ) ) {
Log . d ( TAG , " MediaItemAdapter wasn't ready. Posting again... " ) ;
viewModel . resubmitPreviewData ( ) ;
}
2018-11-08 23:33:37 -08:00
View playbackControls = ( ( MediaItemAdapter ) mediaPager . getAdapter ( ) ) . getPlaybackControls ( mediaPager . getCurrentItem ( ) ) ;
if ( previewData . getAlbumThumbnails ( ) . isEmpty ( ) & & previewData . getCaption ( ) = = null & & playbackControls = = null ) {
detailsContainer . setVisibility ( View . GONE ) ;
} else {
detailsContainer . setVisibility ( View . VISIBLE ) ;
}
albumRail . setVisibility ( previewData . getAlbumThumbnails ( ) . isEmpty ( ) ? View . GONE : View . VISIBLE ) ;
2018-11-20 09:59:23 -08:00
albumRailAdapter . setMedia ( previewData . getAlbumThumbnails ( ) , previewData . getActivePosition ( ) ) ;
2018-11-08 23:33:37 -08:00
albumRail . smoothScrollToPosition ( previewData . getActivePosition ( ) ) ;
captionContainer . setVisibility ( previewData . getCaption ( ) = = null ? View . GONE : View . VISIBLE ) ;
caption . setText ( previewData . getCaption ( ) ) ;
if ( playbackControls ! = null ) {
ViewGroup . LayoutParams params = new ViewGroup . LayoutParams ( ViewGroup . LayoutParams . MATCH_PARENT , ViewGroup . LayoutParams . WRAP_CONTENT ) ;
playbackControls . setLayoutParams ( params ) ;
playbackControlsContainer . removeAllViews ( ) ;
playbackControlsContainer . addView ( playbackControls ) ;
} else {
playbackControlsContainer . removeAllViews ( ) ;
}
} ) ;
}
2015-02-10 01:46:43 -08:00
private void initializeMedia ( ) {
2018-01-04 11:11:49 -08:00
if ( ! isContentTypeSupported ( initialMediaType ) ) {
2014-08-12 12:11:23 -07:00
Log . w ( TAG , " Unsupported media type sent to MediaPreviewActivity, finishing. " ) ;
2014-12-15 09:44:41 -05:00
Toast . makeText ( getApplicationContext ( ) , R . string . MediaPreviewActivity_unssuported_media_type , Toast . LENGTH_LONG ) . show ( ) ;
2014-08-12 12:11:23 -07:00
finish ( ) ;
}
2018-08-02 09:25:33 -04:00
Log . i ( TAG , " Loading Part URI: " + initialMediaUri ) ;
2018-01-04 11:11:49 -08:00
2019-12-03 11:05:03 -05:00
if ( isMediaInDb ( ) ) {
2019-07-19 11:43:55 -04:00
LoaderManager . getInstance ( this ) . restartLoader ( 0 , null , this ) ;
2018-01-04 11:11:49 -08:00
} else {
2021-06-09 10:23:41 -03:00
mediaPager . setAdapter ( new SingleItemPagerAdapter ( getSupportFragmentManager ( ) , initialMediaUri , initialMediaType , initialMediaSize , initialMediaIsVideoGif ) ) ;
2018-11-08 23:33:37 -08:00
if ( initialCaption ! = null ) {
detailsContainer . setVisibility ( View . VISIBLE ) ;
captionContainer . setVisibility ( View . VISIBLE ) ;
caption . setText ( initialCaption ) ;
}
2014-08-12 12:11:23 -07:00
}
}
2018-01-16 12:06:55 -08:00
private int cleanupMedia ( ) {
int restartItem = mediaPager . getCurrentItem ( ) ;
2018-01-04 11:11:49 -08:00
mediaPager . removeAllViews ( ) ;
mediaPager . setAdapter ( null ) ;
2020-07-09 12:19:58 -04:00
viewModel . setCursor ( this , null , leftIsRecent ) ;
2018-01-16 12:06:55 -08:00
return restartItem ;
2014-12-24 14:48:04 -08:00
}
2016-09-20 18:53:44 +02:00
private void showOverview ( ) {
2019-12-03 11:05:03 -05:00
startActivity ( MediaOverviewActivity . forThread ( this , threadId ) ) ;
2016-09-20 18:53:44 +02:00
}
2016-11-03 00:03:03 +01:00
private void forward ( ) {
2018-01-04 11:11:49 -08:00
MediaItem mediaItem = getCurrentMediaItem ( ) ;
if ( mediaItem ! = null ) {
Intent composeIntent = new Intent ( this , ShareActivity . class ) ;
composeIntent . putExtra ( Intent . EXTRA_STREAM , mediaItem . uri ) ;
composeIntent . setType ( mediaItem . type ) ;
startActivity ( composeIntent ) ;
}
2016-11-03 00:03:03 +01:00
}
2020-11-04 15:51:30 -04:00
private void share ( ) {
MediaItem mediaItem = getCurrentMediaItem ( ) ;
if ( mediaItem ! = null ) {
Uri publicUri = PartAuthority . getAttachmentPublicUri ( mediaItem . uri ) ;
String mimeType = Intent . normalizeMimeType ( mediaItem . type ) ;
Intent shareIntent = ShareCompat . IntentBuilder . from ( this )
. setStream ( publicUri )
. setType ( mimeType )
. createChooserIntent ( )
. addFlags ( Intent . FLAG_GRANT_READ_URI_PERMISSION ) ;
try {
startActivity ( shareIntent ) ;
} catch ( ActivityNotFoundException e ) {
Log . w ( TAG , " No activity existed to share the media. " , e ) ;
Toast . makeText ( this , R . string . MediaPreviewActivity_cant_find_an_app_able_to_share_this_media , Toast . LENGTH_LONG ) . show ( ) ;
}
}
}
2018-01-04 11:11:49 -08:00
@SuppressWarnings ( " CodeBlock2Expr " )
@SuppressLint ( " InlinedApi " )
2014-08-12 12:11:23 -07:00
private void saveToDisk ( ) {
2018-01-04 11:11:49 -08:00
MediaItem mediaItem = getCurrentMediaItem ( ) ;
if ( mediaItem ! = null ) {
SaveAttachmentTask . showWarningDialog ( this , ( dialogInterface , i ) - > {
2020-10-19 18:16:29 -03:00
if ( StorageUtil . canWriteToMediaStore ( ) ) {
performSavetoDisk ( mediaItem ) ;
return ;
}
2018-01-04 11:11:49 -08:00
Permissions . with ( this )
2020-10-19 18:16:29 -03:00
. request ( Manifest . permission . WRITE_EXTERNAL_STORAGE )
2018-01-04 11:11:49 -08:00
. ifNecessary ( )
. withPermanentDenialDialog ( getString ( R . string . MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied ) )
. onAnyDenied ( ( ) - > Toast . makeText ( this , R . string . MediaPreviewActivity_unable_to_write_to_external_storage_without_permission , Toast . LENGTH_LONG ) . show ( ) )
. onAllGranted ( ( ) - > {
2020-10-19 18:16:29 -03:00
performSavetoDisk ( mediaItem ) ;
2018-01-04 11:11:49 -08:00
} )
. execute ( ) ;
} ) ;
}
2014-08-12 12:11:23 -07:00
}
2020-10-19 18:16:29 -03:00
private void performSavetoDisk ( @NonNull MediaItem mediaItem ) {
SaveAttachmentTask saveTask = new SaveAttachmentTask ( MediaPreviewActivity . this ) ;
long saveDate = ( mediaItem . date > 0 ) ? mediaItem . date : System . currentTimeMillis ( ) ;
saveTask . executeOnExecutor ( AsyncTask . THREAD_POOL_EXECUTOR , new Attachment ( mediaItem . uri , mediaItem . type , saveDate , null ) ) ;
}
2018-03-13 18:24:42 -07:00
@SuppressLint ( " StaticFieldLeak " )
private void deleteMedia ( ) {
MediaItem mediaItem = getCurrentMediaItem ( ) ;
if ( mediaItem = = null | | mediaItem . attachment = = null ) {
return ;
}
AlertDialog . Builder builder = new AlertDialog . Builder ( this ) ;
2020-11-10 10:20:54 -05:00
builder . setIcon ( R . drawable . ic_warning ) ;
2018-03-13 18:24:42 -07:00
builder . setTitle ( R . string . MediaPreviewActivity_media_delete_confirmation_title ) ;
builder . setMessage ( R . string . MediaPreviewActivity_media_delete_confirmation_message ) ;
builder . setCancelable ( true ) ;
builder . setPositiveButton ( R . string . delete , ( dialogInterface , which ) - > {
new AsyncTask < Void , Void , Void > ( ) {
@Override
protected Void doInBackground ( Void . . . voids ) {
2018-03-15 11:17:40 -07:00
AttachmentUtil . deleteAttachment ( MediaPreviewActivity . this . getApplicationContext ( ) ,
mediaItem . attachment ) ;
2018-03-13 18:24:42 -07:00
return null ;
}
} . execute ( ) ;
finish ( ) ;
} ) ;
builder . setNegativeButton ( android . R . string . cancel , null ) ;
builder . show ( ) ;
}
2014-08-12 12:11:23 -07:00
@Override
2020-08-29 10:45:11 -04:00
public boolean onCreateOptionsMenu ( Menu menu ) {
2014-08-12 12:11:23 -07:00
menu . clear ( ) ;
2014-06-28 20:40:57 -07:00
MenuInflater inflater = this . getMenuInflater ( ) ;
2014-08-12 12:11:23 -07:00
inflater . inflate ( R . menu . media_preview , menu ) ;
2018-03-13 18:24:42 -07:00
2020-08-29 10:45:11 -04:00
super . onCreateOptionsMenu ( menu ) ;
return true ;
}
@Override
public boolean onPrepareOptionsMenu ( Menu menu ) {
2018-03-13 18:24:42 -07:00
if ( ! isMediaInDb ( ) ) {
menu . findItem ( R . id . media_preview__overview ) . setVisible ( false ) ;
menu . findItem ( R . id . delete ) . setVisible ( false ) ;
}
2014-08-12 12:11:23 -07:00
2020-11-04 15:51:30 -04:00
// Restricted to API26 because of MemoryFileUtil not supporting lower API levels well
menu . findItem ( R . id . media_preview__share ) . setVisible ( Build . VERSION . SDK_INT > = 26 ) ;
2019-12-03 11:05:03 -05:00
if ( cameFromAllMedia ) {
menu . findItem ( R . id . media_preview__overview ) . setVisible ( false ) ;
}
2020-08-29 10:45:11 -04:00
super . onPrepareOptionsMenu ( menu ) ;
2014-08-12 12:11:23 -07:00
return true ;
}
@Override
2020-11-04 15:51:30 -04:00
public boolean onOptionsItemSelected ( @NonNull MenuItem item ) {
2014-08-12 12:11:23 -07:00
super . onOptionsItemSelected ( item ) ;
2020-11-04 15:51:30 -04:00
int itemId = item . getItemId ( ) ;
if ( itemId = = R . id . media_preview__overview ) { showOverview ( ) ; return true ; }
if ( itemId = = R . id . media_preview__forward ) { forward ( ) ; return true ; }
if ( itemId = = R . id . media_preview__share ) { share ( ) ; return true ; }
if ( itemId = = R . id . save ) { saveToDisk ( ) ; return true ; }
if ( itemId = = R . id . delete ) { deleteMedia ( ) ; return true ; }
if ( itemId = = android . R . id . home ) { finish ( ) ; return true ; }
2014-08-12 12:11:23 -07:00
return false ;
}
2018-03-13 18:24:42 -07:00
private boolean isMediaInDb ( ) {
2019-12-03 11:05:03 -05:00
return threadId ! = NOT_IN_A_THREAD ;
2018-03-13 18:24:42 -07:00
}
2018-01-04 11:11:49 -08:00
private @Nullable MediaItem getCurrentMediaItem ( ) {
MediaItemAdapter adapter = ( MediaItemAdapter ) mediaPager . getAdapter ( ) ;
if ( adapter ! = null ) {
return adapter . getMediaItemFor ( mediaPager . getCurrentItem ( ) ) ;
} else {
return null ;
}
}
2014-08-12 12:11:23 -07:00
public static boolean isContentTypeSupported ( final String contentType ) {
2021-12-13 12:51:53 -05:00
return MediaUtil . isImageType ( contentType ) | | MediaUtil . isVideoType ( contentType ) ;
2014-08-12 12:11:23 -07:00
}
2018-01-04 11:11:49 -08:00
@Override
2019-05-22 13:51:56 -03:00
public @NonNull Loader < Pair < Cursor , Integer > > onCreateLoader ( int id , Bundle args ) {
2019-12-03 11:05:03 -05:00
return new PagingMediaLoader ( this , threadId , initialMediaUri , leftIsRecent , sorting ) ;
2018-01-04 11:11:49 -08:00
}
@Override
2019-05-22 13:51:56 -03:00
public void onLoadFinished ( @NonNull Loader < Pair < Cursor , Integer > > loader , @Nullable Pair < Cursor , Integer > data ) {
2018-01-04 11:11:49 -08:00
if ( data ! = null ) {
2020-07-09 12:19:58 -04:00
if ( data . first = = cursor ) {
return ;
}
if ( cursor ! = null ) {
cursor . close ( ) ;
}
2021-06-21 09:55:40 -04:00
cursor = Objects . requireNonNull ( data . first ) ;
2020-07-09 12:19:58 -04:00
2022-03-01 09:43:01 -05:00
viewModel . setCursor ( this , cursor , leftIsRecent ) ;
2020-07-09 12:19:58 -04:00
int mediaPosition = Objects . requireNonNull ( data . second ) ;
2022-02-17 11:52:28 -05:00
CursorPagerAdapter oldAdapter = ( CursorPagerAdapter ) mediaPager . getAdapter ( ) ;
if ( oldAdapter = = null ) {
CursorPagerAdapter adapter = new CursorPagerAdapter ( getSupportFragmentManager ( ) , this , cursor , mediaPosition , leftIsRecent ) ;
mediaPager . setAdapter ( adapter ) ;
adapter . setActive ( true ) ;
} else {
oldAdapter . setCursor ( cursor , mediaPosition ) ;
oldAdapter . setActive ( true ) ;
}
2018-01-16 12:06:55 -08:00
2022-03-01 09:43:01 -05:00
if ( oldAdapter = = null | | restartItem > = 0 ) {
int item = restartItem > = 0 ? restartItem : mediaPosition ;
mediaPager . setCurrentItem ( item ) ;
2019-05-02 11:36:20 -03:00
2022-03-01 09:43:01 -05:00
if ( item = = 0 ) {
viewPagerListener . onPageSelected ( 0 ) ;
}
2019-10-23 14:36:50 -07:00
}
2020-07-09 12:19:58 -04:00
} else {
mediaNotAvailable ( ) ;
}
}
2018-01-04 11:11:49 -08:00
@Override
2019-05-22 13:51:56 -03:00
public void onLoaderReset ( @NonNull Loader < Pair < Cursor , Integer > > loader ) {
2018-01-04 11:11:49 -08:00
}
2019-07-19 11:43:55 -04:00
@Override
public boolean singleTapOnMedia ( ) {
2020-09-18 15:55:15 -04:00
fullscreenHelper . toggleUiVisibility ( ) ;
2019-07-19 11:43:55 -04:00
return true ;
}
2020-07-09 12:19:58 -04:00
@Override
public void mediaNotAvailable ( ) {
Toast . makeText ( this , R . string . MediaPreviewActivity_media_no_longer_available , Toast . LENGTH_LONG ) . show ( ) ;
finish ( ) ;
}
2018-01-04 11:11:49 -08:00
private class ViewPagerListener extends ExtendedOnPageChangedListener {
@Override
public void onPageSelected ( int position ) {
super . onPageSelected ( position ) ;
MediaItemAdapter adapter = ( MediaItemAdapter ) mediaPager . getAdapter ( ) ;
if ( adapter ! = null ) {
MediaItem item = adapter . getMediaItemFor ( position ) ;
2021-11-24 16:33:17 -05:00
if ( item ! = null & & item . recipient ! = null ) {
item . recipient . live ( ) . observe ( MediaPreviewActivity . this , r - > initializeActionBar ( ) ) ;
}
2018-11-08 23:33:37 -08:00
viewModel . setActiveAlbumRailItem ( MediaPreviewActivity . this , position ) ;
2018-01-04 11:11:49 -08:00
initializeActionBar ( ) ;
}
}
@Override
public void onPageUnselected ( int position ) {
MediaItemAdapter adapter = ( MediaItemAdapter ) mediaPager . getAdapter ( ) ;
if ( adapter ! = null ) {
MediaItem item = adapter . getMediaItemFor ( position ) ;
2021-11-24 16:33:17 -05:00
if ( item ! = null & & item . recipient ! = null ) {
item . recipient . live ( ) . removeObservers ( MediaPreviewActivity . this ) ;
}
2018-01-04 11:11:49 -08:00
adapter . pause ( position ) ;
}
}
}
2019-07-19 11:43:55 -04:00
private static class SingleItemPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
2018-01-04 11:11:49 -08:00
2021-06-09 10:23:41 -03:00
private final Uri uri ;
private final String mediaType ;
private final long size ;
private final boolean isVideoGif ;
2018-01-04 11:11:49 -08:00
2019-07-19 11:43:55 -04:00
private MediaPreviewFragment mediaPreviewFragment ;
2018-01-04 11:11:49 -08:00
2019-07-19 11:43:55 -04:00
SingleItemPagerAdapter ( @NonNull FragmentManager fragmentManager ,
@NonNull Uri uri ,
@NonNull String mediaType ,
2021-06-09 10:23:41 -03:00
long size ,
boolean isVideoGif )
2018-01-04 11:11:49 -08:00
{
2019-07-19 11:43:55 -04:00
super ( fragmentManager , BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ) ;
2021-06-09 10:23:41 -03:00
this . uri = uri ;
this . mediaType = mediaType ;
this . size = size ;
this . isVideoGif = isVideoGif ;
2018-01-04 11:11:49 -08:00
}
@Override
public int getCount ( ) {
return 1 ;
}
2019-07-19 11:43:55 -04:00
@NonNull
2018-01-04 11:11:49 -08:00
@Override
2019-07-19 11:43:55 -04:00
public Fragment getItem ( int position ) {
2021-06-09 10:23:41 -03:00
mediaPreviewFragment = MediaPreviewFragment . newInstance ( uri , mediaType , size , true , isVideoGif ) ;
2019-07-19 11:43:55 -04:00
return mediaPreviewFragment ;
2018-01-04 11:11:49 -08:00
}
@Override
public void destroyItem ( @NonNull ViewGroup container , int position , @NonNull Object object ) {
2019-07-19 11:43:55 -04:00
if ( mediaPreviewFragment ! = null ) {
mediaPreviewFragment . cleanUp ( ) ;
mediaPreviewFragment = null ;
}
2018-01-04 11:11:49 -08:00
}
@Override
2021-11-24 16:33:17 -05:00
public @Nullable MediaItem getMediaItemFor ( int position ) {
2019-12-03 11:05:03 -05:00
return new MediaItem ( null , null , null , uri , mediaType , - 1 , true ) ;
2018-01-04 11:11:49 -08:00
}
@Override
public void pause ( int position ) {
2019-07-19 11:43:55 -04:00
if ( mediaPreviewFragment ! = null ) {
mediaPreviewFragment . pause ( ) ;
}
2018-01-04 11:11:49 -08:00
}
2018-11-08 23:33:37 -08:00
@Override
public @Nullable View getPlaybackControls ( int position ) {
2019-07-19 11:43:55 -04:00
if ( mediaPreviewFragment ! = null ) {
return mediaPreviewFragment . getPlaybackControls ( ) ;
}
2018-11-08 23:33:37 -08:00
return null ;
}
2019-10-23 14:36:50 -07:00
@Override
public boolean hasFragmentFor ( int position ) {
return mediaPreviewFragment ! = null ;
}
2018-01-04 11:11:49 -08:00
}
2019-08-30 17:31:59 -04:00
private static void anchorMarginsToBottomInsets ( @NonNull View viewToAnchor ) {
ViewCompat . setOnApplyWindowInsetsListener ( viewToAnchor , ( view , insets ) - > {
ViewGroup . MarginLayoutParams layoutParams = ( ViewGroup . MarginLayoutParams ) view . getLayoutParams ( ) ;
layoutParams . setMargins ( insets . getSystemWindowInsetLeft ( ) ,
layoutParams . topMargin ,
insets . getSystemWindowInsetRight ( ) ,
insets . getSystemWindowInsetBottom ( ) ) ;
view . setLayoutParams ( layoutParams ) ;
return insets ;
} ) ;
}
2019-07-19 11:43:55 -04:00
private static class CursorPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
2018-01-04 11:11:49 -08:00
2019-07-19 11:43:55 -04:00
@SuppressLint ( " UseSparseArrays " )
private final Map < Integer , MediaPreviewFragment > mediaFragments = new HashMap < > ( ) ;
2018-01-04 11:11:49 -08:00
2019-07-19 11:43:55 -04:00
private final Context context ;
private final boolean leftIsRecent ;
2018-01-04 11:11:49 -08:00
private boolean active ;
2022-02-17 11:52:28 -05:00
private Cursor cursor ;
2018-01-04 11:11:49 -08:00
private int autoPlayPosition ;
2019-07-19 11:43:55 -04:00
CursorPagerAdapter ( @NonNull FragmentManager fragmentManager ,
@NonNull Context context ,
@NonNull Cursor cursor ,
int autoPlayPosition ,
2018-01-24 19:17:44 -08:00
boolean leftIsRecent )
2018-01-04 11:11:49 -08:00
{
2019-07-19 11:43:55 -04:00
super ( fragmentManager , BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ) ;
2018-01-04 11:11:49 -08:00
this . context = context . getApplicationContext ( ) ;
this . cursor = cursor ;
this . autoPlayPosition = autoPlayPosition ;
2018-01-23 12:39:30 -08:00
this . leftIsRecent = leftIsRecent ;
2018-01-04 11:11:49 -08:00
}
public void setActive ( boolean active ) {
this . active = active ;
notifyDataSetChanged ( ) ;
}
2022-02-17 11:52:28 -05:00
public void setCursor ( @NonNull Cursor cursor , int autoPlayPosition ) {
this . cursor = cursor ;
this . autoPlayPosition = autoPlayPosition ;
}
2018-01-04 11:11:49 -08:00
@Override
public int getCount ( ) {
if ( ! active ) return 0 ;
else return cursor . getCount ( ) ;
}
2019-07-19 11:43:55 -04:00
@NonNull
2018-01-04 11:11:49 -08:00
@Override
2019-07-19 11:43:55 -04:00
public Fragment getItem ( int position ) {
boolean autoPlay = autoPlayPosition = = position ;
int cursorPosition = getCursorPosition ( position ) ;
2018-01-04 11:11:49 -08:00
autoPlayPosition = - 1 ;
2018-01-23 12:39:30 -08:00
cursor . moveToPosition ( cursorPosition ) ;
2018-01-04 11:11:49 -08:00
2019-07-19 11:43:55 -04:00
MediaDatabase . MediaRecord mediaRecord = MediaDatabase . MediaRecord . from ( context , cursor ) ;
2020-07-09 12:19:58 -04:00
DatabaseAttachment attachment = Objects . requireNonNull ( mediaRecord . getAttachment ( ) ) ;
2019-07-19 11:43:55 -04:00
MediaPreviewFragment fragment = MediaPreviewFragment . newInstance ( attachment , autoPlay ) ;
2018-01-04 11:11:49 -08:00
2019-07-19 11:43:55 -04:00
mediaFragments . put ( position , fragment ) ;
2018-01-04 11:11:49 -08:00
2019-07-19 11:43:55 -04:00
return fragment ;
2018-01-04 11:11:49 -08:00
}
@Override
public void destroyItem ( @NonNull ViewGroup container , int position , @NonNull Object object ) {
2019-07-19 11:43:55 -04:00
MediaPreviewFragment removed = mediaFragments . remove ( position ) ;
if ( removed ! = null ) {
removed . cleanUp ( ) ;
}
2018-01-04 11:11:49 -08:00
2019-07-19 11:43:55 -04:00
super . destroyItem ( container , position , object ) ;
2018-01-04 11:11:49 -08:00
}
2021-11-24 16:33:17 -05:00
public @Nullable MediaItem getMediaItemFor ( int position ) {
int cursorPosition = getCursorPosition ( position ) ;
if ( cursor . isClosed ( ) | | cursorPosition < 0 ) {
Log . w ( TAG , " Invalid cursor state! Closed: " + cursor . isClosed ( ) + " Position: " + cursorPosition ) ;
return null ;
}
cursor . moveToPosition ( cursorPosition ) ;
2019-12-03 11:05:03 -05:00
2020-07-09 12:19:58 -04:00
MediaRecord mediaRecord = MediaRecord . from ( context , cursor ) ;
DatabaseAttachment attachment = Objects . requireNonNull ( mediaRecord . getAttachment ( ) ) ;
RecipientId recipientId = mediaRecord . getRecipientId ( ) ;
RecipientId threadRecipientId = mediaRecord . getThreadRecipientId ( ) ;
2018-01-04 11:11:49 -08:00
2019-08-07 14:22:51 -04:00
return new MediaItem ( Recipient . live ( recipientId ) . get ( ) ,
2019-12-04 20:13:51 -05:00
Recipient . live ( threadRecipientId ) . get ( ) ,
2020-07-09 12:19:58 -04:00
attachment ,
2020-09-15 09:27:34 -04:00
Objects . requireNonNull ( attachment . getUri ( ) ) ,
2018-01-04 11:11:49 -08:00
mediaRecord . getContentType ( ) ,
mediaRecord . getDate ( ) ,
mediaRecord . isOutgoing ( ) ) ;
}
@Override
public void pause ( int position ) {
2019-07-19 11:43:55 -04:00
MediaPreviewFragment mediaView = mediaFragments . get ( position ) ;
2018-01-04 11:11:49 -08:00
if ( mediaView ! = null ) mediaView . pause ( ) ;
}
2018-01-23 12:39:30 -08:00
2018-11-08 23:33:37 -08:00
@Override
public @Nullable View getPlaybackControls ( int position ) {
2019-07-19 11:43:55 -04:00
MediaPreviewFragment mediaView = mediaFragments . get ( position ) ;
2018-11-08 23:33:37 -08:00
if ( mediaView ! = null ) return mediaView . getPlaybackControls ( ) ;
return null ;
}
2019-10-23 14:36:50 -07:00
@Override
public boolean hasFragmentFor ( int position ) {
return mediaFragments . containsKey ( position ) ;
}
2018-01-23 12:39:30 -08:00
private int getCursorPosition ( int position ) {
if ( leftIsRecent ) return position ;
else return cursor . getCount ( ) - 1 - position ;
}
2018-01-04 11:11:49 -08:00
}
private static class MediaItem {
2018-03-13 18:24:42 -07:00
private final @Nullable Recipient recipient ;
2019-12-03 11:05:03 -05:00
private final @Nullable Recipient threadRecipient ;
2018-03-13 18:24:42 -07:00
private final @Nullable DatabaseAttachment attachment ;
private final @NonNull Uri uri ;
private final @NonNull String type ;
private final long date ;
private final boolean outgoing ;
private MediaItem ( @Nullable Recipient recipient ,
2019-12-03 11:05:03 -05:00
@Nullable Recipient threadRecipient ,
2018-03-13 18:24:42 -07:00
@Nullable DatabaseAttachment attachment ,
@NonNull Uri uri ,
@NonNull String type ,
long date ,
boolean outgoing )
{
2019-12-03 11:05:03 -05:00
this . recipient = recipient ;
this . threadRecipient = threadRecipient ;
this . attachment = attachment ;
this . uri = uri ;
this . type = type ;
this . date = date ;
this . outgoing = outgoing ;
2018-01-04 11:11:49 -08:00
}
}
interface MediaItemAdapter {
2021-11-24 16:33:17 -05:00
@Nullable MediaItem getMediaItemFor ( int position ) ;
2018-01-04 11:11:49 -08:00
void pause ( int position ) ;
2018-11-08 23:33:37 -08:00
@Nullable View getPlaybackControls ( int position ) ;
2019-10-23 14:36:50 -07:00
boolean hasFragmentFor ( int position ) ;
2018-01-04 11:11:49 -08:00
}
2014-08-12 12:11:23 -07:00
}