/* * Copyright (C) 2014-2017 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 . */ package org.thoughtcrime.securesms; import android.Manifest; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.MenuItem; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; import androidx.core.content.pm.ShortcutManagerCompat; import com.b44t.messenger.DcContext; import java.util.ArrayList; import java.util.List; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.MailtoUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ShareUtil; /** * An activity to quickly share content with chats * * @author Jake McGinty */ public class ShareActivity extends PassphraseRequiredActionBarActivity implements ResolveMediaTask.OnMediaResolvedListener { private static final String TAG = "ShareActivity"; public static final String EXTRA_ACC_ID = "acc_id"; public static final String EXTRA_CHAT_ID = "chat_id"; private ArrayList resolvedExtras; private DcContext dcContext; private boolean isResolvingUrisOnMainThread; @Override protected void onPreCreate() { dynamicTheme = new DynamicNoActionBarTheme(); super.onPreCreate(); } @Override protected void onCreate(Bundle icicle, boolean ready) { dcContext = DcHelper.getContext(this); setContentView(R.layout.share_activity); initializeToolbar(); initializeMedia(); } @Override protected void onNewIntent(Intent intent) { Log.w(TAG, "onNewIntent()"); super.onNewIntent(intent); setIntent(intent); initializeMedia(); } @Override protected void onDestroy() { super.onDestroy(); ResolveMediaTask.cancelTasks(); } private void initializeToolbar() { Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } } private void initializeMedia() { resolvedExtras = new ArrayList<>(); List streamExtras = new ArrayList<>(); if (MailtoUtil.isMailto(getIntent().getData())) { String[] extraEmail = getIntent().getStringArrayExtra(Intent.EXTRA_EMAIL); if (extraEmail == null || extraEmail.length == 0) { getIntent().putExtra(Intent.EXTRA_EMAIL, MailtoUtil.getRecipients(getIntent().getData())); } String text = getIntent().getStringExtra(Intent.EXTRA_TEXT); if (text == null || text.isEmpty()) { getIntent().putExtra(Intent.EXTRA_TEXT, MailtoUtil.getText(getIntent().getData())); } } if (Intent.ACTION_SEND.equals(getIntent().getAction()) && getIntent().getParcelableExtra(Intent.EXTRA_STREAM) != null) { Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM); streamExtras.add(uri); } else if (getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM) != null) { streamExtras = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); } else if (getIntent().getData() != null) { streamExtras.add(getIntent().getData()); } if (needsFilePermission(streamExtras)) { if (Permissions.hasAll(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { resolveUris(streamExtras); } else { requestPermissionForFiles(streamExtras); } } else { resolveUris(streamExtras); } } private boolean needsFilePermission(List uris) { for (Uri uri : uris) { // uri may be null, however, hasFileScheme() just returns false in this case if (hasFileScheme(uri)) { return true; } } return false; } private void requestPermissionForFiles(List streamExtras) { Permissions.with(this) .request( Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) .alwaysGrantOnSdk33() .ifNecessary() .withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied)) .onAllGranted(() -> resolveUris(streamExtras)) .onAnyDenied(this::abortShare) .execute(); } @Override public void onRequestPermissionsResult( int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); } private void abortShare() { Toast.makeText(this, R.string.share_abort, Toast.LENGTH_LONG).show(); finish(); } private void resolveUris(List streamExtras) { isResolvingUrisOnMainThread = true; for (Uri streamExtra : streamExtras) { if (streamExtra != null && PartAuthority.isLocalUri(streamExtra)) { resolvedExtras.add(streamExtra); } else { new ResolveMediaTask(this, this).execute(streamExtra); } } if (!ResolveMediaTask.isExecuting()) { handleResolvedMedia(getIntent()); } isResolvingUrisOnMainThread = false; } @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); switch (item.getItemId()) { case android.R.id.home: finish(); return true; } return false; } @Override public void onMediaResolved(Uri uri) { if (uri != null) { resolvedExtras.add(uri); } if (!ResolveMediaTask.isExecuting() && !isResolvingUrisOnMainThread) { handleResolvedMedia(getIntent()); } } private void handleResolvedMedia(Intent intent) { int accId = intent.getIntExtra(EXTRA_ACC_ID, -1); int chatId = intent.getIntExtra(EXTRA_CHAT_ID, -1); // the intent coming from shortcuts in the share selector might not include the custom extras // but the shortcut ID String shortcutId = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID); if ((chatId == -1 || accId == -1) && shortcutId != null && shortcutId.startsWith("chat-")) { String[] args = shortcutId.split("-"); if (args.length == 3) { accId = Integer.parseInt(args[1]); chatId = Integer.parseInt(args[2]); } } String[] extraEmail = getIntent().getStringArrayExtra(Intent.EXTRA_EMAIL); /* usually, external app will try to start "e-mail sharing" intent, providing it: 1. address(s), packed in array, marked as Intent.EXTRA_EMAIL - mandatory 2. shared content (files, pics, video), packed in Intent.EXTRA_STREAM - optional here is a sample code to trigger this routine from within external app: try { Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts( "mailto", "someone@example.com", null)); File f = new File(getFilesDir() + "/somebinaryfile.bin"); f.createNewFile(); f.setReadable(true, false); byte[] b = new byte[1024]; new Random().nextBytes(b); FileOutputStream fOut = new FileOutputStream(f); DataOutputStream dataStream = new DataOutputStream(fOut); dataStream.write(b); dataStream.close(); Uri sharedURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", f); emailIntent.setAction(Intent.ACTION_SEND); emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"someone@example.com"}); emailIntent.setType("text/plain"); emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); emailIntent.putExtra(Intent.EXTRA_STREAM, sharedURI); // to EXPLICITLY fire DC's sharing activity: // emailIntent.setComponent(new ComponentName("com.b44t.messenger.beta", "org.thoughtcrime.securesms.ShareActivity")); startActivity(emailIntent); } catch (IOException e) { e.printStackTrace(); }catch (ActivityNotFoundException e) { } */ if (chatId == -1 && extraEmail != null && extraEmail.length > 0) { final String addr = extraEmail[0]; int contactId = dcContext.lookupContactIdByAddr(addr); if (contactId == 0) { contactId = dcContext.createContact(null, addr); } chatId = dcContext.createChatByContactId(contactId); accId = dcContext.getAccountId(); } Intent composeIntent; if (accId != -1 && chatId > 0) { composeIntent = getBaseShareIntent(ConversationActivity.class); composeIntent.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId); composeIntent.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, accId); startActivity(composeIntent); } else { composeIntent = getBaseShareIntent(ConversationListRelayingActivity.class); ShareUtil.setIsFromWebxdc(composeIntent, ShareUtil.isFromWebxdc(this)); ConversationListRelayingActivity.start(this, composeIntent); } finish(); } private Intent getBaseShareIntent(final @NonNull Class target) { final Intent intent = new Intent(this, target); ShareUtil.setSharedTitle(intent, ShareUtil.getSharedTitle(this)); ShareUtil.setSharedType(intent, ShareUtil.getSharedType(this)); ShareUtil.setSharedSubject(intent, ShareUtil.getSharedSubject(this)); ShareUtil.setSharedHtml(intent, ShareUtil.getSharedHtml(this)); ShareUtil.setSharedUris(intent, resolvedExtras); String text = getIntent().getStringExtra(Intent.EXTRA_TEXT); if (text == null) { CharSequence cs = getIntent().getCharSequenceExtra(Intent.EXTRA_TEXT); if (cs != null) { text = cs.toString(); } } if (text != null) { ShareUtil.setSharedText(intent, text.toString()); } if (resolvedExtras.size() > 0) { Uri data = resolvedExtras.get(0); if (data != null) { String mimeType = getMimeType(data); intent.setDataAndType(data, mimeType); } } return intent; } private String getMimeType(@Nullable Uri uri) { if (uri != null) { final String mimeType = MediaUtil.getMimeType(getApplicationContext(), uri); if (mimeType != null) return mimeType; } return MediaUtil.getCorrectedMimeType(getIntent().getType()); } private boolean hasFileScheme(Uri uri) { if (uri == null) { return false; } return "file".equals(uri.getScheme()); } }