我正在尝试将Azure ADAL库合并到我的应用程序中,但在com.microsoft.aad.adal.AuthenticationActivity.onCreate(AuthenticationActivity.java:232)上不断收到Null Pointer异常
其中包含以下几行:
mWebView = (WebView) findViewById(this.getResources().getIdentifier("webView1", "id",
this.getPackageName()));
String userAgent = mWebView.getSettings().getUserAgentString();
这在示例应用程序(https://github.com/Azure-Samples/active-directory-android)中工作正常,但似乎与我的应用程序中的某些内容(资源/布局?)冲突。
只要我调用AuthenticationContext.acquireToken()或AuthenticationContext.aquireTokenSilentAsync(),就会发生这种情况。
如果在ADAL库中实例化mWebView,为什么它为null?
这是我的代码:
package com.solvednetworks.cptool;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.webkit.CookieManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.microsoft.aad.adal.ADALError;
import com.microsoft.aad.adal.AuthenticationCallback;
import com.microsoft.aad.adal.AuthenticationContext;
import com.microsoft.aad.adal.AuthenticationException;
import com.microsoft.aad.adal.AuthenticationResult;
import com.microsoft.aad.adal.IDispatcher;
import com.microsoft.aad.adal.Logger;
import com.microsoft.aad.adal.PromptBehavior;
import com.microsoft.aad.adal.Telemetry;
import com.microsoft.windowsazure.mobileservices.MobileServiceClient;
import com.microsoft.windowsazure.mobileservices.table.MobileServiceTable;
import com.solvednetworks.cptool.barcode.BarcodeCaptureActivity;
import com.microsoft.identity.client.*;
import com.microsoft.identity.client.exception.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public class MainActivity extends AppCompatActivity {
/* UI & Debugging Variables */
private static final String TAG = MainActivity.class.getSimpleName();
Button scanButton;
Button signOutButton;
Button provisionButton;
TextView routerMacTextView;
TextView scannedTextView;
TextView welcome;
EditText coachEditText;
MobileServiceClient mClient;
MobileServiceTable<RouterTable> mRouterTable;
/* Constants for intents */
public static final String BarcodeObject = "Barcode";
private static final int SCANBARCODE = 3;
/* Azure AD Constants */
/* Authority is in the form of https://login.microsoftonline.com/yourtenant.onmicrosoft.com */
private static final String AUTHORITY = "https://login.microsoftonline.com/4f79883f-31e7-4eb7-af5f-def6ba6d099d";
/* The clientID of your application is a unique identifier which can be obtained from the app registration portal */
private static final String CLIENT_ID = "5f9a807c-b262-482b-a51d-dbd0f9d0f65f";
/* Resource URI of the endpoint which will be accessed */
private static final String RESOURCE_ID = "https://graph.microsoft.com/";
/* The Redirect URI of the application (Optional) */
private static final String REDIRECT_URI = "http://localhost";
/* Microsoft Graph Constants */
private final static String MSGRAPH_URL = "https://graph.microsoft.com/v1.0/me";
/* Azure AD Variables */
private AuthenticationContext mAuthContext;
private AuthenticationResult mAuthResult;
/* Handler to do an interactive sign in and acquire token */
private Handler mAcquireTokenHandler;
/* Boolean variable to ensure invocation of interactive sign-in only once in case of multiple acquireTokenSilent call failures */
private static AtomicBoolean sIntSignInInvoked = new AtomicBoolean();
/* Constant to send message to the mAcquireTokenHandler to do acquire token with Prompt.Auto*/
private static final int MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO = 1;
/* Constant to send message to the mAcquireTokenHandler to do acquire token with Prompt.Always */
private static final int MSG_INTERACTIVE_SIGN_IN_PROMPT_ALWAYS = 2;
/* Constant to store user id in shared preferences */
private static final String USER_ID = "user_id";
/* Telemetry variables */
// Flag to turn event aggregation on/off
private static final boolean sTelemetryAggregationIsRequired = true;
/* Telemetry dispatcher registration */
static {
Telemetry.getInstance().registerDispatcher(events -> {
// Events from ADAL will be sent to this callback
for(Map.Entry<String, String> entry: events.entrySet()) {
Log.d(TAG, entry.getKey() + ": " + entry.getValue());
}
}, sTelemetryAggregationIsRequired);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setLogo(R.drawable.kclogo);
getSupportActionBar().setDisplayUseLogoEnabled(true);
routerMacTextView = (TextView) findViewById(R.id.routerMacTextView);
scannedTextView = (TextView) findViewById(R.id.scannedTextView);
coachEditText = (EditText) findViewById(R.id.coachEditText);
scanButton = (Button) findViewById(R.id.scanButton);
signOutButton = (Button) findViewById(R.id.signoutButton);
provisionButton = (Button) findViewById(R.id.provisionButton);
signOutButton.setOnClickListener(v -> onSignOutClicked());
scanButton.setOnClickListener(v -> onScanClicked());
provisionButton.setOnClickListener(v -> onProvisionClicked());
mAuthContext = new AuthenticationContext(getApplicationContext(), AUTHORITY, false);
/* Instantiate handler which can invoke interactive sign-in to get the Resource
* sIntSignInInvoked ensures interactive sign-in is invoked one at a time */
mAcquireTokenHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
if( sIntSignInInvoked.compareAndSet(false, true)) {
if (msg.what == MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO){
mAuthContext.acquireToken(getActivity(), RESOURCE_ID, CLIENT_ID, REDIRECT_URI, PromptBehavior.Auto, getAuthInteractiveCallback());
}else if(msg.what == MSG_INTERACTIVE_SIGN_IN_PROMPT_ALWAYS){
mAuthContext.acquireToken(getActivity(), RESOURCE_ID, CLIENT_ID, REDIRECT_URI, PromptBehavior.Always, getAuthInteractiveCallback());
}
}
}
};
/* ADAL Logging callback setup */
Logger.getInstance().setExternalLogger(new Logger.ILogger() {
@Override
public void Log(String tag, String message, String additionalMessage, Logger.LogLevel level, ADALError errorCode) {
// You can filter the logs depending on level or errorcode.
Log.d(TAG, message + " " + additionalMessage);
}
});
//* Attempt an acquireTokenSilent call to see if we're signed in */
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
String userId = preferences.getString(USER_ID, "");
if(!TextUtils.isEmpty(userId)){
mAuthContext.acquireTokenSilentAsync(RESOURCE_ID, CLIENT_ID, userId, getAuthSilentCallback());
}
try {
mClient = new MobileServiceClient(
"https://azurept.azurewebsites.net",
this);
mRouterTable = mClient.getTable(RouterTable.class);
} catch (Exception e) {
e.printStackTrace();
}
}
//
// Core Auth methods used by ADAL
// ==================================
// onActivityResult() - handles redirect from System browser
// callGraphAPI() - called on successful token acquisition which makes an HTTP request to graph
// onSignOutClicked() - Signs user out of the app & updates UI
//
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
String serial;
super.onActivityResult(requestCode, resultCode, data);
mAuthContext.onActivityResult(requestCode, resultCode, data);
if (requestCode == SCANBARCODE) {
if (resultCode == CommonStatusCodes.SUCCESS) {
try {
serial = data.getData().toString();
scannedTextView.setText(serial);
new GetMac().execute(serial);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private class GetAssetID extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... args) {
String result = "";
URL url;
HttpURLConnection urlConnection = null;
try {
url = new URL("https://cradlepointecm.com/api/v2/routers/?fields=asset_id&mac=" + args[0]);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("X-ECM-API-ID", "b39b3be3-05fc-45cd-90ed-70b5ca1c740c");
urlConnection.setRequestProperty("X-ECM-API-KEY", "c579827643e63c992b14e304cdc1a7fc5f272cb3");
urlConnection.setRequestProperty("X-CP-API-ID", "80780c4c");
urlConnection.setRequestProperty("X-CP-API-KEY", "0c384093805172f14a99670d96b8f190");
InputStream in = (InputStream) urlConnection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(in);
int data = inputStreamReader.read();
while (data != -1) {
char current = (char) data;
result += current;
data = inputStreamReader.read();
}
try {
JSONArray arr = new JSONObject(result).getJSONArray("data");
return arr.getJSONObject(0).getString("asset_id");
} catch (JSONException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String asset) {
super.onPostExecute(asset);
coachEditText.setText(asset);
}
}
private class GetMac extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... args) {
try {
List<RouterTable> results = mRouterTable
.where()
.field("serial").eq(args[0])
.select("mac")
.execute()
.get();
return results.get(0).getMac();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
routerMacTextView.setText(result);
new GetAssetID().execute(result.replace(":", ""));
}
}
private void onProvisionClicked() {
try {
} catch (Exception e) {
e.printStackTrace();
}
}
private void onScanClicked() {
mAcquireTokenHandler.sendEmptyMessage(MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO);
Intent intent = new Intent(this, BarcodeCaptureActivity.class);
startActivityForResult(intent, SCANBARCODE);
}
private void callGraphAPI() {
Log.d(TAG, "Starting volley request to graph");
/* Make sure we have a token to send to graph */
if (mAuthResult.getAccessToken() == null) {return;}
RequestQueue queue = Volley.newRequestQueue(this);
JSONObject parameters = new JSONObject();
try {
parameters.put("key", "value");
} catch (Exception e) {
Log.d(TAG, "Failed to put parameters: " + e.toString());
}
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, MSGRAPH_URL,
parameters,new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
/* Successfully called graph, process data and send to UI */
Log.d(TAG, "Response: " + response.toString());
updateGraphUI(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "Error: " + error.toString());
}
}) {
@Override
public Map<String, String> getHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + mAuthResult.getAccessToken());
return headers;
}
};
Log.d(TAG, "Adding HTTP GET to Queue, Request: " + request.toString());
request.setRetryPolicy(new DefaultRetryPolicy(
3000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
queue.add(request);
}
private void onSignOutClicked() {
// End user has clicked the Sign Out button
// Kill the token cache
// Optionally call the signout endpoint to fully sign out the user account
mAuthContext.getCache().removeAll();
updateSignedOutUI();
}
//
// UI Helper methods
// ================================
// updateGraphUI() - Sets graph response in UI
// updateSuccessUI() - Updates UI when token acquisition succeeds
// updateSignedOutUI() - Updates UI when app sign out succeeds
//
private void updateGraphUI(JSONObject response) {
// Called on success from /me endpoint
// Writes graph data to the UI
Log.d(TAG, "updateGraphUI");
}
@SuppressLint("SetTextI18n")
private void updateSuccessUI() {
// Called on success from /me endpoint
// Removed call Graph API button and paint Sign out
signOutButton.setVisibility(View.VISIBLE);
findViewById(R.id.welcome).setVisibility(View.VISIBLE);
((TextView) findViewById(R.id.welcome)).setText("Welcome, " +
mAuthResult.getUserInfo().getGivenName());
}
@SuppressLint("SetTextI18n")
private void updateSignedOutUI() {
signOutButton.setVisibility(View.INVISIBLE);
findViewById(R.id.welcome).setVisibility(View.INVISIBLE);
}
//
// ADAL Callbacks
// ======================
// getActivity() - returns activity so we can acquireToken within a callback
// getAuthSilentCallback() - callback defined to handle acquireTokenSilent() case
// getAuthInteractiveCallback() - callback defined to handle acquireToken() case
//
public Activity getActivity() {
return this;
}
/* Callback used in for silent acquireToken calls.
* Looks if tokens are in the cache (refreshes if necessary and if we don't forceRefresh)
* else errors that we need to do an interactive request.
*/
private AuthenticationCallback<AuthenticationResult> getAuthSilentCallback() {
return new AuthenticationCallback<AuthenticationResult>() {
@Override
public void onSuccess(AuthenticationResult authenticationResult) {
if(authenticationResult==null || TextUtils.isEmpty(authenticationResult.getAccessToken())
|| authenticationResult.getStatus()!= AuthenticationResult.AuthenticationStatus.Succeeded){
Log.d(TAG, "Silent acquire token Authentication Result is invalid, retrying with interactive");
/* retry with interactive */
mAcquireTokenHandler.sendEmptyMessage(MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO);
return;
}
/* Successfully got a token, call graph now */
Log.d(TAG, "Successfully authenticated");
/* Store the mAuthResult */
mAuthResult = authenticationResult;
/* call graph */
callGraphAPI();
/* update the UI to post call graph state */
runOnUiThread(new Runnable() {
@Override
public void run() {
updateSuccessUI();
}
});
}
@Override
public void onError(Exception exception) {
/* Failed to acquireToken */
Log.e(TAG, "Authentication failed: " + exception.toString());
if (exception instanceof AuthenticationException) {
AuthenticationException authException = ((AuthenticationException) exception);
ADALError error = authException.getCode();
logHttpErrors(authException);
/* Tokens expired or no session, retry with interactive */
if (error == ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED ) {
mAcquireTokenHandler.sendEmptyMessage(MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO);
}else if(error == ADALError.NO_NETWORK_CONNECTION_POWER_OPTIMIZATION){
/* Device is in Doze mode or App is in stand by mode.
Wake up the app or show an appropriate prompt for the user to take action
More information on this : https://github.com/AzureAD/azure-activedirectory-library-for-android/wiki/Handle-Doze-and-App-Standby */
Log.e(TAG, "Device is in doze mode or the app is in standby mode");
}
return;
}
/* Attempt an interactive on any other exception */
mAcquireTokenHandler.sendEmptyMessage(MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO);
}
};
}
private void logHttpErrors(AuthenticationException authException){
int httpResponseCode = authException.getServiceStatusCode();
Log.d(TAG , "HTTP Response code: " + authException.getServiceStatusCode());
if(httpResponseCode< 200 || httpResponseCode >300) {
// logging http response headers in case of a http error.
HashMap<String, List<String>> headers = authException.getHttpResponseHeaders();
if (headers != null) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
sb.append(entry.getKey());
sb.append(":");
sb.append(entry.getValue().toString());
sb.append("; ");
}
Log.e(TAG, "HTTP Response headers: " + sb.toString());
}
}
}
/* Callback used for interactive request. If succeeds we use the access
* token to call the Microsoft Graph. Does not check cache
*/
private AuthenticationCallback<AuthenticationResult> getAuthInteractiveCallback() {
return new AuthenticationCallback<AuthenticationResult>() {
@Override
public void onSuccess(AuthenticationResult authenticationResult) {
if(authenticationResult==null || TextUtils.isEmpty(authenticationResult.getAccessToken())
|| authenticationResult.getStatus()!= AuthenticationResult.AuthenticationStatus.Succeeded){
Log.e(TAG, "Authentication Result is invalid");
return;
}
/* Successfully got a token, call graph now */
Log.d(TAG, "Successfully authenticated");
Log.d(TAG, "ID Token: " + authenticationResult.getIdToken());
/* Store the auth result */
mAuthResult = authenticationResult;
/* Store User id to SharedPreferences to use it to acquire token silently later */
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
preferences.edit().putString(USER_ID, authenticationResult.getUserInfo().getUserId()).apply();
/* call graph */
callGraphAPI();
/* update the UI to post call graph state */
runOnUiThread(new Runnable() {
@Override
public void run() {
updateSuccessUI();
}
});
/* set the sIntSignInInvoked boolean back to false */
sIntSignInInvoked.set(false);
}
@Override
public void onError(Exception exception) {
/* Failed to acquireToken */
Log.e(TAG, "Authentication failed: " + exception.toString());
if (exception instanceof AuthenticationException) {
ADALError error = ((AuthenticationException)exception).getCode();
if(error==ADALError.AUTH_FAILED_CANCELLED){
Log.e(TAG, "The user cancelled the authorization request");
}else if(error== ADALError.AUTH_FAILED_NO_TOKEN){
// In this case ADAL has found a token in cache but failed to retrieve it.
// Retry interactive with Prompt.Always to ensure we do an interactive sign in
mAcquireTokenHandler.sendEmptyMessage(MSG_INTERACTIVE_SIGN_IN_PROMPT_ALWAYS);
}else if(error == ADALError.NO_NETWORK_CONNECTION_POWER_OPTIMIZATION){
/* Device is in Doze mode or App is in stand by mode.
Wake up the app or show an appropriate prompt for the user to take action
More information on this : https://github.com/AzureAD/azure-activedirectory-library-for-android/wiki/Handle-Doze-and-App-Standby */
Log.e(TAG, "Device is in doze mode or the app is in standby mode");
}
}
/* set the sIntSignInInvoked boolean back to false */
sIntSignInInvoked.set(false);
}
};
}
}