Commit aa34b7615c9e2c58650f4399de800c73072ba07e

Authored by Jean Marie Pascal
0 parents
Exists in master and in 1 other branch REPO1550

Alfresco Android SAML Testing App

Showing 47 changed files with 1702 additions and 0 deletions   Show diff stats
... ... @@ -0,0 +1,29 @@
  1 +#Android generated
  2 +bin
  3 +gen
  4 +lint.xml
  5 +
  6 +#Eclipse
  7 +.project
  8 +.classpath
  9 +.settings
  10 +.checkstyle
  11 +
  12 +#IntelliJ IDEA
  13 +.idea
  14 +*.iml
  15 +*.ipr
  16 +*.iws
  17 +classes
  18 +gen-external-apklibs
  19 +
  20 +#Maven
  21 +target
  22 +release.properties
  23 +pom.xml.*
  24 +
  25 +#GRADLE
  26 +.gradle
  27 +/local.properties
  28 +.DS_Store
  29 +/build
... ...
... ... @@ -0,0 +1 @@
  1 +/build
... ...
... ... @@ -0,0 +1,38 @@
  1 +apply plugin: 'com.android.application'
  2 +
  3 +android {
  4 + compileSdkVersion 24
  5 + buildToolsVersion "24.0.2"
  6 + defaultConfig {
  7 + applicationId "com.alfresco.android.saml"
  8 + minSdkVersion 19
  9 + targetSdkVersion 24
  10 + versionCode 1
  11 + versionName "1.0"
  12 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  13 + }
  14 + buildTypes {
  15 + release {
  16 + minifyEnabled false
  17 + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  18 + }
  19 + }
  20 +}
  21 +
  22 +dependencies {
  23 + compile fileTree(dir: 'libs', include: ['*.jar'])
  24 + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
  25 + exclude group: 'com.android.support', module: 'support-annotations'
  26 + })
  27 + compile 'com.android.support:appcompat-v7:24.2.1'
  28 + compile 'com.android.support:design:24.2.1'
  29 + compile 'com.android.support:support-v4:24.2.1'
  30 +
  31 + compile 'com.squareup.retrofit2:retrofit:2.1.0'
  32 + compile 'com.squareup.retrofit2:converter-gson:2.1.0'
  33 + compile 'com.squareup.okhttp3:okhttp:3.4.1'
  34 + compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
  35 + compile 'org.alfresco.client:alfresco-java-client:1.0.0-beta1'
  36 +
  37 + testCompile 'junit:junit:4.12'
  38 +}
... ...
app/proguard-rules.pro
... ... @@ -0,0 +1,17 @@
  1 +# Add project specific ProGuard rules here.
  2 +# By default, the flags in this file are appended to flags specified
  3 +# in W:\Tools\Android\AndroidSDK/tools/proguard/proguard-android.txt
  4 +# You can edit the include path and order by changing the proguardFiles
  5 +# directive in build.gradle.
  6 +#
  7 +# For more details, see
  8 +# http://developer.android.com/guide/developing/tools/proguard.html
  9 +
  10 +# Add any project specific keep options here:
  11 +
  12 +# If your project uses WebView with JS, uncomment the following
  13 +# and specify the fully qualified class name to the JavaScript interface
  14 +# class:
  15 +#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
  16 +# public *;
  17 +#}
... ...
app/src/androidTest/java/com/alfresco/android/saml/ExampleInstrumentedTest.java
... ... @@ -0,0 +1,26 @@
  1 +package com.alfresco.android.saml;
  2 +
  3 +import android.content.Context;
  4 +import android.support.test.InstrumentationRegistry;
  5 +import android.support.test.runner.AndroidJUnit4;
  6 +
  7 +import org.junit.Test;
  8 +import org.junit.runner.RunWith;
  9 +
  10 +import static org.junit.Assert.*;
  11 +
  12 +/**
  13 + * Instrumentation test, which will execute on an Android device.
  14 + *
  15 + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
  16 + */
  17 +@RunWith(AndroidJUnit4.class)
  18 +public class ExampleInstrumentedTest {
  19 + @Test
  20 + public void useAppContext() throws Exception {
  21 + // Context of the app under test.
  22 + Context appContext = InstrumentationRegistry.getTargetContext();
  23 +
  24 + assertEquals("com.alfresco.android.saml", appContext.getPackageName());
  25 + }
  26 +}
