Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Krishna Vemulavada
/
vera_2.1_app
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
47ff318d
authored
Sep 27, 2019
by
mpenmatsa@hycite.com
Browse files
Options
_('Browse Files')
Download
Plain Diff
added new ADALActivity
parents
72c4a0a1
c5ee77ec
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
935 additions
and
57 deletions
app/build.gradle
app/src/main/AndroidManifest.xml
app/src/main/java/com/vsoft/vera/ui/ADALActivity.java
app/src/main/java/com/vsoft/vera/ui/LoginChooseActivity.java
app/src/main/java/com/vsoft/vera/ui/SplashScreen.java
app/src/main/res/layout/activity_login.xml
app/src/main/res/layout/adal_main.xml
build.gradle
gradle/wrapper/gradle-wrapper.properties
app/build.gradle
View file @
47ff318d
...
@@ -32,12 +32,12 @@ android {
...
@@ -32,12 +32,12 @@ android {
}
}
}
}
compileSdkVersion
2
7
compileSdkVersion
2
8
defaultConfig
{
defaultConfig
{
applicationId
"com.vsoft.vera"
applicationId
"com.vsoft.vera"
minSdkVersion
21
minSdkVersion
21
targetSdkVersion
2
7
targetSdkVersion
2
8
versionCode
1
versionCode
1
versionName
"0.0.1"
versionName
"0.0.1"
multiDexEnabled
true
multiDexEnabled
true
...
@@ -68,26 +68,7 @@ android {
...
@@ -68,26 +68,7 @@ android {
}
}
productFlavors
{
productFlavors
{
uofl
{
applicationId
"com.vsoft.vera.uofl"
versionCode
1
versionName
"0.0.1"
}
citrix
{
applicationId
"com.vsoft.vera.citrix"
versionCode
1
versionName
"0.0.1"
}
ge
{
applicationId
"com.vsoft.vera.ge"
versionCode
1
versionName
"0.0.1"
}
arrow
{
applicationId
"com.vsoft.vera.arrow"
versionCode
1
versionName
"0.0.1"
}
vportal
{
vportal
{
applicationId
"com.vsoft.vera.vportal"
applicationId
"com.vsoft.vera.vportal"
versionCode
1
versionCode
1
...
@@ -98,8 +79,14 @@ android {
...
@@ -98,8 +79,14 @@ android {
dependencies
{
dependencies
{
implementation
fileTree
(
include:
[
'*.jar'
],
dir:
'libs'
)
implementation
fileTree
(
include:
[
'*.jar'
],
dir:
'libs'
)
implementation
'com.android.support:design:27.1.1'
implementation
'com.android.support:support-annotations:27.1.1'
implementation
'com.android.volley:volley:1.1.+'
implementation
(
'com.microsoft.aad:adal:1.14.+'
)
{
exclude
group:
'com.android.support'
}
implementation
'com.android.support:design:28.0.0'
implementation
'com.android.support:support-annotations:28.0.0'
implementation
'android.arch.lifecycle:extensions:1.1.1'
implementation
'android.arch.lifecycle:extensions:1.1.1'
testImplementation
'junit:junit:4.12'
testImplementation
'junit:junit:4.12'
implementation
'com.squareup.retrofit2:retrofit:2.3.0'
implementation
'com.squareup.retrofit2:retrofit:2.3.0'
...
@@ -117,12 +104,12 @@ dependencies {
...
@@ -117,12 +104,12 @@ dependencies {
}
}
implementation
'com.squareup.picasso:picasso:2.5.2'
implementation
'com.squareup.picasso:picasso:2.5.2'
implementation
'com.github.nkzawa:socket.io-client:0.3.0'
implementation
'com.github.nkzawa:socket.io-client:0.3.0'
implementation
'com.android.support:appcompat-v7:2
7.1.1
'
implementation
'com.android.support:appcompat-v7:2
8.0.0
'
implementation
'com.android.support:cardview-v7:2
7.1.1
'
implementation
'com.android.support:cardview-v7:2
8.0.0
'
implementation
'com.android.support:recyclerview-v7:2
7.1.1
'
implementation
'com.android.support:recyclerview-v7:2
8.0.0
'
implementation
'com.android.support:animated-vector-drawable:2
7.1.1
'
implementation
'com.android.support:animated-vector-drawable:2
8.0.0
'
implementation
'com.android.support:support-media-compat:2
7.1.1
'
implementation
'com.android.support:support-media-compat:2
8.0.0
'
implementation
'com.android.support:support-v4:2
7.1.1
'
implementation
'com.android.support:support-v4:2
8.0.0
'
implementation
(
'com.google.firebase:firebase-messaging:17.6.0'
)
{
implementation
(
'com.google.firebase:firebase-messaging:17.6.0'
)
{
exclude
group:
'com.google.firebase'
,
module:
'firebase-iid'
exclude
group:
'com.google.firebase'
,
module:
'firebase-iid'
}
}
...
...
app/src/main/AndroidManifest.xml
View file @
47ff318d
...
@@ -41,10 +41,20 @@
...
@@ -41,10 +41,20 @@
android:name=
".ui.LoginScreen"
android:name=
".ui.LoginScreen"
android:screenOrientation=
"portrait"
android:screenOrientation=
"portrait"
android:windowSoftInputMode=
"adjustResize|stateHidden"
/>
android:windowSoftInputMode=
"adjustResize|stateHidden"
/>
<activity
<activity
android:name=
".ui.OtpValidationActivity"
android:name=
".ui.OtpValidationActivity"
android:screenOrientation=
"portrait"
android:screenOrientation=
"portrait"
android:windowSoftInputMode=
"adjustResize|stateHidden"
/>
android:windowSoftInputMode=
"adjustResize|stateHidden"
/>
<activity
android:name=
".ui.LoginChooseActivity"
android:screenOrientation=
"portrait"
/>
<activity
android:name=
".ui.ADALActivity"
android:screenOrientation=
"portrait"
/>
<activity
android:name=
".ui.ResetPasswordActivity"
<activity
android:name=
".ui.ResetPasswordActivity"
android:screenOrientation=
"portrait"
android:screenOrientation=
"portrait"
android:fitsSystemWindows=
"true"
android:fitsSystemWindows=
"true"
...
...
app/src/main/java/com/vsoft/vera/ui/ADALActivity.java
0 → 100755
View file @
47ff318d
package
com
.
vsoft
.
vera
.
ui
;
import
android.annotation.SuppressLint
;
import
android.app.Activity
;
import
android.content.Intent
;
import
android.content.SharedPreferences
;
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.widget.Button
;
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.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.vsoft.vera.R
;
import
org.json.JSONObject
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.atomic.AtomicBoolean
;
public
class
ADALActivity
extends
AppCompatActivity
{
/* UI & Debugging Variables */
private
static
final
String
TAG
=
ADALActivity
.
class
.
getSimpleName
();
Button
callGraphButton
;
Button
signOutButton
;
/* 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/common"
;
/* The clientID of your application is a unique identifier which can be obtained from the app registration portal */
private
static
final
String
CLIENT_ID
=
"b9808fea-11a7-4ed5-aab0-baae8688adb4"
;
/* 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
=
"x-msauth-com.com.vsoft.vmetrics://com.vsoft.vmetrics"
;
/* 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
(
new
IDispatcher
()
{
@Override
public
void
dispatchEvent
(
Map
<
String
,
String
>
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
.
adal_main
);
callGraphButton
=
(
Button
)
findViewById
(
R
.
id
.
callGraph
);
signOutButton
=
(
Button
)
findViewById
(
R
.
id
.
clearCache
);
callGraphButton
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
public
void
onClick
(
View
v
)
{
onCallGraphClicked
();
}
});
signOutButton
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
public
void
onClick
(
View
v
)
{
onSignOutClicked
();
}
});
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
());
}
}
//
// Core Auth methods used by ADAL
// ==================================
// onActivityResult() - handles redirect from System browser
// onCallGraphClicked() - attempts to get tokens for graph, if it succeeds calls graph & updates UI
// 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
)
{
super
.
onActivityResult
(
requestCode
,
resultCode
,
data
);
mAuthContext
.
onActivityResult
(
requestCode
,
resultCode
,
data
);
}
/*
* End user clicked call Graph API button, time for Auth
* Use ADAL to get an Access token for the Microsoft Graph API
*/
private
void
onCallGraphClicked
()
{
mAcquireTokenHandler
.
sendEmptyMessage
(
MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO
);
}
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
TextView
graphText
=
(
TextView
)
findViewById
(
R
.
id
.
graphData
);
graphText
.
setText
(
response
.
toString
());
}
@SuppressLint
(
"SetTextI18n"
)
private
void
updateSuccessUI
()
{
// Called on success from /me endpoint
// Removed call Graph API button and paint Sign out
callGraphButton
.
setVisibility
(
View
.
INVISIBLE
);
signOutButton
.
setVisibility
(
View
.
VISIBLE
);
findViewById
(
R
.
id
.
welcome
).
setVisibility
(
View
.
VISIBLE
);
((
TextView
)
findViewById
(
R
.
id
.
welcome
)).
setText
(
"Welcome, "
+
mAuthResult
.
getUserInfo
().
getGivenName
());
findViewById
(
R
.
id
.
graphData
).
setVisibility
(
View
.
VISIBLE
);
}
@SuppressLint
(
"SetTextI18n"
)
private
void
updateSignedOutUI
()
{
callGraphButton
.
setVisibility
(
View
.
VISIBLE
);
signOutButton
.
setVisibility
(
View
.
INVISIBLE
);
findViewById
(
R
.
id
.
welcome
).
setVisibility
(
View
.
INVISIBLE
);
findViewById
(
R
.
id
.
graphData
).
setVisibility
(
View
.
INVISIBLE
);
((
TextView
)
findViewById
(
R
.
id
.
graphData
)).
setText
(
"No Data"
);
}
//
// 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
);
}
};
}
}
app/src/main/java/com/vsoft/vera/ui/LoginChooseActivity.java
0 → 100644
View file @
47ff318d
package
com
.
vsoft
.
vera
.
ui
;
import
android.annotation.SuppressLint
;
import
android.app.Activity
;
import
android.content.Intent
;
import
android.content.SharedPreferences
;
import
android.os.Bundle
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.os.Message
;
import
android.preference.PreferenceManager
;
import
android.text.TextUtils
;
import
android.util.Log
;
import
android.view.View
;
import
android.widget.Button
;
import
android.widget.EditText
;
import
android.widget.ImageView
;
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.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.vsoft.vera.CatalogueApplication
;
import
com.vsoft.vera.R
;
import
com.vsoft.vera.db.models.UserApiValues
;
import
org.json.JSONObject
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
butterknife.BindView
;
import
butterknife.ButterKnife
;
import
butterknife.OnClick
;
public
class
LoginChooseActivity
extends
Activity
{
TextView
loginSSO
,
loginOTP
;
/* UI & Debugging Variables */
private
static
final
String
TAG
=
ADALActivity
.
class
.
getSimpleName
();
/* 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/common"
;
/* The clientID of your application is a unique identifier which can be obtained from the app registration portal */
private
static
final
String
CLIENT_ID
=
"b9808fea-11a7-4ed5-aab0-baae8688adb4"
;
/* 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
=
"x-msauth-com.com.vsoft.vmetrics://com.vsoft.vmetrics"
;
/* 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
(
new
IDispatcher
()
{
@Override
public
void
dispatchEvent
(
Map
<
String
,
String
>
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
)
{
// TODO Auto-generated method stub
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
activity_login
);
loginSSO
=
findViewById
(
R
.
id
.
login_with_sso
);
loginOTP
=
findViewById
(
R
.
id
.
login_with_otp
);
loginSSO
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
view
)
{
Intent
intent
=
new
Intent
(
LoginChooseActivity
.
this
,
ADALActivity
.
class
);
startActivity
(
intent
);
// onCallGraphClicked();
}
});
loginOTP
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
view
)
{
}
});
// callGraphButton = (Button) findViewById(R.id.callGraph);
// signOutButton = (Button) findViewById(R.id.clearCache);
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
());
}
}
/*
* End user clicked call Graph API button, time for Auth
* Use ADAL to get an Access token for the Microsoft Graph API
*/
private
void
onCallGraphClicked
()
{
mAcquireTokenHandler
.
sendEmptyMessage
(
MSG_INTERACTIVE_SIGN_IN_PROMPT_AUTO
);
}
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
TextView
graphText
=
(
TextView
)
findViewById
(
R
.
id
.
graphData
);
graphText
.
setText
(
response
.
toString
());
}
@SuppressLint
(
"SetTextI18n"
)
private
void
updateSuccessUI
()
{
// Called on success from /me endpoint
// Removed call Graph API button and paint Sign out
findViewById
(
R
.
id
.
welcome
).
setVisibility
(
View
.
VISIBLE
);
((
TextView
)
findViewById
(
R
.
id
.
welcome
)).
setText
(
"Welcome, "
+
mAuthResult
.
getUserInfo
().
getGivenName
());
findViewById
(
R
.
id
.
graphData
).
setVisibility
(
View
.
VISIBLE
);
}
@SuppressLint
(
"SetTextI18n"
)
private
void
updateSignedOutUI
()
{
findViewById
(
R
.
id
.
welcome
).
setVisibility
(
View
.
INVISIBLE
);
findViewById
(
R
.
id
.
graphData
).
setVisibility
(
View
.
INVISIBLE
);
((
TextView
)
findViewById
(
R
.
id
.
graphData
)).
setText
(
"No Data"
);
}
//
// 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
);
}
};
}
}
app/src/main/java/com/vsoft/vera/ui/SplashScreen.java
View file @
47ff318d
...
@@ -49,7 +49,7 @@ public class SplashScreen extends Activity {
...
@@ -49,7 +49,7 @@ public class SplashScreen extends Activity {
public
void
run
()
{
public
void
run
()
{
// This method will be executed once the timer is over
// This method will be executed once the timer is over
// Start your app main activity
// Start your app main activity
Intent
i
=
new
Intent
(
SplashScreen
.
this
,
Login
Screen
.
class
);
Intent
i
=
new
Intent
(
SplashScreen
.
this
,
Login
ChooseActivity
.
class
);
startActivity
(
i
);
startActivity
(
i
);
// close this activity
// close this activity
...
...
app/src/main/res/layout/activity_login.xml
View file @
47ff318d
...
@@ -14,43 +14,37 @@
...
@@ -14,43 +14,37 @@
<ImageView
<ImageView
android:layout_width=
"wrap_content"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_height=
"wrap_content"
android:visibility=
"visible"
android:background=
"@drawable/ic_login_banner"
/>
android:background=
"@drawable/ic_login_banner"
/>
<LinearLayout
<LinearLayout
android:layout_width=
"match_parent"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:layout_height=
"match_parent"
android:layout_marginTop=
"50dp"
android:layout_marginTop=
"100dp"
android:layout_gravity=
"center"
android:gravity=
"center"
android:orientation=
"vertical"
>
android:orientation=
"vertical"
>
<EditText
android:id=
"@+id/login_screen_username_edit_text"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_marginLeft=
"@dimen/extra_large_margin"
android:layout_marginRight=
"@dimen/extra_large_margin"
android:background=
"@drawable/username_under_bg_box"
android:drawableLeft=
"@mipmap/ic_user_icon"
android:hint=
"@string/username_string"
android:lines=
"1"
android:padding=
"@dimen/normal_margin"
android:singleLine=
"true"
/>
<EditText
android:id=
"@+id/login_screen_password_edit_text"
<TextView
android:id=
"@+id/login_with_sso"
android:layout_width=
"match_parent"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginBottom=
"@dimen/normal_margin"
android:layout_marginLeft=
"@dimen/extra_large_margin"
android:layout_marginLeft=
"@dimen/extra_large_margin"
android:layout_marginRight=
"@dimen/extra_large_margin"
android:layout_marginRight=
"@dimen/extra_large_margin"
android:background=
"@drawable/username_under_bg_box"
android:layout_marginTop=
"30dp"
android:drawableLeft=
"@mipmap/ic_password_icon"
android:background=
"@drawable/login_bg"
android:hint=
"@string/password_string"
android:gravity=
"center"
android:inputType=
"textPassword"
android:paddingBottom=
"@dimen/normal_margin"
android:lines=
"1"
android:paddingTop=
"@dimen/normal_margin"
android:padding=
"@dimen/normal_margin"
android:text=
"Login with ADAL"
android:singleLine=
"true"
/>
android:textColor=
"@android:color/white"
android:textSize=
"@dimen/large_text_size"
/>
<TextView
<TextView
android:id=
"@+id/login_
screen_login_text_view
"
android:id=
"@+id/login_
with_otp
"
android:layout_width=
"match_parent"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginBottom=
"@dimen/normal_margin"
android:layout_marginBottom=
"@dimen/normal_margin"
...
@@ -61,7 +55,7 @@
...
@@ -61,7 +55,7 @@
android:gravity=
"center"
android:gravity=
"center"
android:paddingBottom=
"@dimen/normal_margin"
android:paddingBottom=
"@dimen/normal_margin"
android:paddingTop=
"@dimen/normal_margin"
android:paddingTop=
"@dimen/normal_margin"
android:text=
"
@string/login_screen_login_string
"
android:text=
"
Login with otp
"
android:textColor=
"@android:color/white"
android:textColor=
"@android:color/white"
android:textSize=
"@dimen/large_text_size"
/>
android:textSize=
"@dimen/large_text_size"
/>
</LinearLayout>
</LinearLayout>
...
...
app/src/main/res/layout/adal_main.xml
0 → 100755
View file @
47ff318d
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
android:id=
"@+id/activity_main"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:background=
"#FFFFFF"
android:orientation=
"vertical"
tools:context=
"com.vsoft.vera.ui.ADALActivity"
>
<TextView
android:text=
"WelCome"
android:textColor=
"#3f3f3f"
android:textSize=
"50px"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginLeft=
"10dp"
android:layout_marginTop=
"15dp"
android:id=
"@+id/welcome"
android:visibility=
"invisible"
/>
<Button
android:id=
"@+id/callGraph"
android:text=
"Call Graph"
android:textColor=
"#FFFFFF"
android:background=
"#00a1f1"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_marginTop=
"200dp"
android:textAllCaps=
"false"
/>
<TextView
android:text=
"Loading"
android:textColor=
"#3f3f3f"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_marginLeft=
"5dp"
android:id=
"@+id/graphData"
android:visibility=
"invisible"
/>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"0dip"
android:layout_weight=
"1"
android:gravity=
"center|bottom"
android:orientation=
"vertical"
>
<Button
android:text=
"Signoutt"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_marginBottom=
"15dp"
android:textColor=
"#FFFFFF"
android:background=
"#00a1f1"
android:textAllCaps=
"false"
android:id=
"@+id/clearCache"
android:visibility=
"invisible"
/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
build.gradle
View file @
47ff318d
...
@@ -8,7 +8,7 @@ buildscript {
...
@@ -8,7 +8,7 @@ buildscript {
maven
{
url
"https://jitpack.io"
}
maven
{
url
"https://jitpack.io"
}
}
}
dependencies
{
dependencies
{
classpath
'com.android.tools.build:gradle:3.
4.2
'
classpath
'com.android.tools.build:gradle:3.
5.0
'
classpath
'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath
'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath
'com.google.gms:google-services:4.2.0'
classpath
'com.google.gms:google-services:4.2.0'
...
...
gradle/wrapper/gradle-wrapper.properties
View file @
47ff318d
...
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
...
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath
=
wrapper/dists
distributionPath
=
wrapper/dists
zipStoreBase
=
GRADLE_USER_HOME
zipStoreBase
=
GRADLE_USER_HOME
zipStorePath
=
wrapper/dists
zipStorePath
=
wrapper/dists
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-5.
1
.1-all.zip
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-5.
4
.1-all.zip
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment