Improve account switching intents (#3732)

Before, intent creation was scattered across multiple sites, with account switching logic in both `ComposeActivity` and `MainActivity`.

Now, intents are only created in `MainActivity` Companion, and account switching only occurs in `MainActivity`

Fixes #3695
This commit is contained in:
Konrad Pozniak 2023-08-19 12:31:47 +02:00 committed by GitHub
parent 85f0b1f320
commit dc9e9f2aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 394 additions and 359 deletions

File diff suppressed because it is too large Load Diff

View File

@ -256,9 +256,8 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
public void openAsAccount(@NonNull String url, @NonNull AccountEntity account) { public void openAsAccount(@NonNull String url, @NonNull AccountEntity account) {
accountManager.setActiveAccount(account.getId()); accountManager.setActiveAccount(account.getId());
Intent intent = new Intent(this, MainActivity.class); Intent intent = MainActivity.redirectIntent(this, account.getId(), url);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(MainActivity.REDIRECT_URL, url);
startActivity(intent); startActivity(intent);
finishWithoutSlideOutAnimation(); finishWithoutSlideOutAnimation();
} }

View File

@ -16,6 +16,7 @@
package com.keylesspalace.tusky package com.keylesspalace.tusky
import android.Manifest import android.Manifest
import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
@ -41,6 +42,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.IntentCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
@ -182,30 +184,39 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
?: return // will be redirected to LoginActivity by BaseActivity ?: return // will be redirected to LoginActivity by BaseActivity
var showNotificationTab = false var showNotificationTab = false
if (intent != null) {
// check for savedInstanceState in order to not handle intent events more than once
if (intent != null && savedInstanceState == null) {
val notificationId = intent.getIntExtra(NOTIFICATION_ID, -1)
if (notificationId != -1) {
// opened from a notification action, cancel the notification
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(intent.getStringExtra(NOTIFICATION_TAG), notificationId)
}
/** there are two possibilities the accountId can be passed to MainActivity: /** there are two possibilities the accountId can be passed to MainActivity:
* - from our code as long 'account_id' * - from our code as Long Intent Extra TUSKY_ACCOUNT_ID
* - from share shortcuts as String 'android.intent.extra.shortcut.ID' * - from share shortcuts as String 'android.intent.extra.shortcut.ID'
*/ */
var accountId = intent.getLongExtra(NotificationHelper.ACCOUNT_ID, -1) var tuskyAccountId = intent.getLongExtra(TUSKY_ACCOUNT_ID, -1)
if (accountId == -1L) { if (tuskyAccountId == -1L) {
val accountIdString = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID) val accountIdString = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID)
if (accountIdString != null) { if (accountIdString != null) {
accountId = accountIdString.toLong() tuskyAccountId = accountIdString.toLong()
} }
} }
val accountRequested = accountId != -1L val accountRequested = tuskyAccountId != -1L
if (accountRequested && accountId != activeAccount.id) { if (accountRequested && tuskyAccountId != activeAccount.id) {
accountManager.setActiveAccount(accountId) accountManager.setActiveAccount(tuskyAccountId)
} }
val openDrafts = intent.getBooleanExtra(OPEN_DRAFTS, false) val openDrafts = intent.getBooleanExtra(OPEN_DRAFTS, false)
if (canHandleMimeType(intent.type)) { if (canHandleMimeType(intent.type) || intent.hasExtra(COMPOSE_OPTIONS)) {
// Sharing to Tusky from an external app // Sharing to Tusky from an external app
if (accountRequested) { if (accountRequested) {
// The correct account is already active // The correct account is already active
forwardShare(intent) forwardToComposeActivity(intent)
} else { } else {
// No account was provided, show the chooser // No account was provided, show the chooser
showAccountChooserDialog( showAccountChooserDialog(
@ -216,10 +227,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
val requestedId = account.id val requestedId = account.id
if (requestedId == activeAccount.id) { if (requestedId == activeAccount.id) {
// The correct account is already active // The correct account is already active
forwardShare(intent) forwardToComposeActivity(intent)
} else { } else {
// A different account was requested, restart the activity // A different account was requested, restart the activity
intent.putExtra(NotificationHelper.ACCOUNT_ID, requestedId) intent.putExtra(TUSKY_ACCOUNT_ID, requestedId)
changeAccount(requestedId, intent) changeAccount(requestedId, intent)
} }
} }
@ -229,10 +240,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
} else if (openDrafts) { } else if (openDrafts) {
val intent = DraftsActivity.newIntent(this) val intent = DraftsActivity.newIntent(this)
startActivity(intent) startActivity(intent)
} else if (accountRequested && savedInstanceState == null) { } else if (accountRequested && intent.hasExtra(NOTIFICATION_TYPE)) {
// user clicked a notification, show follow requests for type FOLLOW_REQUEST, // user clicked a notification, show follow requests for type FOLLOW_REQUEST,
// otherwise show notification tab // otherwise show notification tab
if (intent.getStringExtra(NotificationHelper.TYPE) == Notification.Type.FOLLOW_REQUEST.name) { if (intent.getSerializableExtra(NOTIFICATION_TYPE) == Notification.Type.FOLLOW_REQUEST) {
val intent = AccountListActivity.newIntent(this, AccountListActivity.Type.FOLLOW_REQUESTS) val intent = AccountListActivity.newIntent(this, AccountListActivity.Type.FOLLOW_REQUESTS)
startActivityWithSlideInAnimation(intent) startActivityWithSlideInAnimation(intent)
} else { } else {
@ -422,12 +433,19 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
} }
} }
private fun forwardShare(intent: Intent) { private fun forwardToComposeActivity(intent: Intent) {
val composeIntent = Intent(this, ComposeActivity::class.java) val composeOptions = IntentCompat.getParcelableExtra(intent, COMPOSE_OPTIONS, ComposeActivity.ComposeOptions::class.java)
composeIntent.action = intent.action
composeIntent.type = intent.type val composeIntent = if (composeOptions != null) {
composeIntent.putExtras(intent) ComposeActivity.startIntent(this, composeOptions)
composeIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } else {
Intent(this, ComposeActivity::class.java).apply {
action = intent.action
type = intent.type
putExtras(intent)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
}
startActivity(composeIntent) startActivity(composeIntent)
finish() finish()
} }
@ -1043,8 +1061,75 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
private const val TAG = "MainActivity" // logging tag private const val TAG = "MainActivity" // logging tag
private const val DRAWER_ITEM_ADD_ACCOUNT: Long = -13 private const val DRAWER_ITEM_ADD_ACCOUNT: Long = -13
private const val DRAWER_ITEM_ANNOUNCEMENTS: Long = 14 private const val DRAWER_ITEM_ANNOUNCEMENTS: Long = 14
const val REDIRECT_URL = "redirectUrl" private const val REDIRECT_URL = "redirectUrl"
const val OPEN_DRAFTS = "draft" private const val OPEN_DRAFTS = "draft"
private const val TUSKY_ACCOUNT_ID = "tuskyAccountId"
private const val COMPOSE_OPTIONS = "composeOptions"
private const val NOTIFICATION_TYPE = "notificationType"
private const val NOTIFICATION_TAG = "notificationTag"
private const val NOTIFICATION_ID = "notificationId"
/**
* Switches the active account to the provided accountId and then stays on MainActivity
*/
@JvmStatic
fun accountSwitchIntent(context: Context, tuskyAccountId: Long): Intent {
return Intent(context, MainActivity::class.java).apply {
putExtra(TUSKY_ACCOUNT_ID, tuskyAccountId)
}
}
/**
* Switches the active account to the accountId and takes the user to the correct place according to the notification they clicked
*/
@JvmStatic
fun openNotificationIntent(context: Context, tuskyAccountId: Long, type: Notification.Type): Intent {
return accountSwitchIntent(context, tuskyAccountId).apply {
putExtra(NOTIFICATION_TYPE, type)
}
}
/**
* Switches the active account to the accountId and then opens ComposeActivity with the provided options
* @param tuskyAccountId the id of the Tusky account to open the screen with. Set to -1 for current account.
* @param notificationId optional id of the notification that should be cancelled when this intent is opened
* @param notificationTag optional tag of the notification that should be cancelled when this intent is opened
*/
@JvmStatic
fun composeIntent(
context: Context,
options: ComposeActivity.ComposeOptions,
tuskyAccountId: Long = -1,
notificationTag: String? = null,
notificationId: Int = -1
): Intent {
return accountSwitchIntent(context, tuskyAccountId).apply {
action = Intent.ACTION_SEND // so it can be opened via shortcuts
putExtra(COMPOSE_OPTIONS, options)
putExtra(NOTIFICATION_TAG, notificationTag)
putExtra(NOTIFICATION_ID, notificationId)
}
}
/**
* switches the active account to the accountId and then tries to resolve and show the provided url
*/
@JvmStatic
fun redirectIntent(context: Context, tuskyAccountId: Long, url: String): Intent {
return accountSwitchIntent(context, tuskyAccountId).apply {
putExtra(REDIRECT_URL, url)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
}
/**
* switches the active account to the provided accountId and then opens drafts
*/
fun draftIntent(context: Context, tuskyAccountId: Long): Intent {
return accountSwitchIntent(context, tuskyAccountId).apply {
putExtra(OPEN_DRAFTS, true)
}
}
} }
} }

View File

@ -16,7 +16,6 @@
package com.keylesspalace.tusky.components.compose package com.keylesspalace.tusky.components.compose
import android.Manifest import android.Manifest
import android.app.NotificationManager
import android.app.ProgressDialog import android.app.ProgressDialog
import android.content.ClipData import android.content.ClipData
import android.content.Context import android.content.Context
@ -207,22 +206,7 @@ class ComposeActivity :
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val notificationId = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1) activeAccount = accountManager.activeAccount ?: return
if (notificationId != -1) {
// ComposeActivity was opened from a notification, delete the notification
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(notificationId)
}
// If started from an intent then compose as the account ID from the intent.
// Otherwise use the active account. If null then the user is not logged in,
// and return from the activity.
val intentAccountId = intent.getLongExtra(ACCOUNT_ID_EXTRA, -1)
activeAccount = if (intentAccountId != -1L) {
accountManager.getAccountById(intentAccountId)
} else {
accountManager.activeAccount
} ?: return
val theme = preferences.getString("appTheme", APP_THEME_DEFAULT) val theme = preferences.getString("appTheme", APP_THEME_DEFAULT)
if (theme == "black") { if (theme == "black") {
@ -280,7 +264,7 @@ class ComposeActivity :
binding.composeScheduleView.setDateTime(composeOptions?.scheduledAt) binding.composeScheduleView.setDateTime(composeOptions?.scheduledAt)
} }
setupLanguageSpinner(getInitialLanguages(composeOptions?.language, accountManager.activeAccount)) setupLanguageSpinner(getInitialLanguages(composeOptions?.language, activeAccount))
setupComposeField(preferences, viewModel.startingText) setupComposeField(preferences, viewModel.startingText)
setupContentWarningField(composeOptions?.contentWarning) setupContentWarningField(composeOptions?.contentWarning)
setupPollView() setupPollView()
@ -1355,8 +1339,6 @@ class ComposeActivity :
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1 private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS" internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS"
private const val NOTIFICATION_ID_EXTRA = "NOTIFICATION_ID"
private const val ACCOUNT_ID_EXTRA = "ACCOUNT_ID"
private const val PHOTO_UPLOAD_URI_KEY = "PHOTO_UPLOAD_URI" private const val PHOTO_UPLOAD_URI_KEY = "PHOTO_UPLOAD_URI"
private const val VISIBILITY_KEY = "VISIBILITY" private const val VISIBILITY_KEY = "VISIBILITY"
private const val SCHEDULED_TIME_KEY = "SCHEDULE" private const val SCHEDULED_TIME_KEY = "SCHEDULE"
@ -1364,26 +1346,15 @@ class ComposeActivity :
/** /**
* @param options ComposeOptions to configure the ComposeActivity * @param options ComposeOptions to configure the ComposeActivity
* @param notificationId the id of the notification that starts the Activity
* @param accountId the id of the account to compose with, null for the current account
* @return an Intent to start the ComposeActivity * @return an Intent to start the ComposeActivity
*/ */
@JvmStatic @JvmStatic
@JvmOverloads
fun startIntent( fun startIntent(
context: Context, context: Context,
options: ComposeOptions, options: ComposeOptions
notificationId: Int? = null,
accountId: Long? = null
): Intent { ): Intent {
return Intent(context, ComposeActivity::class.java).apply { return Intent(context, ComposeActivity::class.java).apply {
putExtra(COMPOSE_OPTIONS_EXTRA, options) putExtra(COMPOSE_OPTIONS_EXTRA, options)
if (notificationId != null) {
putExtra(NOTIFICATION_ID_EXTRA, notificationId)
}
if (accountId != null) {
putExtra(ACCOUNT_ID_EXTRA, accountId)
}
} }
} }