... ...
app/src/main/AndroidManifest.xml
... ... @@ -0,0 +1,39 @@
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3 + package="com.alfresco.android.saml">
  4 +
  5 + <uses-permission android:name="android.permission.INTERNET" />
  6 + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  7 +
  8 + <!-- To auto-complete the email text field in the login form with the user's emails -->
  9 + <uses-permission android:name="android.permission.GET_ACCOUNTS" />
  10 + <uses-permission android:name="android.permission.READ_PROFILE" />
  11 + <uses-permission android:name="android.permission.READ_CONTACTS" />
  12 +
  13 + <application
  14 + android:allowBackup="true"
  15 + android:icon="@mipmap/ic_launcher"
  16 + android:label="@string/app_name"
  17 + android:supportsRtl="true"
  18 + android:theme="@style/AppTheme">
  19 + <activity
  20 + android:name=".ui.activity.ApiTestActivity"
  21 + android:label="@string/app_name"
  22 + android:theme="@style/AppTheme.NoActionBar"/>
  23 + <activity
  24 + android:name=".ui.activity.SignInActivity"
  25 + android:configChanges="orientation|keyboardHidden|screenSize"
  26 + android:label="@string/title_activity_sign_in"
  27 + android:theme="@style/FullscreenTheme" />
  28 + <activity
  29 + android:name=".ui.activity.LoginActivity"
  30 + android:label="@string/app_name">
  31 + <intent-filter>
  32 + <action android:name="android.intent.action.MAIN" />
  33 +
  34 + <category android:name="android.intent.category.LAUNCHER" />
  35 + </intent-filter>
  36 + </activity>
  37 + </application>
  38 +
  39 +</manifest>
0 40 \ No newline at end of file
... ...
app/src/main/java/com/alfresco/android/saml/api/SAMLConstant.java
... ... @@ -0,0 +1,15 @@
  1 +package com.alfresco.android.saml.api;
  2 +
  3 +/**
  4 + * Created by jpascal on 20/10/2016.
  5 + */
  6 +
  7 +public interface SAMLConstant
  8 +{
  9 +
  10 + String ALFRESCO_SAML_AUTHENTICATE_URL = "service/saml/-default-/repository/authenticate";
  11 +
  12 + String ALFRESCO_SAML_AUTHENTICATE_RESPONSE_URL = "service/saml/-default-/repository/authenticate-response";
  13 +
  14 + String ALFRESCO_SAML_INFO_URL = "service/saml/-default-/repository/enabled";
  15 +}
... ...
app/src/main/java/com/alfresco/android/saml/api/models/SamlEnabledRepresentation.java
... ... @@ -0,0 +1,120 @@
  1 +package com.alfresco.android.saml.api.models;
  2 +
  3 +import com.google.gson.annotations.Expose;
  4 +import com.google.gson.annotations.SerializedName;
  5 +
  6 +import java.util.Objects;
  7 +
  8 +public class SamlEnabledRepresentation
  9 +{
  10 +
  11 + @SerializedName("isSamlEnabled")
  12 + @Expose
  13 + private Boolean isSamlEnabled;
  14 +
  15 + @SerializedName("isSamlEnforced")
  16 + @Expose
  17 + private Boolean isSamlEnforced;
  18 +
  19 + @SerializedName("idpDescription")
  20 + @Expose
  21 + private String idpDescription;
  22 +
  23 + @SerializedName("tenantDomain")
  24 + @Expose
  25 + private String tenantDomain;
  26 +
  27 + /**
  28 + * @return The isSamlEnabled
  29 + */
  30 + public Boolean getIsSamlEnabled()
  31 + {
  32 + return isSamlEnabled;
  33 + }
  34 +
  35 + /**
  36 + * @param isSamlEnabled The isSamlEnabled
  37 + */
  38 + public void setIsSamlEnabled(Boolean isSamlEnabled)
  39 + {
  40 + this.isSamlEnabled = isSamlEnabled;
  41 + }
  42 +
  43 + /**
  44 + * @return The isSamlEnforced
  45 + */
  46 + public Boolean getIsSamlEnforced()
  47 + {
  48 + return isSamlEnforced;
  49 + }
  50 +
  51 + /**
  52 + * @param isSamlEnforced The isSamlEnforced
  53 + */
  54 + public void setIsSamlEnforced(Boolean isSamlEnforced)
  55 + {
  56 + this.isSamlEnforced = isSamlEnforced;
  57 + }
  58 +
  59 + /**
  60 + * @return The idpDescription
  61 + */
  62 + public String getIdpDescription()
  63 + {
  64 + return idpDescription;
  65 + }
  66 +
  67 + /**
  68 + * @param idpDescription The idpDescription
  69 + */
  70 + public void setIdpDescription(String idpDescription)
  71 + {
  72 + this.idpDescription = idpDescription;
  73 + }
  74 +
  75 + /**
  76 + * @return The tenantDomain
  77 + */
  78 + public String getTenantDomain()
  79 + {
  80 + return tenantDomain;
  81 + }
  82 +
  83 + /**
  84 + * @param tenantDomain The tenantDomain
  85 + */
  86 + public void setTenantDomain(String tenantDomain)
  87 + {
  88 + this.tenantDomain = tenantDomain;
  89 + }
  90 +
  91 +
  92 + @Override
  93 + public int hashCode()
  94 + {
  95 + return Objects.hash(isSamlEnabled, isSamlEnforced, idpDescription, tenantDomain);
  96 + }
  97 +
  98 + @Override
  99 + public String toString()
  100 + {
  101 + StringBuilder sb = new StringBuilder();
  102 + sb.append("class SAMLEnabledRepresentation {\n");
  103 + sb.append(" isSamlEnabled: ").append(toIndentedString(isSamlEnabled)).append("\n");
  104 + sb.append(" isSamlEnforced: ").append(toIndentedString(isSamlEnforced)).append("\n");
  105 + sb.append(" idpDescription: ").append(toIndentedString(idpDescription)).append("\n");
  106 + sb.append(" tenantDomain: ").append(toIndentedString(tenantDomain)).append("\n");
  107 + sb.append("}");
  108 + return sb.toString();
  109 + }
  110 +
  111 + /**
  112 + * Convert the given object to string with each line indented by 4 spaces
  113 + * (except the first line).
  114 + */
  115 + private String toIndentedString(java.lang.Object o)
  116 + {
  117 + if (o == null) { return "null"; }
  118 + return o.toString().replace("\n", "\n ");
  119 + }
  120 +}
