diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 8f326a5..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -EMDK-DeviceIdentifiers-Sample \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 6593b67..0108d6b 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -13,7 +13,6 @@ diff --git a/DeviceIdentifiersWrapper/build.gradle b/DeviceIdentifiersWrapper/build.gradle index 1b0b60a..f6c0991 100644 --- a/DeviceIdentifiersWrapper/build.gradle +++ b/DeviceIdentifiersWrapper/build.gradle @@ -1,9 +1,5 @@ -apply plugin: 'com.android.library' - -ext { - PUBLISH_GROUP_ID = 'com.zebra.deviceidentifierswrapper' - PUBLISH_ARTIFACT_ID = 'deviceidentifierswrapper' - PUBLISH_VERSION = '0.2' +plugins { + id 'com.android.library' } android { @@ -12,8 +8,8 @@ android { defaultConfig { minSdkVersion 19 targetSdkVersion 32 - versionCode 2 - versionName "0.2" + versionCode 8 + versionName "0.8" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -25,6 +21,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } @@ -36,54 +36,3 @@ dependencies { androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' compileOnly 'com.symbol:emdk:+' } - -/* -Generate release files for publication and Zip them -https://medium.com/@daniellevass/how-to-publish-your-android-studio-library-to-jcenter-5384172c4739 -https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle - */ - -// ./gradlew clean build generateRelease -apply plugin: 'maven' - -def version = project.PUBLISH_VERSION - -def localReleaseDest = "${buildDir}/release/${version}" - -task androidJavadocs(type: Javadoc) { - failOnError = false - source = android.sourceSets.main.java.srcDirs - ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" - classpath += files(ext.androidJar) -} - -task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { - archiveClassifier = 'javadoc' - from androidJavadocs.destinationDir -} - -task androidSourcesJar(type: Jar) { - archiveClassifier = 'sources' - from android.sourceSets.main.java.srcDirs -} - -task zipRelease(type: Zip) { - from localReleaseDest - destinationDir buildDir - archiveBaseName = "release-${version}" -} - -task generateRelease { - doLast { - println "Release ${version} can be found at ${localReleaseDest}/" - println "Release ${version} zipped can be found ${buildDir}/release-${version}.zip" - } -} - -generateRelease.dependsOn(zipRelease) - - -artifacts { - archives androidSourcesJar - archives androidJavadocsJar -} \ No newline at end of file diff --git a/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/DIHelper.java b/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/DIHelper.java index 78a9905..fcca803 100644 --- a/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/DIHelper.java +++ b/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/DIHelper.java @@ -24,9 +24,32 @@ public class DIHelper { // TODO: Put your custom certificate in the apkCertificate member for MX AccessMgr registering (only if necessary and if you know what you are doing) public static Signature apkCertificate = null; + protected static String sIMEI = null; + protected static String sSerialNumber = null; + + public static final long SEC_IN_MS = 1000; + public static final long MIN_IN_MS = SEC_IN_MS * 60; + public static long MAX_EMDK_TIMEOUT_IN_MS = 10 * MIN_IN_MS; // 10 minutes + public static long WAIT_PERIOD_BEFORE_RETRY_EMDK_RETRIEVAL_IN_MS = 2 * SEC_IN_MS; // 2 seconds + + public static void resetCachedValues() + { + sIMEI = null; + sSerialNumber = null; + } + // This method will return the serial number in the string passed through the onSuccess method public static void getSerialNumber(Context context, IDIResultCallbacks callbackInterface) { + if(sSerialNumber != null) + { + if(callbackInterface != null) + { + callbackInterface.onDebugStatus("Serial number already in cache."); + } + callbackInterface.onSuccess(sSerialNumber); + return; + } if (android.os.Build.VERSION.SDK_INT < 29) { returnSerialUsingAndroidAPIs(context, callbackInterface); } else { @@ -37,9 +60,11 @@ public static void getSerialNumber(Context context, IDIResultCallbacks callbackI @SuppressLint({"MissingPermission", "ObsoleteSdkInt", "HardwareIds"}) private static void returnSerialUsingAndroidAPIs(Context context, IDIResultCallbacks callbackInterface) { if (android.os.Build.VERSION.SDK_INT < 26) { + sSerialNumber = Build.SERIAL; callbackInterface.onSuccess(Build.SERIAL); } else { if (ContextCompat.checkSelfPermission(context, permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { + sSerialNumber = Build.getSerial(); callbackInterface.onSuccess(Build.getSerial()); } else { callbackInterface.onError("Please grant READ_PHONE_STATE permission"); @@ -47,15 +72,42 @@ private static void returnSerialUsingAndroidAPIs(Context context, IDIResultCallb } } - private static void returnSerialUsingZebraAPIs(Context context, IDIResultCallbacks callbackInterface) { + private static void returnSerialUsingZebraAPIs(Context context, final IDIResultCallbacks callbackInterface) { + IDIResultCallbacks tempCallbackInterface = new IDIResultCallbacks() { + @Override + public void onSuccess(String message) { + sSerialNumber = message; + callbackInterface.onSuccess(message); + } + + @Override + public void onError(String message) { + callbackInterface.onError(message); + } + + @Override + public void onDebugStatus(String message) { + callbackInterface.onDebugStatus(message); + } + }; + new RetrieveOEMInfoTask() .execute(context, Uri.parse("content://oem_info/oem.zebra.secure/build_serial"), - callbackInterface); + tempCallbackInterface); } // This method will return the imei number in the string passed through the onSuccess method public static void getIMEINumber(Context context, IDIResultCallbacks callbackInterface) { + if(sIMEI != null) + { + if(callbackInterface != null) + { + callbackInterface.onDebugStatus("IMEI number already in cache."); + } + callbackInterface.onSuccess(sIMEI); + return; + } if (android.os.Build.VERSION.SDK_INT < 29) { returnImeiUsingAndroidAPIs(context, callbackInterface); } else { @@ -68,6 +120,7 @@ private static void returnImeiUsingAndroidAPIs(Context context, IDIResultCallbac TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (android.os.Build.VERSION.SDK_INT < 26) {String imei = telephonyManager.getDeviceId(); if (imei != null && !imei.isEmpty()) { + sIMEI = imei; callbackInterface.onSuccess(imei); } else { callbackInterface.onError("Could not get IMEI number"); @@ -76,6 +129,7 @@ private static void returnImeiUsingAndroidAPIs(Context context, IDIResultCallbac if (ContextCompat.checkSelfPermission(context, permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { String imei = telephonyManager.getImei(); if (imei != null && !imei.isEmpty()) { + sIMEI = imei; callbackInterface.onSuccess(imei); } else { callbackInterface.onError("Could not get IMEI number"); @@ -86,8 +140,26 @@ private static void returnImeiUsingAndroidAPIs(Context context, IDIResultCallbac } } - private static void returnImeiUsingZebraAPIs(Context context, IDIResultCallbacks callbackInterface) { + private static void returnImeiUsingZebraAPIs(Context context, final IDIResultCallbacks callbackInterface) { + IDIResultCallbacks tempCallbackInterface = new IDIResultCallbacks() { + @Override + public void onSuccess(String message) { + sIMEI = message; + callbackInterface.onSuccess(message); + } + + @Override + public void onError(String message) { + callbackInterface.onError(message); + } + + @Override + public void onDebugStatus(String message) { + callbackInterface.onDebugStatus(message); + } + }; + new RetrieveOEMInfoTask().execute(context, Uri.parse("content://oem_info/wan/imei"), - callbackInterface); + tempCallbackInterface); } } diff --git a/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/DIProfileManagerCommand.java b/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/DIProfileManagerCommand.java index f7b4435..661d88e 100644 --- a/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/DIProfileManagerCommand.java +++ b/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/DIProfileManagerCommand.java @@ -17,6 +17,7 @@ import java.io.StringReader; import java.util.ArrayList; +import java.util.Date; class DIProfileManagerCommand extends DICommandBase { public class ErrorHolder @@ -55,6 +56,9 @@ public class ErrorHolder // Provides full error description string public String msErrorString = ""; + // To prevent multiple initializations at the same time + private boolean bInitializing = false; + // Status Listener implementation (ensure that we retrieve the profile manager asynchronously EMDKManager.StatusListener mStatusListener = new EMDKManager.StatusListener() { @Override @@ -114,6 +118,9 @@ public void execute(String mxProfile, String mxProfileName, IDIResultCallbacks r private void initializeEMDK() { + if(bInitializing) + return; + bInitializing = true; if(mEMDKManager == null) { EMDKResults results = null; @@ -126,6 +133,7 @@ private void initializeEMDK() { logMessage("Error while requesting EMDKManager.\n" + e.getLocalizedMessage(), EMessageType.ERROR); e.printStackTrace(); + waitForEMDK(); return; } @@ -134,6 +142,7 @@ private void initializeEMDK() logMessage("EMDKManager request command issued with success", EMessageType.DEBUG); }else { logMessage("EMDKManager request command error", EMessageType.ERROR); + waitForEMDK(); } } else @@ -142,6 +151,36 @@ private void initializeEMDK() } } + private void waitForEMDK() + { + logMessage("EMDKManager error, this could be a BOOT_COMPLETED issue.", EMessageType.DEBUG); + logMessage("Starting watcher thread to wait for EMDK initialization.", EMessageType.DEBUG); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + long startDate = new Date().getTime(); + long delta = 0; + while(mEMDKManager == null || delta < DIHelper.MAX_EMDK_TIMEOUT_IN_MS ) { + // Try to initialize EMDK + logMessage("Calling EMDK Initialization method", EMessageType.DEBUG); + initializeEMDK(); + try { + logMessage("Waiting " + DIHelper.WAIT_PERIOD_BEFORE_RETRY_EMDK_RETRIEVAL_IN_MS + " milliseconds before retrying.", EMessageType.DEBUG); + Thread.sleep(DIHelper.WAIT_PERIOD_BEFORE_RETRY_EMDK_RETRIEVAL_IN_MS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + delta = new Date().getTime() - startDate; + logMessage("Delta in ms since first EMDK retrieval try: " + delta + "ms stops at " + DIHelper.MAX_EMDK_TIMEOUT_IN_MS + "ms", EMessageType.DEBUG); + } + bInitializing = false; + logMessage("Could not retrieve EMDK Manager after waiting " + DIHelper.WAIT_PERIOD_BEFORE_RETRY_EMDK_RETRIEVAL_IN_MS/DIHelper.SEC_IN_MS + " seconds. Please contact your administrator or check logcat for any EMDK related error.", EMessageType.ERROR); + } + }); + t.setPriority(Thread.MIN_PRIORITY); + t.start(); + } + private void onEMDKManagerRetrieved(EMDKManager emdkManager) { mEMDKManager = emdkManager; @@ -186,6 +225,7 @@ private void releaseManagers() private void onProfileManagerInitialized(ProfileManager profileManager) { mProfileManager = profileManager; + bInitializing = false; logMessage("Profile Manager retrieved.", EMessageType.DEBUG); processMXContent(); } diff --git a/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/RetrieveOEMInfoTask.java b/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/RetrieveOEMInfoTask.java index a13a4ff..1d885b7 100644 --- a/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/RetrieveOEMInfoTask.java +++ b/DeviceIdentifiersWrapper/src/main/java/com/zebra/deviceidentifierswrapper/RetrieveOEMInfoTask.java @@ -1,5 +1,6 @@ package com.zebra.deviceidentifierswrapper; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -106,7 +107,10 @@ private static void registerCurrentApplication(Context context, Uri serviceIdent // You can copy/paste this snippet if you want to provide your own // certificate // TODO: use the following code snippet to extract your custom certificate if necessary - final Signature[] arrSignatures = packageInfo.signingInfo.getApkContentsSigners(); + Signature[] arrSignatures = null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { + arrSignatures = packageInfo.signingInfo.getApkContentsSigners(); + } if(arrSignatures == null || arrSignatures.length == 0) { if(callbackInterface != null) @@ -124,7 +128,10 @@ private static void registerCurrentApplication(Context context, Uri serviceIdent final byte[] rawCert = sig.toByteArray(); // Get the certificate as a base64 string - String encoded = Base64.getEncoder().encodeToString(rawCert); + String encoded = null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + encoded = Base64.getEncoder().encodeToString(rawCert); + } profileData = "" + @@ -163,7 +170,7 @@ private static void getURIValue(Cursor cursor, Uri uri, IDIResultCallbacks resul else{ for (int i = 0; i < cursor.getColumnCount(); i++) { try { - String data = cursor.getString(cursor.getColumnIndex(cursor.getColumnName(i))); + @SuppressLint("Range") String data = cursor.getString(cursor.getColumnIndex(cursor.getColumnName(i))); resultCallbacks.onSuccess(data); cursor.close(); return; diff --git a/README.md b/README.md index f725ee8..9477554 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # DeviceIdentifiersWrapper +## Easy access to serial number and IMEI + +Forget about StageNow, EMDK, certificates, application signature... complexity.... + +Just get the serial number and the IMEI number of your Zebra device in one method call (see at the end of this document). + +Have fun with Zebra's devices :) + + @@ -11,8 +20,19 @@ ## Sample Repository https://github.com/ltrudu/DeviceIdentifiersWrapper-Sample - -## Update for A11 +## V0.4 to v0.8 : Basic cache mechanism & Wait for EMDK availability +```text + Added basic cache mechanism. + The IMei and the Serial number will be cached once they get retrieved. + The cache can be reset with the method: + DIHelper.resetCachedValues() + Added a mechanism to wait for the EMDK if it is not available (when responding to the BOOT_COMPLETED event for ex.) + To be tested... feel free to report any issue regarding this feature. + Check the sample for a basic implementation. + Added lots of logs that will be sent to logCat or to the onDebugStatus callback method. + Updated gradle version to release 7.3.3 +``` +## V0.3 : Update for A11 ```text Update your graddle distribution to >= 7.3.3 update your compileSdkVersion to 30 @@ -23,7 +43,15 @@ https://github.com/ltrudu/DeviceIdentifiersWrapper-Sample You can use the sample as a copy/paste source. ``` - +## Important !! +```text + Due to usage of the EMDK and the need to register the application, it is strongly advised to call the methods in your application class + Check https://github.com/ltrudu/DeviceIdentifiersWrapper-Sample implementation. + It's a basic implementation using static members. + Feel free to remove statics and replace them with a better code in terms of architecture. + The goal was to pass the idea that theses number should be retrieved only once, and the best place for it is the Application class. + Note that a mechanism has been added in V0.4 to wait for the EMDK in case it would not be available (the classic use case is when your app respond to the BOOT_COMPLETED event that occurs way before the EMDK finishes its initialization) +``` ## Description A wrapper to easily retrieve the Serial Number and the IMEI number of an Android 10+ Zebra device. diff --git a/build.gradle b/build.gradle index da807a2..5ae9a7b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,22 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - google() - jcenter() - } - dependencies { - classpath "com.android.tools.build:gradle:4.0.0" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - jcenter() - } +plugins { + id 'com.android.application' version '7.2.2' apply false + id 'com.android.library' version '7.2.2' apply false } task clean(type: Delete) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fe0dbff..5fe5e46 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip diff --git a/settings.gradle b/settings.gradle index 09209e7..88cd915 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,22 @@ -include ':SampleApplication',':DeviceIdentifiersWrapper' -rootProject.name = "EMDK-DeviceIdentifiers-Sample" \ No newline at end of file +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + jcenter() + maven { url 'https://jitpack.io' } + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() + maven { url 'https://jitpack.io' } + } +} + + +include ':DeviceIdentifiersWrapper' +rootProject.name = "DeviceIdentifiersWrapper" \ No newline at end of file