package org.thoughtcrime.securesms.connect; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.Settings; import android.util.Log; import android.webkit.MimeTypeMap; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.content.FileProvider; import chat.delta.rpc.Rpc; import chat.delta.rpc.RpcException; import com.b44t.messenger.DcAccounts; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcLot; import com.b44t.messenger.DcMsg; import java.io.File; import java.util.Date; import java.util.HashMap; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.LocalHelpActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.ShareActivity; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.notifications.NotificationCenter; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import org.thoughtcrime.securesms.qr.QrActivity; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ShareUtil; import org.thoughtcrime.securesms.util.Util; public class DcHelper { private static final String TAG = DcHelper.class.getSimpleName(); public static final String CONFIG_CONFIGURED_ADDRESS = "configured_addr"; public static final String CONFIG_DISPLAY_NAME = "displayname"; public static final String CONFIG_SELF_STATUS = "selfstatus"; public static final String CONFIG_SELF_AVATAR = "selfavatar"; public static final String CONFIG_MVBOX_MOVE = "mvbox_move"; public static final String CONFIG_ONLY_FETCH_MVBOX = "only_fetch_mvbox"; public static final String CONFIG_BCC_SELF = "bcc_self"; public static final String CONFIG_SHOW_EMAILS = "show_emails"; public static final String CONFIG_MEDIA_QUALITY = "media_quality"; public static final String CONFIG_PROXY_ENABLED = "proxy_enabled"; public static final String CONFIG_PROXY_URL = "proxy_url"; public static final String CONFIG_PRIVATE_TAG = "private_tag"; public static final String CONFIG_STATS_SENDING = "stats_sending"; public static final String CONFIG_STATS_ID = "stats_id"; public static DcContext getContext(@NonNull Context context) { return ApplicationContext.getInstance(context).getDcContext(); } public static Rpc getRpc(@NonNull Context context) { return ApplicationContext.getInstance(context).getRpc(); } public static DcAccounts getAccounts(@NonNull Context context) { return ApplicationContext.getInstance(context).getDcAccounts(); } public static DcEventCenter getEventCenter(@NonNull Context context) { return ApplicationContext.getInstance(context).getEventCenter(); } public static NotificationCenter getNotificationCenter(@NonNull Context context) { return ApplicationContext.getInstance(context).getNotificationCenter(); } public static boolean isConfigured(Context context) { DcContext dcContext = getContext(context); return dcContext.isConfigured() == 1; } public static int getInt(Context context, String key) { DcContext dcContext = getContext(context); return dcContext.getConfigInt(key); } public static String get(Context context, String key) { DcContext dcContext = getContext(context); return dcContext.getConfig(key); } @Deprecated public static int getInt(Context context, String key, int defaultValue) { return getInt(context, key); } @Deprecated public static String get(Context context, String key, String defaultValue) { return get(context, key); } public static void set(Context context, String key, String value) { DcContext dcContext = getContext(context); dcContext.setConfig(key, value); } public static void setStockTranslations(Context context) { DcContext dcContext = getContext(context); // the integers are defined in the core and used only here, an enum or sth. like that won't have // a big benefit dcContext.setStockTranslation(1, context.getString(R.string.chat_no_messages)); dcContext.setStockTranslation(2, context.getString(R.string.self)); dcContext.setStockTranslation(3, context.getString(R.string.draft)); dcContext.setStockTranslation(7, context.getString(R.string.voice_message)); dcContext.setStockTranslation(9, context.getString(R.string.image)); dcContext.setStockTranslation(10, context.getString(R.string.video)); dcContext.setStockTranslation(11, context.getString(R.string.audio)); dcContext.setStockTranslation(12, context.getString(R.string.file)); dcContext.setStockTranslation(23, context.getString(R.string.gif)); dcContext.setStockTranslation(35, context.getString(R.string.contact_verified)); dcContext.setStockTranslation(40, context.getString(R.string.chat_archived_label)); dcContext.setStockTranslation(60, context.getString(R.string.login_error_cannot_login)); dcContext.setStockTranslation(66, context.getString(R.string.location)); dcContext.setStockTranslation(67, context.getString(R.string.sticker)); dcContext.setStockTranslation(68, context.getString(R.string.device_talk)); dcContext.setStockTranslation(69, context.getString(R.string.saved_messages)); dcContext.setStockTranslation(70, context.getString(R.string.device_talk_explain)); dcContext.setStockTranslation( 71, context.getString( R.string.device_welcome_message, "https://i.delta.chat/#0A45953086F0C166D3BAF1D4BB2025496E4C2704&x=MVPi07rQBEmHO4FRb3brpwDe&j=n8mkKqu42WAKKUCx1bQOVh23&s=AM1YCd_2hKuiSiTEb-2177On&a=arcanechat%40arcanechat.me&n=ArcaneChat&b=ArcaneChat+Channel")); dcContext.setStockTranslation( 73, context.getString(R.string.systemmsg_subject_for_new_contact)); dcContext.setStockTranslation(74, context.getString(R.string.systemmsg_failed_sending_to)); dcContext.setStockTranslation(84, context.getString(R.string.configuration_failed_with_error)); dcContext.setStockTranslation(85, context.getString(R.string.devicemsg_bad_time)); dcContext.setStockTranslation(86, context.getString(R.string.devicemsg_update_reminder)); dcContext.setStockTranslation(90, context.getString(R.string.reply_noun)); dcContext.setStockTranslation(91, context.getString(R.string.devicemsg_self_deleted)); dcContext.setStockTranslation(97, context.getString(R.string.forwarded)); dcContext.setStockTranslation(98, context.getString(R.string.devicemsg_storage_exceeding)); dcContext.setStockTranslation(103, context.getString(R.string.incoming_messages)); dcContext.setStockTranslation(104, context.getString(R.string.outgoing_messages)); dcContext.setStockTranslation(107, context.getString(R.string.connectivity_connected)); dcContext.setStockTranslation(108, context.getString(R.string.connectivity_connecting)); dcContext.setStockTranslation(109, context.getString(R.string.connectivity_updating)); dcContext.setStockTranslation(110, context.getString(R.string.sending)); dcContext.setStockTranslation(111, context.getString(R.string.last_msg_sent_successfully)); dcContext.setStockTranslation(112, context.getString(R.string.error_x)); dcContext.setStockTranslation(113, context.getString(R.string.not_supported_by_provider)); dcContext.setStockTranslation(114, context.getString(R.string.messages)); dcContext.setStockTranslation(116, context.getString(R.string.part_of_total_used)); dcContext.setStockTranslation(117, context.getString(R.string.secure_join_started)); dcContext.setStockTranslation(118, context.getString(R.string.secure_join_replies)); dcContext.setStockTranslation(119, context.getString(R.string.qrshow_join_contact_hint)); // HACK: svg does not handle entities correctly and shows `"` as the text `quot;`. // until that is fixed, we fix the most obvious errors (core uses encode_minimal, so this does // not affect so many characters) // cmp. https://github.com/deltachat/deltachat-android/issues/2187 dcContext.setStockTranslation( 120, context.getString(R.string.qrshow_join_group_hint).replace("\"", "")); dcContext.setStockTranslation(121, context.getString(R.string.connectivity_not_connected)); dcContext.setStockTranslation(124, context.getString(R.string.group_name_changed_by_you)); dcContext.setStockTranslation(125, context.getString(R.string.group_name_changed_by_other)); dcContext.setStockTranslation(126, context.getString(R.string.group_image_changed_by_you)); dcContext.setStockTranslation(127, context.getString(R.string.group_image_changed_by_other)); dcContext.setStockTranslation(128, context.getString(R.string.add_member_by_you)); dcContext.setStockTranslation(129, context.getString(R.string.add_member_by_other)); dcContext.setStockTranslation(130, context.getString(R.string.remove_member_by_you)); dcContext.setStockTranslation(131, context.getString(R.string.remove_member_by_other)); dcContext.setStockTranslation(132, context.getString(R.string.group_left_by_you)); dcContext.setStockTranslation(133, context.getString(R.string.group_left_by_other)); dcContext.setStockTranslation(134, context.getString(R.string.group_image_deleted_by_you)); dcContext.setStockTranslation(135, context.getString(R.string.group_image_deleted_by_other)); dcContext.setStockTranslation(136, context.getString(R.string.location_enabled_by_you)); dcContext.setStockTranslation(137, context.getString(R.string.location_enabled_by_other)); dcContext.setStockTranslation(138, context.getString(R.string.ephemeral_timer_disabled_by_you)); dcContext.setStockTranslation( 139, context.getString(R.string.ephemeral_timer_disabled_by_other)); dcContext.setStockTranslation(140, context.getString(R.string.ephemeral_timer_seconds_by_you)); dcContext.setStockTranslation( 141, context.getString(R.string.ephemeral_timer_seconds_by_other)); dcContext.setStockTranslation(144, context.getString(R.string.ephemeral_timer_1_hour_by_you)); dcContext.setStockTranslation(145, context.getString(R.string.ephemeral_timer_1_hour_by_other)); dcContext.setStockTranslation(146, context.getString(R.string.ephemeral_timer_1_day_by_you)); dcContext.setStockTranslation(147, context.getString(R.string.ephemeral_timer_1_day_by_other)); dcContext.setStockTranslation(148, context.getString(R.string.ephemeral_timer_1_week_by_you)); dcContext.setStockTranslation(149, context.getString(R.string.ephemeral_timer_1_week_by_other)); dcContext.setStockTranslation(150, context.getString(R.string.ephemeral_timer_minutes_by_you)); dcContext.setStockTranslation( 151, context.getString(R.string.ephemeral_timer_minutes_by_other)); dcContext.setStockTranslation(152, context.getString(R.string.ephemeral_timer_hours_by_you)); dcContext.setStockTranslation(153, context.getString(R.string.ephemeral_timer_hours_by_other)); dcContext.setStockTranslation(154, context.getString(R.string.ephemeral_timer_days_by_you)); dcContext.setStockTranslation(155, context.getString(R.string.ephemeral_timer_days_by_other)); dcContext.setStockTranslation(156, context.getString(R.string.ephemeral_timer_weeks_by_you)); dcContext.setStockTranslation(157, context.getString(R.string.ephemeral_timer_weeks_by_other)); dcContext.setStockTranslation(158, context.getString(R.string.ephemeral_timer_1_year_by_you)); dcContext.setStockTranslation(159, context.getString(R.string.ephemeral_timer_1_year_by_other)); dcContext.setStockTranslation(162, context.getString(R.string.multidevice_qr_subtitle)); dcContext.setStockTranslation( 163, context.getString(R.string.multidevice_transfer_done_devicemsg)); dcContext.setStockTranslation( 170, context.getString(R.string.chat_protection_enabled_tap_to_learn_more)); dcContext.setStockTranslation(172, context.getString(R.string.chat_new_group_hint)); dcContext.setStockTranslation(173, context.getString(R.string.member_x_added)); dcContext.setStockTranslation( 174, context.getString(R.string.invalid_unencrypted_tap_to_learn_more)); dcContext.setStockTranslation(176, context.getString(R.string.reaction_by_you)); dcContext.setStockTranslation(177, context.getString(R.string.reaction_by_other)); dcContext.setStockTranslation(178, context.getString(R.string.member_x_removed)); dcContext.setStockTranslation(190, context.getString(R.string.secure_join_wait)); dcContext.setStockTranslation(193, context.getString(R.string.donate_device_msg)); dcContext.setStockTranslation(196, context.getString(R.string.declined_call)); dcContext.setStockTranslation(197, context.getString(R.string.canceled_call)); dcContext.setStockTranslation(198, context.getString(R.string.missed_call)); dcContext.setStockTranslation(200, context.getString(R.string.channel_left_by_you)); dcContext.setStockTranslation(201, context.getString(R.string.qrshow_join_channel_hint)); dcContext.setStockTranslation(202, context.getString(R.string.you_joined_the_channel)); dcContext.setStockTranslation(203, context.getString(R.string.secure_join_channel_started)); dcContext.setStockTranslation(210, context.getString(R.string.stats_msg_body)); dcContext.setStockTranslation(220, context.getString(R.string.proxy_enabled)); dcContext.setStockTranslation(221, context.getString(R.string.proxy_enabled_hint)); dcContext.setStockTranslation(230, context.getString(R.string.chat_unencrypted_explanation)); dcContext.setStockTranslation(232, context.getString(R.string.audio_call)); dcContext.setStockTranslation(233, context.getString(R.string.video_call)); dcContext.setStockTranslation(234, context.getString(R.string.audio_call)); dcContext.setStockTranslation(235, context.getString(R.string.video_call)); dcContext.setStockTranslation(240, context.getString(R.string.chat_description_changed_by_you)); dcContext.setStockTranslation( 241, context.getString(R.string.chat_description_changed_by_other)); dcContext.setStockTranslation(242, context.getString(R.string.messages_are_e2ee)); } public static File getImexDir() { // DIRECTORY_DOCUMENTS could be used but DIRECTORY_DOWNLOADS seems to be easier accessible by // the user, // eg. "Download Managers" are nearly always installed. // CAVE: do not use DownloadManager to add the file as it is deleted on uninstall then ... return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); } // When the user shares a file to another app or opens a file in another app, it is added here. // `HashMap` where `file` is the name of the file in the blobdir (not the // user-visible filename). public static final HashMap sharedFiles = new HashMap<>(); public static void openForViewOrShare(Context activity, int msg_id, String cmd) { DcContext dcContext = getContext(activity); if (!(activity instanceof Activity)) { // would be nicer to accepting only Activity objects, // however, typically in Android just Context objects are passed around (as this normally does // not make a difference). // Accepting only Activity here would force callers to cast, which would easily result in even // more ugliness. Toast.makeText(activity, "openForViewOrShare() expects an Activity object", Toast.LENGTH_LONG) .show(); return; } DcMsg msg = dcContext.getMsg(msg_id); String path = msg.getFile(); String filename = msg.getFilename(); String mimeType = msg.getFilemime(); try { File file = new File(path); if (!file.exists()) { Toast.makeText( activity, activity.getString(R.string.file_not_found, path), Toast.LENGTH_LONG) .show(); return; } Uri uri; mimeType = checkMime(filename, mimeType); if (path.startsWith(dcContext.getBlobdir())) { // Build a Uri that will later be passed to AttachmentsContentProvider.openFile(). // The last part needs to be `filename`, i.e. the original, user-visible name of the file, // so that the external apps show the name of the file correctly. uri = Uri.parse( "content://" + BuildConfig.APPLICATION_ID + ".attachments/" + Uri.encode(file.getName()) + "/" + Uri.encode(filename)); // As different Android version handle uris in putExtra differently, // we also check on our own that the file was actually shared. // The check happens in AttachmentsContentProvider.openFile(). sharedFiles.put(file.getName(), mimeType); } else { if (Build.VERSION.SDK_INT >= 24) { uri = FileProvider.getUriForFile( activity, BuildConfig.APPLICATION_ID + ".fileprovider", file); } else { uri = Uri.fromFile(file); } } if (cmd.equals(Intent.ACTION_VIEW)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(uri, mimeType); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity((Activity) activity, intent); } else { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(mimeType); intent.putExtra(Intent.EXTRA_STREAM, uri); intent.putExtra(Intent.EXTRA_TEXT, msg.getText()); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); activity.startActivity( Intent.createChooser(intent, activity.getString(R.string.chat_share_with_title))); } } catch (RuntimeException e) { Toast.makeText( activity, String.format( "%s (%s)", activity.getString(R.string.no_app_to_handle_data), mimeType), Toast.LENGTH_LONG) .show(); Log.i(TAG, "opening of external activity failed.", e); } } public static void sendToChat( Context activity, byte[] data, String type, String fileName, String html, String subject, String text) { Intent intent = new Intent(activity, ShareActivity.class); intent.setAction(Intent.ACTION_SEND); ShareUtil.setIsFromWebxdc(intent, true); if (data != null) { String mimeType = "application/octet-stream"; Uri uri = PersistentBlobProvider.getInstance().create(activity, data, mimeType, fileName); intent.setType(mimeType); intent.putExtra(Intent.EXTRA_STREAM, uri); ShareUtil.setSharedType(intent, type); ShareUtil.setSharedTitle(intent, activity.getString(R.string.send_file_to, fileName)); } else { ShareUtil.setSharedTitle(intent, activity.getString(R.string.send_message_to)); } if (text != null) { intent.putExtra(Intent.EXTRA_TEXT, text); } if (subject != null) { ShareUtil.setSharedSubject(intent, subject); } if (html != null) { String mimeType = "application/octet-stream"; Uri uri = PersistentBlobProvider.getInstance() .create(activity, html.getBytes(), mimeType, "index.html"); ShareUtil.setSharedHtml(intent, uri); } activity.startActivity(intent); } private static void startActivity(Activity activity, Intent intent) { // request for permission to install apks on API 26+ if intent mimetype is an apk if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && "application/vnd.android.package-archive".equals(intent.getType()) && !activity.getPackageManager().canRequestPackageInstalls()) { activity.startActivity( new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) .setData(Uri.parse(String.format("package:%s", activity.getPackageName())))); return; } activity.startActivity(intent); } public static String checkMime(String path, String mimeType) { if (mimeType == null || mimeType.equals("application/octet-stream")) { path = path.replaceAll(" ", ""); String extension = MediaUtil.getFileExtensionFromUrl(path); String newType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); if (newType != null) return newType; } return mimeType; } /** * Return the path of a not-yet-existing file in the blobdir with roughly the given filename and * the given extension. In many cases, since we're using setFileAndDeduplicate now, this wouldn't * be necessary anymore and we could just create a file with a random filename, but there are a * few usages that still need the current behavior (like `openMaps()`). */ public static String getBlobdirFile(DcContext dcContext, String filename, String ext) { filename = FileUtils.sanitizeFilename(filename); ext = FileUtils.sanitizeFilename(ext); String outPath = null; for (int i = 0; i < 1000; i++) { String test = dcContext.getBlobdir() + "/" + filename + (i == 0 ? "" : i < 100 ? "-" + i : "-" + (new Date().getTime() + i)) + ext; if (!new File(test).exists()) { outPath = test; break; } } if (outPath == null) { // should not happen outPath = dcContext.getBlobdir() + "/" + Math.random(); } return outPath; } public static String getBlobdirFile(DcContext dcContext, String path) { String filename = path.substring( path.lastIndexOf('/') + 1); // is the whole path if '/' is not found (lastIndexOf() returns -1 then) String ext = ""; int point = filename.indexOf('.'); if (point != -1) { ext = filename.substring(point); filename = filename.substring(0, point); } return getBlobdirFile(dcContext, filename, ext); } @NonNull public static ThreadRecord getThreadRecord( Context context, DcLot summary, DcChat chat) { // adapted from ThreadDatabase.getCurrent() int chatId = chat.getId(); String body = summary.getText1(); if (!body.isEmpty()) { body += ": "; } body += summary.getText2(); Recipient recipient = new Recipient(context, chat); long date = summary.getTimestamp(); int unreadCount = getContext(context).getFreshMsgCount(chatId); return new ThreadRecord( body, recipient, date, unreadCount, chatId, chat.getVisibility(), chat.isSendingLocations(), chat.isMuted(), chat.isContactRequest(), summary); } public static boolean isNetworkConnected(Context context) { try { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo netInfo = cm.getActiveNetworkInfo(); if (netInfo != null && netInfo.isConnected()) { return true; } } catch (Exception ignored) { } return false; } /** * Gets a string you can show to the user with basic information about connectivity. * * @param context * @param connectedString Usually "Connected", but when using this as the title in * ConversationListActivity, we want to write "Delta Chat" or the user's display name there * instead. * @return */ public static String getConnectivitySummary(Context context, String connectedString) { int connectivity = getContext(context).getConnectivity(); if (connectivity >= DcContext.DC_CONNECTIVITY_CONNECTED) { return connectedString; } else if (connectivity >= DcContext.DC_CONNECTIVITY_WORKING) { return context.getString(R.string.connectivity_updating); } else if (connectivity >= DcContext.DC_CONNECTIVITY_CONNECTING) { return context.getString(R.string.connectivity_connecting); } else { return context.getString(R.string.connectivity_not_connected); } } public static void showProtectionEnabledDialog(Context context) { new AlertDialog.Builder(context) .setMessage(context.getString(R.string.chat_protection_enabled_explanation)) .setNeutralButton(R.string.learn_more, (d, w) -> openHelp(context, "#e2ee")) .setPositiveButton(R.string.ok, null) .setCancelable(true) .show(); } public static void showInvalidUnencryptedDialog(Context context) { new AlertDialog.Builder(context) .setMessage(context.getString(R.string.invalid_unencrypted_explanation)) .setNeutralButton(R.string.learn_more, (d, w) -> openHelp(context, "#howtoe2ee")) .setNegativeButton( R.string.qrscan_title, (d, w) -> context.startActivity(new Intent(context, QrActivity.class))) .setPositiveButton(R.string.ok, null) .setCancelable(true) .show(); } public static void openHelp(Context context, String section) { Intent intent = new Intent(context, LocalHelpActivity.class); if (section != null) { intent.putExtra(LocalHelpActivity.SECTION_EXTRA, section); } context.startActivity(intent); } /** * For the PGP-Contacts migration, things can go wrong. The migration happens when the account is * setup, at which point no events can be sent yet. So, instead, if something goes wrong, it's * returned by getLastError(). This function shows the error message to the user. * *

A few releases after the PGP-contacts migration (which happened in 2025-05), we can remove * this function again. */ public static void maybeShowMigrationError(Context context) { try { String lastError = DcHelper.getRpc(context).getMigrationError(DcHelper.getContext(context).getAccountId()); if (lastError != null && !lastError.isEmpty()) { Log.w(TAG, "Opening account failed, trying to share error: " + lastError); String subject = "Delta Chat failed to update"; String email = "delta@merlinux.eu"; new AlertDialog.Builder(context) .setMessage(context.getString(R.string.error_x, lastError)) .setNeutralButton( R.string.global_menu_edit_copy_desktop, (d, which) -> { Util.writeTextToClipboard(context, lastError); }) .setPositiveButton( R.string.menu_send, (d, which) -> { Intent sharingIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", email, null)); sharingIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {email}); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); sharingIntent.putExtra(Intent.EXTRA_TEXT, lastError); if (sharingIntent.resolveActivity(context.getPackageManager()) == null) { Log.w(TAG, "No email client found to send crash report"); sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setType("text/plain"); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); sharingIntent.putExtra(Intent.EXTRA_TEXT, lastError); sharingIntent.putExtra(Intent.EXTRA_EMAIL, email); } Intent chooser = Intent.createChooser(sharingIntent, "Send using..."); chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); chooser.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); context.startActivity(chooser); }) .setCancelable(false) .show(); } } catch (RpcException e) { e.printStackTrace(); } } }