... ...
app/src/main/java/com/alfresco/android/saml/api/models/SamlTicketRepresentation.java
... ... @@ -0,0 +1,62 @@
  1 +package com.alfresco.android.saml.api.models;
  2 +
  3 +import com.google.gson.annotations.Expose;
  4 +import com.google.gson.annotations.SerializedName;
  5 +
  6 +import java.io.Serializable;
  7 +import java.util.Objects;
  8 +
  9 +public class SamlTicketRepresentation implements Serializable
  10 +{
  11 +
  12 + @SerializedName("ticket")
  13 + @Expose
  14 + private String ticket;
  15 +
  16 + @SerializedName("userId")
  17 + @Expose
  18 + private String userId;
  19 +
  20 + public String getTicket() {
  21 + return ticket;
  22 + }
  23 +
  24 + public void setTicket(String ticket) {
  25 + this.ticket = ticket;
  26 + }
  27 +
  28 + public String getUserId() {
  29 + return userId;
  30 + }
  31 +
  32 + public void setUserId(String userId) {
  33 + this.userId = userId;
  34 + }
  35 +
  36 + @Override
  37 + public int hashCode()
  38 + {
  39 + return Objects.hash(ticket, userId);
  40 + }
  41 +
  42 + @Override
  43 + public String toString()
  44 + {
  45 + StringBuilder sb = new StringBuilder();
  46 + sb.append("class SamlTicketRepresentation {\n");
  47 + sb.append(" ticket: ").append(toIndentedString(ticket)).append("\n");
  48 + sb.append(" userId: ").append(toIndentedString(userId)).append("\n");
  49 + sb.append("}");
  50 + return sb.toString();
  51 + }
  52 +
  53 + /**
  54 + * Convert the given object to string with each line indented by 4 spaces
  55 + * (except the first line).
  56 + */
  57 + private String toIndentedString(java.lang.Object o)
  58 + {
  59 + if (o == null) { return "null"; }
  60 + return o.toString().replace("\n", "\n ");
  61 + }
  62 +}
