Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Browser intent and return to correct activity (close opened tab)

When implementing a custom account type in AccountManager in Android I have the following problem for the sign in flow:

The sign in should happen through an OAuth provider. Therefore I have created a SignInActivity which launches a WebView and starts an OAuth flow. This works fine, when the callback is received to my-custom-scheme://callback the WebView detects it, receives the code querystring parameter and completes the flow. The disadvantage with using a WebView is that even though the user might already have an active session in the browser, this session is not used in the WebView so the user will have to login again in the WebView.

To solve this, I tried switching to using an intent-filter in AndroidManifest.xml, like this:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="my-custom-scheme" android:path="callback"/>
</intent-filter>

Instead of opening a WebView in the SignInActivity, I then launch a browser intent and wait for the browser to hit my-custom-scheme://callback.

Intent browserIntent = new Intent(Intent.ACTION_VIEW, "http://oauth2provider/authorize");
startActivity(browserIntent);
finish();

In my SignInActivity I have the following code to handle the callback:

if (intent != null && intent.getData() != null && getString("my-custom-scheme").equals(intent.getData().getScheme())) {
    String code = getIntent().getData().getQueryParameter("code");
    // complete oauth flow
}

This works. But, to the problem (finally!):

  1. If the user is not signed in, the browser intent will display the sign in page for the oauth provider. After the user has signed in, Chrome will redirect to my-custom-scheme://callback and SignInActivity will launch to handle the intent. As this activity is invisible, the browser will stay open on the sign in page, and to the user it will look like nothing happened. The browser never closes.
  2. If the user is already signed in, the oauth provider will redirect directly to my-custom-scheme://callback. In this case, the browser tab is closed automatically but the browser itself remains open (with no tabs visible).

So my question is: is there anyway to make the browser behave differently after having redirected to my-custom-scheme://callback? Ideally, I would like it to simply close after having redirected to the callback, and to return to the previous activity in the activity stack (i.e. to the activity that started the SignInActivity from the beginning).

like image 394
joscarsson Avatar asked Sep 14 '14 09:09

joscarsson


People also ask

What is the backstack?

A task is a collection of activities that users interact with when trying to do something in your app. These activities are arranged in a stack—the back stack—in the order in which each activity is opened. For example, an email app might have one activity to show a list of new messages.

What is FLAG_ activity_ new_ task?

When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK for a flag to disable this behavior.

What is intent activity in Android?

An Intent is a messaging object you can use to request an action from another app component. Although intents facilitate communication between components in several ways, there are three fundamental use cases: Starting an activity. An Activity represents a single screen in an app.

What is affinity in Android?

Task affinity lets you define which task an activity belongs to. By default, an activity has the same task affinity as its root activity. With task affinity, we can now separate activities into different tasks. <activity android:name=".Activity_A"


1 Answers

I used the next approach to fix the same issue.

Let's assume we have MainActivity with Sign In button. Instead of starting browser directly clicking on that button I'm starting SignInActivity using startActivityForResult method. It's using for then I can handle result of login flow.

startActivityForResult(new Intent(this, SignInActivity.class), requestCode);

SignInActivity is responsible for:

  • open browser with login page
  • catch redirect to custom-scheme://callback
  • finish login flow using token received from redirected url
  • return result back to MainActivity

So, its onCreate method looks like:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.login);

    if (intent != null && intent.getData() != null && "custom-scheme".equals(intent.getData().getScheme())) {
        String code = getIntent().getData().getQueryParameter("code");
       // complete oauth flow
    } else {
        Intent browserIntent = new Intent(Intent.ACTION_VIEW, "http://oauth2provider/authorize")
            .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_FROM_BACKGROUND);
        startActivity(browserIntent);
        finish();
    }
}

Admit flags set into the browser intent.

This way if SignInActivity is opened from MainActivity it just opens login page in the browser and if it's opened catching redirect url it completes login flow sending appropriate request.

After you complete login flow sending the code to some endpoint in your callback method you should do the following:

setResult(Activity.RESULT_OK);
this.finish();

Naturally, you receive access_token from that endpoint. You can store it somewhere either here, in the success callback or return it back to the MainActivity to handle it there.

As layout for SignInActivity you can use just ProgressBar in the center of the page. It will appear during login flow completion after SignInActivity is opened caught redirect url (custom-scheme://callback).

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
>

    <ProgressBar
       android:layout_width="48dp"
       android:layout_height="48dp"
       android:layout_centerVertical="true"
       android:layout_centerHorizontal="true"
    />
</RelativeLayout>

Here is declaration of SignInActivity in AndroidManifest.xml

<activity android:name=".SignInActivity"
          android:launchMode="singleTask"
          android:noHistory="true"
     >
     <intent-filter>
         <action android:name="android.intent.action.VIEW" />
         <category android:name="android.intent.category.DEFAULT"/>
         <category android:name="android.intent.category.BROWSABLE"/>
         <data android:scheme="custom-scheme" android:host="callback"/>
     </intent-filter>
</activity>
like image 118
Andrew Panasiuk Avatar answered Oct 20 '22 12:10

Andrew Panasiuk