Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix ANR issue caused by MediaDRM api #791

Merged
merged 9 commits into from
Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,7 @@ public Analytics build() {
AnalyticsContext.create(application, traitsCache.get(), collectDeviceID);
CountDownLatch advertisingIdLatch = new CountDownLatch(1);
analyticsContext.attachAdvertisingId(application, advertisingIdLatch, logger);
analyticsContext.attachDeviceId(getSegmentSharedPreferences(application, tag));

List<Integration.Factory> factories = new ArrayList<>(1 + this.factories.size());
factories.add(SegmentIntegration.FACTORY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import static android.net.ConnectivityManager.TYPE_WIFI;
import static com.segment.analytics.internal.Utils.NullableConcurrentHashMap;
import static com.segment.analytics.internal.Utils.createMap;
import static com.segment.analytics.internal.Utils.getDeviceId;
import static com.segment.analytics.internal.Utils.getSystemService;
import static com.segment.analytics.internal.Utils.hasPermission;
import static com.segment.analytics.internal.Utils.isNullOrEmpty;
Expand All @@ -40,6 +39,7 @@

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
Expand Down Expand Up @@ -95,7 +95,7 @@ public class AnalyticsContext extends ValueMap {
// Campaign
private static final String CAMPAIGN_KEY = "campaign";
// Device
private static final String DEVICE_KEY = "device";
static final String DEVICE_KEY = "device";
// Library
private static final String LIBRARY_KEY = "library";
private static final String LIBRARY_NAME_KEY = "name";
Expand Down Expand Up @@ -131,7 +131,7 @@ static synchronized AnalyticsContext create(
new AnalyticsContext(new NullableConcurrentHashMap<String, Object>());
analyticsContext.putApp(context);
analyticsContext.setTraits(traits);
analyticsContext.putDevice(context, collectDeviceId);
analyticsContext.putDevice(collectDeviceId);
analyticsContext.putLibrary();
analyticsContext.put(
LOCALE_KEY,
Expand Down Expand Up @@ -172,6 +172,10 @@ void attachAdvertisingId(Context context, CountDownLatch latch, Logger logger) {
}
}

void attachDeviceId(SharedPreferences segmentSharedPreference) {
new GetDeviceIdTask(this, segmentSharedPreference, new CountDownLatch(1)).execute();
}

@Override
public AnalyticsContext putValue(String key, Object value) {
super.putValue(key, value);
Expand Down Expand Up @@ -234,9 +238,11 @@ public Campaign campaign() {
}

/** Fill this instance with device info from the provided {@link Context}. */
void putDevice(Context context, boolean collectDeviceID) {
void putDevice(boolean collectDeviceID) {
Device device = new Device();
String identifier = collectDeviceID ? getDeviceId() : traits().anonymousId();

// use empty string to indicate device id is not yet ready
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets add a comment that deviceId will be populated async

String identifier = collectDeviceID ? "" : 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
136 changes: 136 additions & 0 deletions analytics/src/main/java/com/segment/analytics/GetDeviceIdTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2014 Segment.io, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.segment.analytics;

import static com.segment.analytics.internal.Utils.getUniqueID;
import static com.segment.analytics.internal.Utils.isNullOrEmpty;

import android.content.SharedPreferences;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class GetDeviceIdTask {

private final ExecutorService executor = Executors.newFixedThreadPool(2);

private final AnalyticsContext analyticsContext;

private final SharedPreferences segmentSharedPreference;

private final CountDownLatch latch;

private static final String DEVICE_ID_CACHE_KEY = "device.id";

public GetDeviceIdTask(
AnalyticsContext analyticsContext,
SharedPreferences segmentSharedPreference,
CountDownLatch latch) {
this.analyticsContext = analyticsContext;
this.segmentSharedPreference = segmentSharedPreference;
this.latch = latch;
}

public void execute() {
if (cacheHit()) {
return;
}

final Future<?> future =
executor.submit(
new Runnable() {
@Override
public void run() {
String deviceId = getDeviceId();

if (!Thread.currentThread().isInterrupted()) {
updateDeviceId(deviceId);
updateCache(deviceId);
}
}
});

executor.execute(
new Runnable() {
@Override
public void run() {
try {
future.get(2, TimeUnit.SECONDS);
} catch (Exception e) {
future.cancel(true);
String fallbackDeviceId = UUID.randomUUID().toString();
updateDeviceId(fallbackDeviceId);
updateCache(fallbackDeviceId);
}

latch.countDown();
executor.shutdownNow();
}
});
}

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();
}

private boolean cacheHit() {
String cache = segmentSharedPreference.getString(DEVICE_ID_CACHE_KEY, null);

if (cache != null) {
updateDeviceId(cache);
return true;
} else {
return false;
}
}

private void updateDeviceId(String deviceId) {
synchronized (analyticsContext) {
if (!analyticsContext.containsKey(AnalyticsContext.DEVICE_KEY)) {
analyticsContext.put(AnalyticsContext.DEVICE_KEY, new AnalyticsContext.Device());
}

AnalyticsContext.Device device =
(AnalyticsContext.Device) analyticsContext.get(AnalyticsContext.DEVICE_KEY);
device.put(AnalyticsContext.Device.DEVICE_ID_KEY, deviceId);
}
}

private void updateCache(String deviceId) {
SharedPreferences.Editor editor = segmentSharedPreference.edit();
editor.putString(DEVICE_ID_CACHE_KEY, deviceId);
editor.apply();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -298,25 +298,12 @@ public static <T> List<T> immutableCopyOf(@Nullable List<T> list) {
return Collections.unmodifiableList(new ArrayList<>(list));
}

/** 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() {
public static String getUniqueID() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that this is not a safe API to use anymore, would it make sense to move it into the GetDeviceIdTask class?

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return null;

UUID wideVineUuid = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@ import android.content.Context
import android.content.Context.CONNECTIVITY_SERVICE
import android.net.ConnectivityManager
import com.google.common.collect.ImmutableMap
import com.nhaarman.mockitokotlin2.doAnswer
import com.nhaarman.mockitokotlin2.whenever
import com.segment.analytics.Utils.createContext
import com.segment.analytics.core.BuildConfig
import java.util.concurrent.CountDownLatch
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.fail
import org.assertj.core.data.MapEntry
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
Expand Down Expand Up @@ -243,4 +246,48 @@ class AnalyticsContextTest {
.build()
)
}

@Test
fun deviceIdFetchedIn2Seconds() {
val sharedPreferences = RuntimeEnvironment.application
.getSharedPreferences("analytics-test-qaz", Context.MODE_PRIVATE)
context = AnalyticsContext.create(RuntimeEnvironment.application, traits, true)
val latch = CountDownLatch(1)
val task = spy(GetDeviceIdTask(context, sharedPreferences, latch))

doAnswer {
"randomUUID"
}.`when`(task).deviceId

task.execute()
latch.await()

assertThat(context.getValueMap("device"))
.containsKey("id")
assertThat(context.getValueMap("device"))
.containsEntry("id", "randomUUID")
}

@Test
fun randomUUIDGeneratedAsDeviceIdAfter2Seconds() {
val sharedPreferences = RuntimeEnvironment.application
.getSharedPreferences("analytics-test-qaz", Context.MODE_PRIVATE)
context = AnalyticsContext.create(RuntimeEnvironment.application, traits, true)
val latch = CountDownLatch(1)
val task = spy(GetDeviceIdTask(context, sharedPreferences, latch))

doAnswer {
Thread.sleep(3000)
"randomUUID"
}.`when`(task).deviceId

task.execute()
latch.await()

assertThat(context.getValueMap("device"))
.containsKey("id")
// a random uuid should be generated to override the default empty value
assertThat(context.getValueMap("device"))
.doesNotContainEntry("id", "")
}
}