... ...
app/src/main/java/com/alfresco/android/saml/ui/activity/ApiTestActivity.java
... ... @@ -0,0 +1,395 @@
  1 +package com.alfresco.android.saml.ui.activity;
  2 +
  3 +import java.io.IOException;
  4 +import java.util.List;
  5 +
  6 +import com.alfresco.android.saml.R;
  7 +import com.alfresco.android.saml.api.SAMLConstant;
  8 +import com.alfresco.android.saml.api.models.SamlEnabledRepresentation;
  9 +import com.alfresco.android.saml.api.models.SamlTicketRepresentation;
  10 +import com.alfresco.client.AlfrescoCoreClient;
  11 +import com.alfresco.client.api.common.representation.ResultPaging;
  12 +import com.alfresco.client.api.core.model.representation.PersonRepresentation;
  13 +import com.alfresco.client.api.core.model.representation.SiteMemberRepresentation;
  14 +import com.alfresco.client.api.core.model.representation.SiteRepresentation;
  15 +import com.alfresco.client.api.core.model.representation.TagRepresentation;
  16 +import com.google.gson.Gson;
  17 +
  18 +import android.content.Intent;
  19 +import android.os.Bundle;
  20 +import android.support.design.widget.FloatingActionButton;
  21 +import android.support.design.widget.NavigationView;
  22 +import android.support.design.widget.Snackbar;
  23 +import android.support.v4.view.GravityCompat;
  24 +import android.support.v4.widget.DrawerLayout;
  25 +import android.support.v7.app.ActionBarDrawerToggle;
  26 +import android.support.v7.app.AppCompatActivity;
  27 +import android.support.v7.widget.Toolbar;
  28 +import android.view.MenuItem;
  29 +import android.view.View;
  30 +import android.widget.TextView;
  31 +
  32 +import okhttp3.OkHttpClient;
  33 +import okhttp3.Request;
  34 +import okhttp3.logging.HttpLoggingInterceptor;
  35 +import retrofit2.Call;
  36 +import retrofit2.Callback;
  37 +import retrofit2.Response;
  38 +
  39 +public class ApiTestActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener
  40 +{
  41 +
  42 + public static final String EXTRA_ID_SAML_TICKET = "SAMLTicket";
  43 +
  44 + private AlfrescoCoreClient client;
  45 +
  46 + private SamlTicketRepresentation ticketRe;
  47 +
  48 + private String baseUrl;
  49 +
  50 + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  51 + // LIFE CYCLE
  52 + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  53 + @Override
  54 + protected void onCreate(Bundle savedInstanceState)
  55 + {
  56 + super.onCreate(savedInstanceState);
  57 +
  58 + // Retrieve extras
  59 + if (getIntent().getExtras() != null)
  60 + {
  61 + baseUrl = getIntent().getStringExtra(SignInActivity.EXTRA_ID_ALFRESCO_BASEURL);
  62 + ticketRe = (SamlTicketRepresentation) getIntent().getSerializableExtra(EXTRA_ID_SAML_TICKET);
  63 + }
  64 +
  65 + if (baseUrl == null || ticketRe == null)
  66 + {
  67 + finish();
  68 + return;
  69 + }
  70 +
  71 + setContentView(R.layout.activity_saml);
  72 + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
  73 + setSupportActionBar(toolbar);
  74 +
  75 + FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
  76 + fab.setVisibility(View.GONE);
  77 +
  78 + DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
  79 + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open,
  80 + R.string.navigation_drawer_close);
  81 + drawer.setDrawerListener(toggle);
  82 + toggle.syncState();
  83 +
  84 + NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
  85 + navigationView.setNavigationItemSelectedListener(this);
  86 + }
  87 +
  88 + @Override
  89 + public void onBackPressed()
  90 + {
  91 + DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
  92 + if (drawer.isDrawerOpen(GravityCompat.START))
  93 + {
  94 + drawer.closeDrawer(GravityCompat.START);
  95 + }
  96 + else
  97 + {
  98 + super.onBackPressed();
  99 + }
  100 + }
  101 +
  102 + @SuppressWarnings("StatementWithEmptyBody")
  103 + @Override
  104 + public boolean onNavigationItemSelected(MenuItem item)
  105 + {
  106 + // Handle navigation view item clicks here.
  107 + int id = item.getItemId();
  108 +
  109 + if (id == R.id.nav_saml_info)
  110 + {
  111 + getSAMLInfo();
  112 + }
  113 + else if (id == R.id.nav_saml_signin)
  114 + {
  115 + requestTicket();
  116 + }
  117 + else if (client == null)
  118 + {
  119 + createClient();
  120 + }
  121 +
  122 + if (client != null)
  123 + {
  124 + switch (id)
  125 + {
  126 + case R.id.nav_saml_ticket:
  127 + displayTicket();
  128 + break;
  129 + case R.id.nav_api_sites:
  130 + displaySites();
  131 + break;
  132 + case R.id.nav_api_site_info:
  133 + displaySwsdpInfo();
  134 + break;
  135 + case R.id.nav_api_site_members:
  136 + displaySwsdpMembers();
  137 + break;
  138 + case R.id.nav_api_person_info:
  139 + displayAdminProfile();
  140 + break;
  141 + case R.id.nav_api_tags:
  142 + displayTags();
  143 + break;
  144 +
  145 + }
  146 + }
  147 +
  148 + DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
  149 + drawer.closeDrawer(GravityCompat.START);
  150 + return true;
  151 + }
  152 +
  153 + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  154 + // INTERNAL
  155 + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  156 +
  157 + private void createClient()
  158 + {
  159 + if (ticketRe == null)
  160 + {
  161 + Snackbar.make(ApiTestActivity.this.findViewById(R.id.nav_view), "You need to sign in first.",
  162 + Snackbar.LENGTH_LONG).setAction("Action", null).show();
  163 + }
  164 + else
  165 + {
  166 + client = new AlfrescoCoreClient.Builder().connectWithTicket(baseUrl, ticketRe.getTicket())
  167 + .httpLogging(HttpLoggingInterceptor.Level.BODY).build();
  168 + }
  169 + }
  170 +
  171 + private void setResultText(String result)
  172 + {
  173 + ((TextView) ApiTestActivity.this.findViewById(R.id.central_text)).setText(result);
  174 + }
  175 +
  176 + private void requestTicket()
  177 + {
  178 + finish();
  179 + }
  180 +
  181 + private void getSAMLInfo()
  182 + {
  183 + OkHttpClient client = new OkHttpClient();
  184 + Request request = new Request.Builder().url(baseUrl.concat(SAMLConstant.ALFRESCO_SAML_INFO_URL)).build();
  185 +
  186 + client.newCall(request).enqueue(new okhttp3.Callback()
  187 + {
  188 + @Override
  189 + public void onFailure(okhttp3.Call call, IOException e)
  190 + {
  191 +
  192 + }
  193 +
  194 + @Override
  195 + public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException
  196 + {
  197 + Gson gson = new Gson();
  198 + final SamlEnabledRepresentation samlInfo = gson.fromJson(response.body().string(),
  199 + SamlEnabledRepresentation.class);
  200 + runOnUiThread(new Runnable()
  201 + {
  202 + @Override
  203 + public void run()
  204 + {
  205 + setResultText(samlInfo.toString());
  206 + }
  207 + });
  208 +
  209 + }
  210 + });
  211 + }
  212 +
  213 + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  214 + // INTERNAL
  215 + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  216 + private void displayTicket()
  217 + {
  218 + if (ticketRe != null)
  219 + {
  220 + setResultText(ticketRe.toString());
  221 + }
  222 + else
  223 + {
  224 + Snackbar.make(ApiTestActivity.this.findViewById(R.id.nav_view), "You need to sign in first.",
  225 + Snackbar.LENGTH_LONG).setAction("Action", null).show();
  226 + }
  227 + }
  228 +
  229 + private void displaySwsdpInfo()
  230 + {
  231 + client.getSitesAPI().getSiteCall("swsdp").enqueue(new Callback<SiteRepresentation>()
  232 + {
  233 + @Override
  234 + public void onResponse(Call<SiteRepresentation> call, Response<SiteRepresentation> response)
  235 + {
  236 + if (response.isSuccessful())
  237 + {
  238 + SiteRepresentation site = response.body();
  239 + setResultText(site.toString());
  240 + }
  241 + else
  242 + {
  243 + Snackbar.make(ApiTestActivity.this.findViewById(R.id.nav_view), response.code(),
  244 + Snackbar.LENGTH_LONG).setAction("Action", null).show();
  245 + }
  246 + }
  247 +
  248 + @Override
  249 + public void onFailure(Call<SiteRepresentation> call, Throwable t)
  250 + {
  251 +
  252 + }
  253 + });
  254 + }
  255 +
  256 + private void displaySites()
  257 + {
  258 + client.getSitesAPI().listSitesCall().enqueue(new Callback<ResultPaging<SiteRepresentation>>()
  259 + {
  260 + @Override
  261 + public void onResponse(Call<ResultPaging<SiteRepresentation>> call,
  262 + Response<ResultPaging<SiteRepresentation>> response)
  263 + {
  264 + if (response.isSuccessful())
  265 + {
  266 + List<SiteRepresentation> sites = response.body().getList();
  267 + StringBuilder builder = new StringBuilder();
  268 + for (SiteRepresentation site : sites)
  269 + {
  270 + builder.append(site.toString());
  271 + builder.append("\n ----------------------------- ");
  272 + }
  273 + setResultText(builder.toString());
  274 + }
  275 + else
  276 + {
  277 + Snackbar.make(ApiTestActivity.this.findViewById(R.id.nav_view), response.code(),
  278 + Snackbar.LENGTH_LONG).setAction("Action", null).show();
  279 + }
  280 + }
  281 +
  282 + @Override
  283 + public void onFailure(Call<ResultPaging<SiteRepresentation>> call, Throwable t)
  284 + {
  285 +
  286 + }
  287 + });
  288 + }
  289 +
  290 + private void displaySwsdpMembers()
  291 + {
  292 + client.getSitesAPI().listSiteMembershipsCall("swsdp")
  293 + .enqueue(new Callback<ResultPaging<SiteMemberRepresentation>>()
  294 + {
  295 + @Override
  296 + public void onResponse(Call<ResultPaging<SiteMemberRepresentation>> call,
  297 + Response<ResultPaging<SiteMemberRepresentation>> response)
  298 + {
  299 + if (response.isSuccessful())
  300 + {
  301 + List<SiteMemberRepresentation> members = response.body().getList();
  302 + StringBuilder builder = new StringBuilder();
  303 + for (SiteMemberRepresentation member : members)
  304 + {
  305 + builder.append(member.toString());
  306 + builder.append("\n ----------------------------- ");
  307 + builder.append("\n ");
  308 + }
  309 + setResultText(builder.toString());
  310 + }
  311 + else
  312 + {
  313 + Snackbar.make(ApiTestActivity.this.findViewById(R.id.nav_view), response.code(),
  314 + Snackbar.LENGTH_LONG).setAction("Action", null).show();
  315 + }
  316 + }
  317 +
  318 + @Override
  319 + public void onFailure(Call<ResultPaging<SiteMemberRepresentation>> call, Throwable t)
  320 + {
  321 +
  322 + }
  323 + });
  324 + }
  325 +
  326 + private void displayAdminProfile()
  327 + {
  328 + client.getPeopleAPI().getPersonCall("admin").enqueue(new Callback<PersonRepresentation>()
  329 + {
  330 + @Override
  331 + public void onResponse(Call<PersonRepresentation> call, Response<PersonRepresentation> response)
  332 + {
  333 + if (response.isSuccessful())
  334 + {
  335 + PersonRepresentation person = response.body();
  336 + setResultText(person.toString());
  337 + }
  338 + else
  339 + {
  340 + Snackbar.make(ApiTestActivity.this.findViewById(R.id.nav_view), response.code(),
  341 + Snackbar.LENGTH_LONG).setAction("Action", null).show();
  342 + }
  343 + }
  344 +
  345 + @Override
  346 + public void onFailure(Call<PersonRepresentation> call, Throwable t)
  347 + {
  348 +
  349 + }
  350 + });
  351 + }
  352 +
  353 + private void displayTags()
  354 + {
  355 + client.getTagsAPI().listTagsCall().enqueue(new Callback<ResultPaging<TagRepresentation>>()
  356 + {
  357 + @Override
  358 + public void onResponse(Call<ResultPaging<TagRepresentation>> call,
  359 + Response<ResultPaging<TagRepresentation>> response)
  360 + {
  361 + if (response.isSuccessful())
  362 + {
  363 + List<TagRepresentation> tags = response.body().getList();
  364 + StringBuilder builder = new StringBuilder();
  365 + if (tags.isEmpty())
  366 + {
  367 + builder.append("No Tags available");
  368 + }
  369 + else
  370 + {
  371 + for (TagRepresentation tag : tags)
  372 + {
  373 + builder.append(tag.toString());
  374 + builder.append("\n ----------------------------- ");
  375 + builder.append("\n ");
  376 + }
  377 + }
  378 +
  379 + setResultText(builder.toString());
  380 + }
  381 + else
  382 + {
  383 + Snackbar.make(ApiTestActivity.this.findViewById(R.id.nav_view), response.code(),
  384 + Snackbar.LENGTH_LONG).setAction("Action", null).show();
  385 + }
  386 + }
  387 +
  388 + @Override
  389 + public void onFailure(Call<ResultPaging<TagRepresentation>> call, Throwable t)
  390 + {
  391 +
  392 + }
  393 + });
  394 + }
  395 +}
