Skip to content

Commit bf2f2c0

Browse files
authored
address violation of retrieving unsettable device (#781)
* 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
1 parent d068d87 commit bf2f2c0

File tree

5 files changed

+55
-38
lines changed

5 files changed

+55
-38
lines changed

.circleci/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ jobs:
55
docker:
66
- image: circleci/android:api-28-alpha
77
environment:
8-
JVM_OPTS: -Xmx6400m
8+
JVM_OPTS: -Xmx8192m
99
CIRCLE_JDK_VERSION: oraclejdk8
10+
resource_class: large
1011
steps:
1112
- checkout
1213
- restore_cache:

analytics/src/main/java/com/segment/analytics/AnalyticsContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public Campaign campaign() {
236236
/** Fill this instance with device info from the provided {@link Context}. */
237237
void putDevice(Context context, boolean collectDeviceID) {
238238
Device device = new Device();
239-
String identifier = collectDeviceID ? getDeviceId(context) : traits().anonymousId();
239+
String identifier = collectDeviceID ? getDeviceId() : traits().anonymousId();
240240
device.put(Device.DEVICE_ID_KEY, identifier);
241241
device.put(Device.DEVICE_MANUFACTURER_KEY, Build.MANUFACTURER);
242242
device.put(Device.DEVICE_MODEL_KEY, Build.MODEL);

analytics/src/main/java/com/segment/analytics/internal/Utils.java

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,22 @@
2424
package com.segment.analytics.internal;
2525

2626
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
27-
import static android.Manifest.permission.READ_PHONE_STATE;
2827
import static android.content.Context.CONNECTIVITY_SERVICE;
2928
import static android.content.Context.MODE_PRIVATE;
30-
import static android.content.Context.TELEPHONY_SERVICE;
31-
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
3229
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
3330
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
34-
import static android.provider.Settings.Secure.ANDROID_ID;
35-
import static android.provider.Settings.Secure.getString;
3631

3732
import android.annotation.SuppressLint;
3833
import android.app.Activity;
3934
import android.content.Context;
4035
import android.content.Intent;
4136
import android.content.SharedPreferences;
37+
import android.media.MediaDrm;
4238
import android.net.ConnectivityManager;
4339
import android.net.NetworkInfo;
4440
import android.net.Uri;
4541
import android.os.Build;
4642
import android.os.Process;
47-
import android.telephony.TelephonyManager;
4843
import android.text.TextUtils;
4944
import androidx.annotation.NonNull;
5045
import androidx.annotation.Nullable;
@@ -58,6 +53,7 @@
5853
import java.io.InputStreamReader;
5954
import java.lang.reflect.Array;
6055
import java.net.HttpURLConnection;
56+
import java.security.MessageDigest;
6157
import java.text.ParseException;
6258
import java.util.ArrayList;
6359
import java.util.Collection;
@@ -302,40 +298,58 @@ public static <T> List<T> immutableCopyOf(@Nullable List<T> list) {
302298
return Collections.unmodifiableList(new ArrayList<>(list));
303299
}
304300

305-
/**
306-
* Creates a unique device id. Suppresses `HardwareIds` lint warnings as we don't use this ID
307-
* for identifying specific users. This is also what is required by the Segment spec.
308-
*/
309-
@SuppressLint("HardwareIds")
310-
public static String getDeviceId(Context context) {
311-
String androidId = getString(context.getContentResolver(), ANDROID_ID);
312-
if (!isNullOrEmpty(androidId)
313-
&& !"9774d56d682e549c".equals(androidId)
314-
&& !"unknown".equals(androidId)
315-
&& !"000000000000000".equals(androidId)) {
316-
return androidId;
317-
}
318-
319-
// Serial number, guaranteed to be on all non phones in 2.3+.
320-
if (!isNullOrEmpty(Build.SERIAL)) {
321-
return Build.SERIAL;
322-
}
323-
324-
// Telephony ID, guaranteed to be on all phones, requires READ_PHONE_STATE permission
325-
if (hasPermission(context, READ_PHONE_STATE) && hasFeature(context, FEATURE_TELEPHONY)) {
326-
TelephonyManager telephonyManager = getSystemService(context, TELEPHONY_SERVICE);
327-
@SuppressLint("MissingPermission")
328-
String telephonyId = telephonyManager.getDeviceId();
329-
if (!isNullOrEmpty(telephonyId)) {
330-
return telephonyId;
331-
}
301+
/** Creates a unique device id. */
302+
public static String getDeviceId() {
303+
// unique id generated from DRM API
304+
String uniqueID = getUniqueID();
305+
if (!isNullOrEmpty(uniqueID)) {
306+
return uniqueID;
332307
}
333308

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

314+
/**
315+
* Workaround for not able to get device id on Android 10 or above using DRM API {@see
316+
* https://stackoverflow.com/questions/58103580/android-10-imei-no-longer-available-on-api-29-looking-for-alternatives}
317+
* {@see https://developer.android.com/training/articles/user-data-ids}
318+
*/
319+
private static String getUniqueID() {
320+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return null;
321+
322+
UUID wideVineUuid = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
323+
MediaDrm wvDrm = null;
324+
try {
325+
wvDrm = new MediaDrm(wideVineUuid);
326+
byte[] wideVineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID);
327+
MessageDigest md = MessageDigest.getInstance("SHA-256");
328+
md.update(wideVineId);
329+
return byteArrayToHexString(md.digest());
330+
} catch (Exception e) {
331+
// Inspect exception
332+
return null;
333+
} finally {
334+
if (wvDrm == null) {
335+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
336+
wvDrm.close();
337+
} else {
338+
wvDrm.release();
339+
}
340+
}
341+
}
342+
}
343+
344+
private static String byteArrayToHexString(byte[] bytes) {
345+
StringBuilder buffer = new StringBuilder();
346+
for (byte element : bytes) {
347+
buffer.append(String.format("%02x", element));
348+
}
349+
350+
return buffer.toString();
351+
}
352+
339353
/** Returns a shared preferences for storing any library preferences. */
340354
public static SharedPreferences getSegmentSharedPreferences(Context context, String tag) {
341355
return context.getSharedPreferences("analytics-android-" + tag, MODE_PRIVATE);

analytics/src/test/java/com/segment/analytics/AnalyticsContextTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ class AnalyticsContextTest {
7070
assertThat(context.getValueMap("app"))
7171
.containsEntry("build", "0")
7272

73+
// only check esistence of device id, since we don't know the value
74+
// and we can't mock static method in mockito 2
7375
assertThat(context.getValueMap("device"))
74-
.containsEntry("id", "unknown")
76+
.containsKey("id")
7577
assertThat(context.getValueMap("device"))
7678
.containsEntry("manufacturer", "unknown")
7779
assertThat(context.getValueMap("device"))

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ POM_LICENCE_DIST=repo
1818
POM_DEVELOPER_ID=segmentio
1919
POM_DEVELOPER_NAME=Segment, Inc.
2020

21-
org.gradle.jvmargs=-Xmx1536m
22-
21+
org.gradle.jvmargs=-Xmx2048m
22+
org.gradle.daemon=false
2323
android.useAndroidX=true
2424
android.enableJetifier=true

0 commit comments

Comments
 (0)