View File

@ -85,13 +85,6 @@ public class NotificationHelper {
/** Dynamic notification IDs start here */ /** Dynamic notification IDs start here */
private static int notificationId = NOTIFICATION_ID_PRUNE_CACHE + 1; private static int notificationId = NOTIFICATION_ID_PRUNE_CACHE + 1;
/**
* constants used in Intents
*/
public static final String ACCOUNT_ID = "account_id";
public static final String TYPE = APPLICATION_ID + ".notification.type";
private static final String TAG = "NotificationHelper"; private static final String TAG = "NotificationHelper";
public static final String REPLY_ACTION = "REPLY_ACTION"; public static final String REPLY_ACTION = "REPLY_ACTION";
@ -325,11 +318,10 @@ public class NotificationHelper {
// Create a notification that summarises the other notifications in this group // Create a notification that summarises the other notifications in this group
// All notifications in this group have the same type, so get it from the first. // All notifications in this group have the same type, so get it from the first.
String notificationType = members.get(0).getNotification().extras.getString(EXTRA_NOTIFICATION_TYPE); Notification.Type notificationType = (Notification.Type)members.get(0).getNotification().extras.getSerializable(EXTRA_NOTIFICATION_TYPE);
Intent summaryResultIntent = MainActivity.openNotificationIntent(context, accountId, notificationType);
Intent summaryResultIntent = new Intent(context, MainActivity.class);
summaryResultIntent.putExtra(ACCOUNT_ID, (long) accountId);
summaryResultIntent.putExtra(TYPE, notificationType);
TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context); TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context);
summaryStackBuilder.addParentStack(MainActivity.class); summaryStackBuilder.addParentStack(MainActivity.class);
summaryStackBuilder.addNextIntent(summaryResultIntent); summaryStackBuilder.addNextIntent(summaryResultIntent);
@ -373,10 +365,8 @@ public class NotificationHelper {
private static NotificationCompat.Builder newAndroidNotification(Context context, Notification body, AccountEntity account) { private static NotificationCompat.Builder newAndroidNotification(Context context, Notification body, AccountEntity account) {
// we have to switch account here Intent eventResultIntent = MainActivity.openNotificationIntent(context, account.getId(), body.getType());
Intent eventResultIntent = new Intent(context, MainActivity.class);
eventResultIntent.putExtra(ACCOUNT_ID, account.getId());
eventResultIntent.putExtra(TYPE, body.getType().name());
TaskStackBuilder eventStackBuilder = TaskStackBuilder.create(context); TaskStackBuilder eventStackBuilder = TaskStackBuilder.create(context);
eventStackBuilder.addParentStack(MainActivity.class); eventStackBuilder.addParentStack(MainActivity.class);
eventStackBuilder.addNextIntent(eventResultIntent); eventStackBuilder.addNextIntent(eventResultIntent);
@ -464,12 +454,7 @@ public class NotificationHelper {
composeOptions.setLanguage(actionableStatus.getLanguage()); composeOptions.setLanguage(actionableStatus.getLanguage());
composeOptions.setKind(ComposeActivity.ComposeKind.NEW); composeOptions.setKind(ComposeActivity.ComposeKind.NEW);
Intent composeIntent = ComposeActivity.startIntent( Intent composeIntent = MainActivity.composeIntent(context, composeOptions, account.getId(), body.getId(), (int)account.getId());
context,
composeOptions,
notificationId,
account.getId()
);
composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

View File

@ -379,9 +379,7 @@ class SendStatusService : Service(), Injectable {
accountId: Long, accountId: Long,
statusId: Int statusId: Int
): Notification { ): Notification {
val intent = Intent(this, MainActivity::class.java) val intent = MainActivity.draftIntent(this, accountId)
intent.putExtra(NotificationHelper.ACCOUNT_ID, accountId)
intent.putExtra(MainActivity.OPEN_DRAFTS, true)
val pendingIntent = PendingIntent.getActivity( val pendingIntent = PendingIntent.getActivity(
this, this,

View File

@ -19,6 +19,7 @@ import android.annotation.TargetApi
import android.content.Intent import android.content.Intent
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.MainActivity
import com.keylesspalace.tusky.components.compose.ComposeActivity
/** /**
* Small Addition that adds in a QuickSettings tile * Small Addition that adds in a QuickSettings tile
@ -29,11 +30,8 @@ import com.keylesspalace.tusky.MainActivity
class TuskyTileService : TileService() { class TuskyTileService : TileService() {
override fun onClick() { override fun onClick() {
val intent = Intent(this, MainActivity::class.java).apply { val intent = MainActivity.composeIntent(this, ComposeActivity.ComposeOptions())
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
action = Intent.ACTION_SEND
type = "text/plain"
}
startActivityAndCollapse(intent) startActivityAndCollapse(intent)
} }
} }

View File

@ -29,7 +29,6 @@ import androidx.core.graphics.drawable.IconCompat
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.MainActivity
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountEntity
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
@ -72,7 +71,7 @@ fun updateShortcut(context: Context, account: AccountEntity) {
val intent = Intent(context, MainActivity::class.java).apply { val intent = Intent(context, MainActivity::class.java).apply {
action = Intent.ACTION_SEND action = Intent.ACTION_SEND
type = "text/plain" type = "text/plain"
putExtra(NotificationHelper.ACCOUNT_ID, account.id) putExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, account.id.toString())
} }
val shortcutInfo = ShortcutInfoCompat.Builder(context, account.id.toString()) val shortcutInfo = ShortcutInfoCompat.Builder(context, account.id.toString())