Skip to content

Commit

Permalink
address violation of retrieving unsettable device (#781)
Browse files Browse the repository at this point in the history
* address violation of unsettable device id

* get rid of android id and serial number

* remove unused imports

* fix unit test

* disable daemon to fix ci issue

* increase vm memory size to address ci issue

* try to address ci issue

* try to address ci issue

* enlarge vm settings
  • Loading branch information
wenxi-zeng committed Jan 11, 2022
1 parent d068d87 commit bf2f2c0
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 38 deletions.
3 changes: 2 additions & 1 deletion .circleci/config.yml
Expand Up @@ -5,8 +5,9 @@ jobs:
docker:
- image: circleci/android:api-28-alpha
environment:
JVM_OPTS: -Xmx6400m
JVM_OPTS: -Xmx8192m
CIRCLE_JDK_VERSION: oraclejdk8
resource_class: large
steps:
- checkout
- restore_cache:
Expand Down
Expand Up @@ -236,7 +236,7 @@ public Campaign campaign() {
/** Fill this instance with device info from the provided {@link Context}. */
void putDevice(Context context, boolean collectDeviceID) {
Device device = new Device();
String identifier = collectDeviceID ? getDeviceId(context) : traits().anonymousId();
String identifier = collectDeviceID ? getDeviceId() : traits().anonymousId();
device.put(Device.DEVICE_ID_KEY, identifier);
device.put(Device.DEVICE_MANUFACTURER_KEY, Build.MANUFACTURER);
device.put(Device.DEVICE_MODEL_KEY, Build.MODEL);
Expand Down
80 changes: 47 additions & 33 deletions analytics/src/main/java/com/segment/analytics/internal/Utils.java
Expand Up @@ -24,27 +24,22 @@
package com.segment.analytics.internal;

import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.TELEPHONY_SERVICE;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.provider.Settings.Secure.ANDROID_ID;
import static android.provider.Settings.Secure.getString;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.MediaDrm;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand All @@ -58,6 +53,7 @@
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.net.HttpURLConnection;
import java.security.MessageDigest;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -302,40 +298,58 @@ public static <T> List<T> immutableCopyOf(@Nullable List<T> list) {
return Collections.unmodifiableList(new ArrayList<>(list));
}

/**
* Creates a unique device id. Suppresses `HardwareIds` lint warnings as we don't use this ID
* for identifying specific users. This is also what is required by the Segment spec.
*/
@SuppressLint("HardwareIds")
public static String getDeviceId(Context context) {
String androidId = getString(context.getContentResolver(), ANDROID_ID);
if (!isNullOrEmpty(androidId)
&& !"9774d56d682e549c".equals(androidId)
&& !"unknown".equals(androidId)
&& !"000000000000000".equals(androidId)) {
return androidId;
}

// Serial number, guaranteed to be on all non phones in 2.3+.
if (!isNullOrEmpty(Build.SERIAL)) {
return Build.SERIAL;
}

// Telephony ID, guaranteed to be on all phones, requires READ_PHONE_STATE permission
if (hasPermission(context, READ_PHONE_STATE) && hasFeature(context, FEATURE_TELEPHONY)) {
TelephonyManager telephonyManager = getSystemService(context, TELEPHONY_SERVICE);
@SuppressLint("MissingPermission")
String telephonyId = telephonyManager.getDeviceId();
if (!isNullOrEmpty(telephonyId)) {
return telephonyId;
}
/** Creates a unique device id. */
public static String getDeviceId() {
// unique id generated from DRM API
String uniqueID = getUniqueID();
if (!isNullOrEmpty(uniqueID)) {
return uniqueID;
}

// If this still fails, generate random identifier that does not persist across
// installations
return UUID.randomUUID().toString();
}

/**
* Workaround for not able to get device id on Android 10 or above using DRM API {@see
* https://stackoverflow.com/questions/58103580/android-10-imei-no-longer-available-on-api-29-looking-for-alternatives}
* {@see https://developer.android.com/training/articles/user-data-ids}
*/
private static String getUniqueID() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return null;

UUID wideVineUuid = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
MediaDrm wvDrm = null;
try {
wvDrm = new MediaDrm(wideVineUuid);
byte[] wideVineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID);
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(wideVineId);
return byteArrayToHexString(md.digest());
} catch (Exception e) {
// Inspect exception
return null;
} finally {
if (wvDrm == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
wvDrm.close();
} else {
wvDrm.release();
}
}
}
}

private static String byteArrayToHexString(byte[] bytes) {
StringBuilder buffer = new StringBuilder();
for (byte element : bytes) {
buffer.append(String.format("%02x", element));
}

return buffer.toString();
}

/** Returns a shared preferences for storing any library preferences. */
public static SharedPreferences getSegmentSharedPreferences(Context context, String tag) {
return context.getSharedPreferences("analytics-android-" + tag, MODE_PRIVATE);
Expand Down
Expand Up @@ -70,8 +70,10 @@ class AnalyticsContextTest {
assertThat(context.getValueMap("app"))
.containsEntry("build", "0")

// only check esistence of device id, since we don't know the value
// and we can't mock static method in mockito 2
assertThat(context.getValueMap("device"))
.containsEntry("id", "unknown")
.containsKey("id")
assertThat(context.getValueMap("device"))
.containsEntry("manufacturer", "unknown")
assertThat(context.getValueMap("device"))
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Expand Up @@ -18,7 +18,7 @@ POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=segmentio
POM_DEVELOPER_NAME=Segment, Inc.

org.gradle.jvmargs=-Xmx1536m

org.gradle.jvmargs=-Xmx2048m
org.gradle.daemon=false
android.useAndroidX=true
android.enableJetifier=true

0 comments on commit bf2f2c0

Please sign in to comment.