... ...
app/src/main/java/com/alfresco/android/saml/ui/activity/LoginActivity.java
... ... @@ -0,0 +1,75 @@
  1 +package com.alfresco.android.saml.ui.activity;
  2 +
  3 +import android.animation.Animator;
  4 +import android.animation.AnimatorListenerAdapter;
  5 +import android.annotation.TargetApi;
  6 +import android.content.Intent;
  7 +import android.content.pm.PackageManager;
  8 +import android.support.annotation.NonNull;
  9 +import android.support.design.widget.Snackbar;
  10 +import android.support.v7.app.AppCompatActivity;
  11 +import android.app.LoaderManager.LoaderCallbacks;
  12 +
  13 +import android.content.CursorLoader;
  14 +import android.content.Loader;
  15 +import android.database.Cursor;
  16 +import android.net.Uri;
  17 +import android.os.AsyncTask;
  18 +
  19 +import android.os.Build;
  20 +import android.os.Bundle;
  21 +import android.provider.ContactsContract;
  22 +import android.text.TextUtils;
  23 +import android.view.KeyEvent;
  24 +import android.view.View;
  25 +import android.view.View.OnClickListener;
  26 +import android.view.inputmethod.EditorInfo;
  27 +import android.widget.ArrayAdapter;
  28 +import android.widget.AutoCompleteTextView;
  29 +import android.widget.Button;
  30 +import android.widget.EditText;
  31 +import android.widget.TextView;
  32 +
  33 +import java.util.ArrayList;
  34 +import java.util.List;
  35 +
  36 +import com.alfresco.android.saml.R;
  37 +
  38 +import static android.Manifest.permission.READ_CONTACTS;
  39 +
  40 +/**
  41 + * A login screen that offers login via email/password.
  42 + */
  43 +public class LoginActivity extends AppCompatActivity
  44 +{
  45 +
  46 + // UI references.
  47 + private AutoCompleteTextView urlInputText;
  48 +
  49 + @Override
  50 + protected void onCreate(Bundle savedInstanceState)
  51 + {
  52 + super.onCreate(savedInstanceState);
  53 + setContentView(R.layout.activity_login);
  54 + // Set up the login form.
  55 + urlInputText = (AutoCompleteTextView) findViewById(R.id.email);
  56 +
  57 + Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
  58 + mEmailSignInButton.setOnClickListener(new OnClickListener()
  59 + {
  60 + @Override
  61 + public void onClick(View view)
  62 + {
  63 + attemptLogin();
  64 + }
  65 + });
  66 +
  67 + }
  68 +
  69 + private void attemptLogin()
  70 + {
  71 + Intent i = new Intent(Intent.ACTION_VIEW, null, getApplicationContext(), SignInActivity.class);
  72 + i.putExtra(SignInActivity.EXTRA_ID_ALFRESCO_BASEURL, urlInputText.getText().toString());
  73 + startActivity(i);
  74 + }
  75 +}
