com.microsoft.identity.client.testapp.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.identity.client.testapp.MainActivity.java

Source

//  Copyright (c) Microsoft Corporation.
//  All rights reserved.
//
//  This code is licensed under the MIT License.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files(the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions :
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

package com.microsoft.identity.client.testapp;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.Toast;

import com.microsoft.identity.client.AuthenticationCallback;
import com.microsoft.identity.client.AuthenticationResult;
import com.microsoft.identity.client.IAccount;
import com.microsoft.identity.client.ILoggerCallback;
import com.microsoft.identity.client.Logger;
import com.microsoft.identity.client.PublicClientApplication;
import com.microsoft.identity.client.UiBehavior;
import com.microsoft.identity.client.exception.MsalArgumentException;
import com.microsoft.identity.client.exception.MsalClientException;
import com.microsoft.identity.client.exception.MsalException;
import com.microsoft.identity.client.exception.MsalServiceException;
import com.microsoft.identity.client.exception.MsalUiRequiredException;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * The app's main activity.
 */
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
        AcquireTokenFragment.OnFragmentInteractionListener, CacheFragment.OnFragmentInteractionListener {

    private static final String TAG = MainActivity.class.getSimpleName();

    private PublicClientApplication mApplication;
    private IAccount mSelectedAccount;
    private Handler mHandler;

    private String mAuthority;
    private String[] mScopes;
    private UiBehavior mUiBehavior;
    private String mLoginHint;
    private List<Pair<String, String>> mExtraQp;
    private String[] mExtraScopesToConsent;
    private boolean mEnablePiiLogging;
    private boolean mForceRefresh;
    private AuthenticationResult mAuthResult;

    private RelativeLayout mContentMain;

    /**
     * When initializing the {@link PublicClientApplication}, all the apps should only provide us the application context instead of
     * the running activity itself. If running activity itself is provided, that will have the sdk hold a strong reference of the activity
     * which could potentially cause the object not correctly garbage collected and cause activity leak.
     * <p>
     * External Logger should be provided by the Calling app. The sdk logs to the logcat by default, and loglevel is enabled at verbose level.
     * To set external logger,
     * {@link Logger#setExternalLogger(ILoggerCallback)}.
     * To set log level,
     * {@link Logger#setLogLevel(Logger.LogLevel)}
     * By default, the sdk won't give back any Pii logging. However the app can turn it on, this is up to the application's privacy policy.
     * To turn on the Pii logging,
     * {@link Logger#setEnablePII(boolean)}
     * Application can also set the component name. There are cases that other sdks will also take dependency on MSAL i.e. microsoft graph sdk
     * or Intune mam sdk, providing the component name will help separate the logs from application and the logs from the sdk running inside of
     * the apps.
     * <p>
     * For the {@link AuthenticationCallback}, MSAL exposes three results 1) Success, which contains the {@link AuthenticationResult} 2) Failure case,
     * which contains {@link MsalException} and 3) Cancel, specifically for user canceling the flow.
     * <p>
     * For the failure case, MSAL exposes three sub exceptions:
     * 1) {@link MsalClientException}, which is specifically for the exceptions running inside the client app itself, could be no active network,
     * Json parsing failure, etc.
     * 2) {@link MsalServiceException}, which is the error that the sdk gets back when communicating to the service, could be oauth2 errors, socket timout
     * or 500/503/504. For oauth2 erros, MSAL returns back the exact error that server returns back to the sdk.
     * 3) {@link MsalUiRequiredException}, which means that UI is required.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mContentMain = findViewById(R.id.content_main);

        final Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        final DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, 0, 0);
        drawerLayout.addDrawerListener(toggle);
        toggle.syncState();

        final NavigationView navigationView = findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        if (savedInstanceState == null) {
            // auto select the first item
            onNavigationItemSelected(navigationView.getMenu().getItem(0));
        }

        if (mApplication == null) {
            mApplication = new PublicClientApplication(this.getApplicationContext(), R.raw.msal_config);
        }

    }

    @Override
    public boolean onNavigationItemSelected(final MenuItem item) {
        final Fragment fragment;
        int menuItemId = item.getItemId();
        if (menuItemId == R.id.nav_acquire) {
            fragment = new AcquireTokenFragment();
        } else if (menuItemId == R.id.nav_result) {
            fragment = new ResultFragment();
            final Bundle bundle = new Bundle();
            if (mAuthResult != null) {
                bundle.putString(ResultFragment.ACCESS_TOKEN, mAuthResult.getAccessToken());
                bundle.putString(ResultFragment.ID_TOKEN, mAuthResult.getIdToken());
                bundle.putString(ResultFragment.DISPLAYABLE, mAuthResult.getAccount().getUsername());
            }

            fragment.setArguments(bundle);
            mAuthResult = null;
        } else if (menuItemId == R.id.nav_cache) {
            fragment = new CacheFragment();
            final Bundle args = new Bundle();
            args.putSerializable(CacheFragment.ARG_LIST_CONTENTS, (Serializable) CacheFragment.TEST_LIST_ELEMENTS);
            fragment.setArguments(args);
        } else if (menuItemId == R.id.nav_log) {
            fragment = new LogFragment();
            final String logs = ((MsalSampleApp) this.getApplication()).getLogs();
            final Bundle bundle = new Bundle();
            bundle.putString(LogFragment.LOG_MSG, logs);
            fragment.setArguments(bundle);
        } else {
            fragment = null;
        }

        attachFragment(fragment);
        return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        mApplication.handleInteractiveRequestRedirect(requestCode, resultCode, data);
    }

    @Override
    public void onGetUser() {
        final Fragment fragment = new UsersFragment();
        attachFragment(fragment);
    }

    List<IAccount> getAccounts() {
        return mApplication.getAccounts();
    }

    private void attachFragment(final Fragment fragment) {
        final FragmentManager fragmentManager = getSupportFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        final DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
        drawerLayout.closeDrawer(GravityCompat.START);
        fragmentTransaction.replace(mContentMain.getId(), fragment).addToBackStack(null).commit();
    }

    @Override
    public void onAcquireTokenClicked(final AcquireTokenFragment.RequestOptions requestOptions) {
        prepareRequestParameters(requestOptions);

        if (mEnablePiiLogging) {
            Logger.getInstance().setEnableLogcatLog(mEnablePiiLogging);
        }

        callAcquireToken(mScopes, mUiBehavior, mLoginHint, mExtraQp, mExtraScopesToConsent);
    }

    public void onRemoveUserClicked(String username) {
        final List<IAccount> accountsToRemove = mApplication.getAccounts();

        for (final IAccount accountToRemove : accountsToRemove) {
            if (TextUtils.isEmpty(username)
                    || accountToRemove.getUsername().equals(username.trim().toLowerCase())) {
                mApplication.removeAccount(accountToRemove);
            }
        }
    }

    IAccount getAccount(final String loginHint) {
        for (final IAccount account : mApplication.getAccounts()) {
            if (account.getUsername().equals(loginHint.trim().toLowerCase())) {
                return account;
            }
        }

        return null;
    }

    @Override
    public void onAcquireTokenSilentClicked(final AcquireTokenFragment.RequestOptions requestOptions) {
        prepareRequestParameters(requestOptions);

        final IAccount requestAccount = getAccount(requestOptions.getLoginHint());

        callAcquireTokenSilent(mScopes, requestAccount, mForceRefresh);
    }

    @Override
    public void bindSelectAccountSpinner(Spinner selectUser) {
        final ArrayAdapter<String> userAdapter = new ArrayAdapter<>(getApplicationContext(),
                android.R.layout.simple_spinner_item, new ArrayList<String>() {
                    {
                        for (IAccount account : mApplication.getAccounts())
                            add(account.getUsername());
                    }
                });
        userAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        selectUser.setAdapter(userAdapter);
    }

    void prepareRequestParameters(final AcquireTokenFragment.RequestOptions requestOptions) {
        mAuthority = getAuthority(requestOptions.getAuthorityType());
        mLoginHint = requestOptions.getLoginHint();
        mUiBehavior = requestOptions.getUiBehavior();
        mEnablePiiLogging = requestOptions.enablePiiLogging();
        mForceRefresh = requestOptions.forceRefresh();
        Constants.UserAgent userAgent = requestOptions.getUserAgent();

        final String scopes = requestOptions.getScopes();
        if (scopes == null) {
            throw new IllegalArgumentException("null scope");
        }

        mScopes = scopes.toLowerCase().split(" ");
        mExtraScopesToConsent = requestOptions.getExtraScopesToConsent() == null ? null
                : requestOptions.getExtraScopesToConsent().toLowerCase().split(" ");

        if (userAgent.name().equalsIgnoreCase("BROWSER")) {
            mApplication = new PublicClientApplication(this.getApplicationContext(), R.raw.msal_config_browser);
        } else if (userAgent.name().equalsIgnoreCase("WEBVIEW")) {
            mApplication = new PublicClientApplication(this.getApplicationContext(), R.raw.msal_config_webview);
        } else {
            mApplication = new PublicClientApplication(this.getApplicationContext(), R.raw.msal_config);
        }
    }

    final String getAuthority(Constants.AuthorityType authorityTypeType) {
        switch (authorityTypeType) {
        case AAD_COMMON:
            return Constants.AAD_AUTHORITY;
        case B2C:
            return "B2c is not configured yet";
        case AAD_MSDEVEX:
            return Constants.AAD_MSDEVEX;
        case AAD_GUEST:
            return Constants.AAD_GUEST;
        }

        throw new IllegalArgumentException("Not supported authority type");
    }

    void setUser(final IAccount user) {
        mSelectedAccount = user;
    }

    private void callAcquireToken(final String[] scopes, final UiBehavior uiBehavior, final String loginHint,
            final List<Pair<String, String>> extraQueryParam, final String[] extraScope) {
        // The sample app is having the PII enable setting on the MainActivity. Ideally, app should decide to enable Pii or not,
        // if it's enabled, it should be  the setting when the application is onCreate.
        if (mEnablePiiLogging) {
            Logger.getInstance().setEnablePII(true);
        } else {
            Logger.getInstance().setEnablePII(false);
        }

        try {
            mApplication.acquireToken(this, scopes, loginHint, uiBehavior, extraQueryParam, extraScope, null,
                    getAuthenticationCallback());
        } catch (IllegalArgumentException e) {
            showMessage(e.getMessage());
        }
    }

    private void callAcquireTokenSilent(final String[] scopes, final IAccount account, boolean forceRefresh) {
        mApplication.acquireTokenSilentAsync(scopes, account, null, forceRefresh, getAuthenticationCallback());
    }

    private AuthenticationCallback getAuthenticationCallback() {
        return new AuthenticationCallback() {

            @Override
            public void onSuccess(AuthenticationResult authenticationResult) {
                mAuthResult = authenticationResult;
                onNavigationItemSelected(getNavigationView().getMenu().getItem(1));
                mSelectedAccount = null;
            }

            @Override
            public void onError(MsalException exception) {
                // Check the exception type.
                if (exception instanceof MsalClientException) {
                    // This means errors happened in the sdk itself, could be network, Json parse, etc. Check MsalError.java
                    // for detailed list of the errors.
                    showMessage(exception.getMessage());
                } else if (exception instanceof MsalServiceException) {
                    // This means something is wrong when the sdk is communication to the service, mostly likely it's the client
                    // configuration.
                    showMessage(exception.getMessage());
                } else if (exception instanceof MsalArgumentException) {
                    showMessage(exception.getMessage());
                } else if (exception instanceof MsalUiRequiredException) {
                    // This explicitly indicates that developer needs to prompt the user, it could be refresh token is expired, revoked
                    // or user changes the password; or it could be that no token was found in the token cache.
                    callAcquireToken(mScopes, mUiBehavior, mLoginHint, mExtraQp, mExtraScopesToConsent);
                }
            }

            @Override
            public void onCancel() {
                showMessage("User cancelled the flow.");
            }
        };
    }

    private NavigationView getNavigationView() {
        final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        return navigationView;
    }

    private void showMessage(final String msg) {
        getHandler().post(new Runnable() {

            @Override
            public void run() {
                Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
            }
        });
    }

    private Handler getHandler() {
        if (mHandler == null) {
            return new Handler(MainActivity.this.getMainLooper());
        }

        return mHandler;
    }

    @Override
    public void onDeleteToken(int position, final CacheFragment cacheFragment) {
        Log.d(TAG, "onDeleteToken(" + position + ")");
        cacheFragment.setLoading();
        // TODO delete the items or whatever
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                cacheFragment.reload(CacheFragment.TEST_LIST_ELEMENTS);
            }
        }, 750L);
    }
}