Enable Push Notification for Android

HYPR SDK for Android: Web Authentication

πŸ“˜

Do This First

This process assumes that you have an existing Firebase project and that you have connected it to Control Center via Configuring Push Notifications via FireBase.

Follow these integration steps to facilitate out-of-band (OOB) Authentication with Firebase push notifications for your Android project.

  1. Open your Firebase console and select the project you wish to use.
  2. Click Project Overview on the left; then select the Android icon.
1820
  1. Fill in the Android package name and App nickname; add a SHA sum if desired. When finished, click Register App.
648
  1. Download google.services.json to the app/ directory and click Next.
895
  1. Follow the instructions to modify the app/build.gradle file, then click Next. HYPR includes this entry by default in the reference app.
891
  1. If you are satisfied with your project configuration, click Continue to console.
861

Add HYPR Dependencies

Copy the Android .aar files into the app/libs directory from the downloaded package /aars folder. These will be referenced as needed in the modules' build.gradle files.

Add Firebase SDK to Your Android App

Here is an example of the default shipped app/commonlibraries/build.gradle file:

plugins {
    id 'com.android.library'
    id 'kotlin-android'
}

// HYPR Related
ext {
    hyprVersion = "7.3.0"
    hyprCode = 70300
    cryptoVersion = "3.7.0"
    adpVersion = "3.6.0"

}