... ...
app/src/main/java/com/alfresco/android/saml/ui/activity/SignInActivity.java
... ... @@ -0,0 +1,134 @@
  1 +package com.alfresco.android.saml.ui.activity;
  2 +
  3 +import android.content.Intent;
  4 +import android.os.Build;
  5 +import android.support.v7.app.AppCompatActivity;
  6 +import android.os.Bundle;
  7 +import android.util.Log;
  8 +import android.webkit.ConsoleMessage;
  9 +import android.webkit.WebChromeClient;
  10 +import android.webkit.WebSettings;
  11 +import android.webkit.WebView;
  12 +import android.webkit.WebViewClient;
  13 +
  14 +import com.alfresco.android.saml.R;
  15 +import com.alfresco.android.saml.api.SAMLConstant;
  16 +import com.alfresco.android.saml.api.models.SamlEnabledRepresentation;
  17 +import com.alfresco.android.saml.api.models.SamlTicketRepresentation;
  18 +import com.google.gson.Gson;
  19 +
  20 +public class SignInActivity extends AppCompatActivity
  21 +{
  22 + public static final String EXTRA_ID_ALFRESCO_BASEURL = "AlfrescoBaseUrl";
  23 +
  24 + private WebView webView;
  25 +
  26 + private String lastUrl;
  27 +
  28 + private String baseUrl;
  29 +
  30 + @Override
  31 + protected void onCreate(Bundle savedInstanceState)
  32 + {
  33 + super.onCreate(savedInstanceState);
  34 +
  35 + // Retrieve extras
  36 + if (getIntent().getExtras() != null)
  37 + {
  38 + baseUrl = getIntent().getStringExtra(EXTRA_ID_ALFRESCO_BASEURL);
  39 + }
  40 +
  41 + if (baseUrl == null)
  42 + {
  43 + finish();
  44 + return;
  45 + }
  46 +
  47 + setContentView(R.layout.activity_sign_in);
  48 + setTitle("Authentication");
  49 +
  50 + webView = (WebView) findViewById(R.id.webview);
  51 + webView.clearCache(true);
  52 + webView.getSettings().setJavaScriptEnabled(true);
  53 + webView.getSettings().setBuiltInZoomControls(true);
  54 + webView.getSettings().setUseWideViewPort(true);
  55 + if (Build.VERSION.SDK_INT >= 21)
  56 + {
  57 + webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
  58 + }
  59 + webView.setWebChromeClient(new WebChromeClient()
  60 + {
  61 + public boolean onConsoleMessage(ConsoleMessage cmsg)
  62 + {
  63 + String message = cmsg.message().trim();
  64 +
  65 + if (lastUrl != null && lastUrl.endsWith(SAMLConstant.ALFRESCO_SAML_AUTHENTICATE_RESPONSE_URL))
  66 + {
  67 + if (message.startsWith("{"))
  68 + {
  69 + Gson gson = new Gson();
  70 + SamlTicketRepresentation ticketRepresentation = gson.fromJson(message,
  71 + SamlTicketRepresentation.class);
  72 + ticketRepresentation.getTicket();
  73 +
  74 + Intent ticketIntent = new Intent();
  75 + ticketIntent.putExtra(ApiTestActivity.EXTRA_ID_SAML_TICKET, ticketRepresentation);
  76 + ticketIntent.putExtra(SignInActivity.EXTRA_ID_ALFRESCO_BASEURL, baseUrl);
  77 + ticketIntent.setClassName(getApplicationContext(), ApiTestActivity.class.getName());
  78 +
  79 + SignInActivity.this.startActivity(ticketIntent);
  80 + SignInActivity.this.finish();
  81 + }
  82 + }
  83 + else if (lastUrl != null && lastUrl.endsWith(SAMLConstant.ALFRESCO_SAML_INFO_URL))
  84 + {
  85 + if (message.startsWith("{"))
  86 + {
  87 + Gson gson = new Gson();
  88 + SamlEnabledRepresentation enabledRepresentation = gson.fromJson(message,
  89 + SamlEnabledRepresentation.class);
  90 + enabledRepresentation.getIdpDescription();
  91 + }
  92 + }
  93 + return true;
  94 + }
  95 + });
  96 +
  97 + webView.setWebViewClient(new WebViewClient()
  98 + {
  99 +
  100 + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
  101 + {
  102 + Log.d("SAML", "onReceivedError: " + failingUrl);
  103 + if (failingUrl.endsWith(SAMLConstant.ALFRESCO_SAML_AUTHENTICATE_RESPONSE_URL))
  104 + {
  105 + view.loadUrl(baseUrl + SAMLConstant.ALFRESCO_SAML_AUTHENTICATE_URL);
  106 + return;
  107 + }
  108 + }
  109 +
  110 + @Override
  111 + public void onPageFinished(WebView view, String url)
  112 + {
  113 + lastUrl = url;
  114 + if (url.endsWith(SAMLConstant.ALFRESCO_SAML_AUTHENTICATE_URL))
  115 + {
  116 + view.loadUrl("javascript:console.log(document.body.getElementsByTagName('pre')[0].innerHTML);");
  117 + }
  118 + else if (url.endsWith(SAMLConstant.ALFRESCO_SAML_AUTHENTICATE_RESPONSE_URL))
  119 + {
  120 + Log.d("SAML", "onPageFinished: Get Info - " + url);
  121 + view.loadUrl("javascript:console.log(document.body.getElementsByTagName('pre')[0].innerHTML);");
  122 + }
  123 + }
  124 + });
  125 +
  126 + webView.loadUrl(baseUrl + SAMLConstant.ALFRESCO_SAML_AUTHENTICATE_URL);
  127 + }
  128 +
  129 + @Override
  130 + protected void onPostCreate(Bundle savedInstanceState)
  131 + {
  132 + super.onPostCreate(savedInstanceState);
  133 + }
  134 +}