android {
    compileSdkVersion 31
    buildToolsVersion "30.0.2"

    defaultConfig {
        targetSdkVersion 31
        versionCode hyprCode
        versionName hyprVersion

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"

        // HYPR Related
        minSdkVersion 23  // Minimum Android API Version that the HYPR SDK supports
        multiDexEnabled true // Gets past the 65,536 method limit that can be referenced in a single DEX file
        ndk {
            abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
            // Restricts the set of ABIs that the application supports.
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

// HYPR Related: References the HYPR AARs in the /libs directory
allprojects {
    repositories {
        jcenter()
        flatDir {
            dirs "$rootProject.projectDir/libs"
        }
    }
}

dependencies {
    api fileTree(dir: "$rootProject.projectDir/libs", include: ['*.jar'])

    // Android
    api 'androidx.multidex:multidex:2.0.1'
    api 'androidx.appcompat:appcompat:1.3.1' // do not change yet - https://stackoverflow.com/questions/69033022/message-error-resource-androidattr-lstar-not-found
    api 'com.google.android.material:material:1.4.0'
    api 'androidx.constraintlayout:constraintlayout:2.1.2'
    api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.4.0'
    api "androidx.security:security-crypto:1.0.0"

    // GJON / POJO
    api 'com.google.code.gson:gson:2.8.6'
    api 'org.apache.commons:commons-lang3:3.5'

    // RxAndroid
    api group: 'io.reactivex.rxjava2', name: 'rxandroid', version: '2.1.1'
    api group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.21'
    api 'javax.annotation:jsr250-api:1.0'

    // Retrofit
    api 'com.squareup.retrofit2:retrofit:2.9.0'
    api 'com.squareup.retrofit2:converter-gson:2.9.0'
    api 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
    api 'com.squareup.okhttp3:logging-interceptor:4.9.3'

    // QR Scanner
    api 'com.google.mlkit:barcode-scanning:17.0.0'

    // Fingerprint
    api 'androidx.biometric:biometric:1.1.0'

    // HYPR Crypto
    api(name: "crypto", version: "${cryptoVersion}", ext: 'aar') { transitive = true }
    api(name: 'HyprCommon', version: "${hyprVersion}", ext: 'aar')
    api(name: 'HyprBiometricPrompt', version: "${hyprVersion}", ext: 'aar')
    api(name: 'HyprPin', version: "${hyprVersion}", ext: 'aar')
    api(name: 'HyprSilent', version: "${hyprVersion}", ext: 'aar')
    api(name: 'HyprPresence', version: "${hyprVersion}", ext: 'aar')
}

Create the CustomFirebaseMessagingService Subclass

πŸ“˜

NOTE

The example subclass is named CustomFirebaseMessagingService for the purposes of demonstration.

public class CustomFirebaseMessagingService extends FirebaseMessagingService {
    @Override
    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
        Map<String, String> data = remoteMessage.getData();
        HyprPushNotificationAdapter.getInstance()
                .setOnNotificationPressedLaunchActivity(MainActivity.class)
                .shouldSavePendingAuthenticationRequest(true)
                .processRemoteMessage(this, remoteMessage.getData());
    }

    @Override
    public void onNewToken(@NonNull String s) {
        super.onNewToken(s);
        HyprPushNotificationAdapter.getInstance().onNewPushChannelId(this, s);
    }
}

Activity That Processes the Push Notification

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    HyprApp.getDbAdapter().onNewIntent(this, intent);
}

@Override
protected void onPause() {
    super.onPause();
    HyprPushNotificationAdapter.getInstance().unregisterBroadcastReceiver(this);
}

@Override
protected void onResume() {
    super.onResume();
    HyprPushNotificationAdapter.getInstance().registerBroadcastReceiver(this, new HyprActionCallbacks.HyprWebLoginCallback() {
        @Override
        public void onSuccess(@NonNull HyprActionCallbacks.WebLoginType webLoginType, @NonNull HyprStatusResult hyprStatusResult) {
            // success
        }

        @Override
        public void onFailure(@NonNull HyprActionCallbacks.WebLoginType webLoginType, @NonNull HyprStatusResult hyprStatusResult) {
            // fail
        }
    });
    handlePushIfAvailable();
}

public void handlePushIfAvailable() {
  if(HyprApp.getDbAdapter().isPushPayloadAvailable(this)) {
    HyprActions.getInstance().startPushNotificationIntent(this, new HyprActionCallbacks.HyprWebLoginCallback() {
      @Override
        public void onSuccess(@NonNull HyprActionCallbacks.WebLoginType webLoginType, @NonNull HyprStatusResult hyprStatusResult) {
        toast("Success");
      }

      @Override
        public void onFailure(@NonNull HyprActionCallbacks.WebLoginType webLoginType, @NonNull HyprStatusResult hyprStatusResult) {
        toast("Success");
      }
    });
  }
  else if (HyprApp.getDbAdapter().isPendingAuthenticationAvailable(this)) {
    HyprActions.getInstance().startPendingAuthentication(this, new HyprActionCallbacks.HyprWebLoginCallback() {
      @Override
        public void onSuccess(@NonNull HyprActionCallbacks.WebLoginType webLoginType, @NonNull HyprStatusResult hyprStatusResult) {
        toast("Success");
      }

      @Override
        public void onFailure(@NonNull HyprActionCallbacks.WebLoginType webLoginType, @NonNull HyprStatusResult hyprStatusResult) {
        toast("Fail");
      }
    });
  }
}

Firebase in AndroidManifest.xml

Ensure the following values are present in the web account unlock manifest in app/webaccountunlock/src/main/res. Make sure to replace <package.path.to> with the correct path from your build.

<manifest>
  // ...
  <application>
    ...
    <service android:name="<package.path.to>.CustomFirebaseMessagingService">
      <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
      </intent-filter>
    </service>
    ...
  </application>
</manifest>

Update the MultiDexApplication Subclass

public class App extends MultiDexApplication {
  @Override
  public void onCreate() {
    super.onCreate();
  	initPushNotifications();
    HyprApp.initializeApp(this, new HyprInit.InitTrustDataCallback() {
      @Override
        public void onInstallComplete() {
        Log.d(LOG_TAG, "onInstallComplete");
        updateWebAppProfile()
        }

      @Override
        public void onInstallError(@NonNull String s, @Nullable Throwable throwable) {
        Log.e(LOG_TAG, "onInstallError: " + s);
      }
    });
  }
  
  private void updateWebAppProfile() {
    try {
      HyprAppProfileData appProfileData = HyprApp.getDbAdapter().getCurHyprAppProfileData(this);
      appProfileData.setCustomerConfiguredDefaultRpVersion(this, 4);
      appProfileData.setHyprRpAppType(this, HyprRpAppType.WebsiteOnly);
    } catch (HyprException e) {
      Log.e(LOG_TAG, e.toString());
    }
  }
  
  private void initPushNotifications() {
    FirebaseApp.initializeApp(this);
    HyprPushNotificationAdapter.getInstance().setPushId(context, FirebaseModel.getFirebaseSenderId());
  }
}

UI Customization

We can customize the OOB Device Setup screens via XML overrides. Each screen has a corresponding XML layout file. By creating a layout file with the same name as the one you want to override, the SDK will present that layout file instead.

🚧

Android IDs

If you are overriding the layout files, please make sure to keep all the android:ids with their respective view from the layout that you want to override as the SDK uses those IDs.

Overriding the OOB Device Setup Screen

Create a file called hypr_common_view_single_pin.xml using the example below. Now you can alter/add views and change the properties of those views.

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

    <RelativeLayout
        android:id="@+id/layout_movable_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/hypr_pin_layout_pin_one"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_above="@+id/layout_pin_instructions_text"
            android:layout_gravity="center_horizontal"
            android:gravity="center_horizontal"
            android:orientation="horizontal">

            <com.hypr.hyprandroidcommon.uiadapter.ui.views.HyprStatefullEditText
                android:id="@+id/hypr_pin_editview_pin_one_first"
                style="@style/PinEditTextStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/hyprColorPinFocusedLine" />

            <com.hypr.hyprandroidcommon.uiadapter.ui.views.HyprStatefullEditText
                android:id="@+id/hypr_pin_editview_pin_one_second"
                style="@style/PinEditTextStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/hyprColorPinFocusedLine" />

            <com.hypr.hyprandroidcommon.uiadapter.ui.views.HyprStatefullEditText
                android:id="@+id/hypr_pin_editview_pin_one_third"
                style="@style/PinEditTextStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/hyprColorPinFocusedLine" />

            <com.hypr.hyprandroidcommon.uiadapter.ui.views.HyprStatefullEditText
                android:id="@+id/hypr_pin_editview_pin_one_fourth"
                style="@style/PinEditTextStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/hyprColorPinFocusedLine" />

            <com.hypr.hyprandroidcommon.uiadapter.ui.views.HyprStatefullEditText
                android:id="@+id/hypr_pin_editview_pin_one_fifth"
                style="@style/PinEditTextStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/hyprColorPinFocusedLine" />

            <com.hypr.hyprandroidcommon.uiadapter.ui.views.HyprStatefullEditText
                android:id="@+id/hypr_pin_editview_pin_one_sixth"
                style="@style/PinEditTextStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/hyprColorPinFocusedLine" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/layout_pin_instructions_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_above="@+id/hypr_common_view_cancel_button"
            android:layout_marginTop="@dimen/hypr_common_pin_instructions_text_margin_top"
            android:layout_marginBottom="@dimen/hypr_common_pin_instructions_text_margin_bottom"
            android:gravity="center_horizontal"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@font/sharetech_regular"
                android:gravity="center_horizontal"
                android:text="@string/hypr_pin_instructions_title"
                android:textColor="@color/hyprColorTextPrimary"
                android:textSize="@dimen/hypr_common_license_entry_title_size" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@font/sharetech_regular"
                android:gravity="center_horizontal"
                android:text="@string/hypr_pin_instructions_text"
                android:textColor="@color/hyprColorTextPrimary"
                android:textSize="@dimen/hypr_common_license_entry_desc_size" />

            <com.hypr.hyprandroidcommon.uiadapter.ui.icons.SetupWorkstationAndDevice
                android:layout_width="@dimen/hypr_common_license_entry_img_width"
                android:layout_height="@dimen/hypr_common_license_entry_img_height"
                android:layout_marginTop="@dimen/hypr_common_license_entry_img_margin_top"
                android:contentDescription="@string/hypr_todo_content_description"
                android:gravity="center_horizontal" />

        </LinearLayout>

        <include
            android:id="@+id/hypr_common_view_cancel_button"
            layout="@layout/hypr_common_view_cancel_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_above="@+id/view_pin_entry_text_buffer"
            android:layout_marginBottom="@dimen/hypr_common_pin_cancel_margin_bottom" />

        <View
            android:id="@+id/view_pin_entry_text_buffer"
            android:layout_width="match_parent"
            android:layout_height="@dimen/hypr_common_pin_buffer_view_height"
            android:layout_above="@+id/view_bottom_anchor"
            android:visibility="visible" />

        <View
            android:id="@+id/view_bottom_anchor"
            android:layout_width="match_parent"
            android:layout_height="@dimen/hypr_common_keyboard_divider_bar_height"
            android:layout_alignParentBottom="true"
            android:background="@color/hyprColorKeyboardAnchorView"
            android:visibility="invisible" />

    </RelativeLayout>

    <include layout="@layout/hypr_common_view_keyboard_layout" />
</RelativeLayout>
787

After OOB Device Setup is implemented, you can now perform OOB Authentication. For more information, please refer to the following documentation:

Out-of-band Authentication Setup: JavaScript Web SDK
Out-of-band Authentication Setup: Java Web SDK

Check the Server for Push Payload

In the event that the push notification does not arrive on the device, we still would like to authenticate.
The HYPR SDK for Android provides functionality to check the HYPR server for any push notifications for authentication, and if found, to start the authentication. You can do so with the code provided here:

val webAccounts = HyprApp.getDbAdapter().getCurHyprAppProfileData(activity).hyprMachineProfileDatas
val webAccount = webAccounts[0]; // Get the web account that you want to check
HyprActions.getInstance().checkServerForPendingAuthentication(activity, webAccount.dbId, object: HyprActionCallbacks.HyprActionCallback {
  override fun onSuccess(statusResult: HyprStatusResult) {
    HyprActions.getInstance().startPendingAuthentication(activity, object: HyprActionCallbacks.HyprWebLoginCallback {
      override fun onSuccess(
        webLoginType: HyprActionCallbacks.WebLoginType,
        statusResult: HyprStatusResult
      ) {

      }

      override fun onFailure(
        webLoginType: HyprActionCallbacks.WebLoginType,
        statusResult: HyprStatusResult
      ) {

      }

    })
    }

  override fun onFailure(statusResult: HyprStatusResult) {

  }
})