... ...
app/src/main/res/drawable-v21/ic_menu_camera.xml
... ... @@ -0,0 +1,12 @@
  1 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  2 + android:width="24dp"
  3 + android:height="24dp"
  4 + android:viewportHeight="24.0"
  5 + android:viewportWidth="24.0">
  6 + <path
  7 + android:fillColor="#FF000000"
  8 + android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
  9 + <path
  10 + android:fillColor="#FF000000"
  11 + android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
  12 +</vector>
... ...
app/src/main/res/drawable-v21/ic_menu_gallery.xml
... ... @@ -0,0 +1,9 @@
  1 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  2 + android:width="24dp"
  3 + android:height="24dp"
  4 + android:viewportHeight="24.0"
  5 + android:viewportWidth="24.0">
  6 + <path
  7 + android:fillColor="#FF000000"
  8 + android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z" />
  9 +</vector>
... ...
app/src/main/res/drawable-v21/ic_menu_manage.xml
... ... @@ -0,0 +1,9 @@
  1 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  2 + android:width="24dp"
  3 + android:height="24dp"
  4 + android:viewportHeight="24.0"
  5 + android:viewportWidth="24.0">
  6 + <path
  7 + android:fillColor="#FF000000"
  8 + android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
  9 +</vector>
0 10 \ No newline at end of file
... ...
app/src/main/res/drawable-v21/ic_menu_send.xml
... ... @@ -0,0 +1,9 @@
  1 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  2 + android:width="24dp"
  3 + android:height="24dp"
  4 + android:viewportHeight="24.0"
  5 + android:viewportWidth="24.0">
  6 + <path
  7 + android:fillColor="#FF000000"
  8 + android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z" />
  9 +</vector>
... ...
app/src/main/res/drawable-v21/ic_menu_share.xml
... ... @@ -0,0 +1,9 @@
  1 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  2 + android:width="24dp"
  3 + android:height="24dp"
  4 + android:viewportHeight="24.0"
  5 + android:viewportWidth="24.0">
  6 + <path
  7 + android:fillColor="#FF000000"
  8 + android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
  9 +</vector>
... ...
app/src/main/res/drawable-v21/ic_menu_slideshow.xml
... ... @@ -0,0 +1,9 @@
  1 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  2 + android:width="24dp"
  3 + android:height="24dp"
  4 + android:viewportHeight="24.0"
  5 + android:viewportWidth="24.0">
  6 + <path
  7 + android:fillColor="#FF000000"
  8 + android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z" />
  9 +</vector>
... ...
app/src/main/res/drawable/side_nav_bar.xml
... ... @@ -0,0 +1,9 @@
  1 +<shape xmlns:android="http://schemas.android.com/apk/res/android"
  2 + android:shape="rectangle">
  3 + <gradient
  4 + android:angle="135"
  5 + android:centerColor="#4CAF50"
  6 + android:endColor="#2E7D32"