package com.mopub.common;
import android.content.Context;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
import com.mopub.common.factories.MethodBuilderFactory;
import com.mopub.common.logging.MoPubLog;
import com.mopub.common.util.AsyncTasks;
import java.lang.ref.WeakReference;
import static com.mopub.common.util.Reflection.MethodBuilder;
import static com.mopub.common.util.Reflection.classFound;
public class GpsHelper {
static public final int GOOGLE_PLAY_SUCCESS_CODE = 0;
static public final String ADVERTISING_ID_KEY = "advertisingId";
static public final String IS_LIMIT_AD_TRACKING_ENABLED_KEY = "isLimitAdTrackingEnabled";
private static String sPlayServicesUtilClassName = "com.google.android.gms.common.GooglePlayServicesUtil";
private static String sAdvertisingIdClientClassName = "com.google.android.gms.ads.identifier.AdvertisingIdClient";
public static class AdvertisingInfo {
public final String advertisingId;
public final boolean limitAdTracking;
public AdvertisingInfo(String adId, boolean limitAdTrackingEnabled) {
advertisingId = adId;
limitAdTracking = limitAdTrackingEnabled;
}
}
public interface GpsHelperListener {
public void onFetchAdInfoCompleted();
}
public static boolean isPlayServicesAvailable(final Context context) {
try {
MethodBuilder methodBuilder = MethodBuilderFactory.create(null, "isGooglePlayServicesAvailable")
.setStatic(Class.forName(sPlayServicesUtilClassName))
.addParam(Context.class, context);
Object result = methodBuilder.execute();
return (result != null && (Integer) result == GOOGLE_PLAY_SUCCESS_CODE);
} catch (Exception exception) {
return false;
}
}
static public boolean isLimitAdTrackingEnabled(Context context) {
final boolean defaultValue = false;
if (isPlayServicesAvailable(context)) {
return SharedPreferencesHelper.getSharedPreferences(context)
.getBoolean(IS_LIMIT_AD_TRACKING_ENABLED_KEY, defaultValue);
} else {
return defaultValue;
}
}
static boolean isClientMetadataPopulated(final Context context) {
return ClientMetadata.getInstance(context).isAdvertisingInfoSet();
}
static public void fetchAdvertisingInfoAsync(final Context context, final GpsHelperListener gpsHelperListener) {
// This method guarantees that the Google Play Services (GPS) advertising info will
// be populated if GPS is available and the ad info is not already cached
// The above will happen before the callback is run
boolean playServicesIsAvailable = isPlayServicesAvailable(context);
if (playServicesIsAvailable && !isClientMetadataPopulated(context)) {
internalFetchAdvertisingInfoAsync(context, gpsHelperListener);
} else {
if (gpsHelperListener != null) {
gpsHelperListener.onFetchAdInfoCompleted();
}
if (playServicesIsAvailable) {
// Kick off a request to update the ad information in the background.
internalFetchAdvertisingInfoAsync(context, null);
}
}
}
@Nullable
static public AdvertisingInfo fetchAdvertisingInfoSync(final Context context) {
if (context == null) {
return null;
}
Object adInfo = null;
try {
MethodBuilder methodBuilder = MethodBuilderFactory.create(null, "getAdvertisingIdInfo")
.setStatic(Class.forName(sAdvertisingIdClientClassName))
.addParam(Context.class, context);
adInfo = methodBuilder.execute();
} catch (Exception e) {
MoPubLog.d("Unable to obtain Google AdvertisingIdClient.Info via reflection.");
return null;
}
String advertisingId = reflectedGetAdvertisingId(adInfo, null);
boolean isLimitAdTrackingEnabled = reflectedIsLimitAdTrackingEnabled(adInfo, false);
return new AdvertisingInfo(advertisingId, isLimitAdTrackingEnabled);
}
static private void internalFetchAdvertisingInfoAsync(final Context context, final GpsHelperListener gpsHelperListener) {
if (!classFound(sAdvertisingIdClientClassName)) {
if (gpsHelperListener != null) {
gpsHelperListener.onFetchAdInfoCompleted();
}
return;
}
try {
AsyncTasks.safeExecuteOnExecutor(new FetchAdvertisingInfoTask(context, gpsHelperListener));
} catch (Exception exception) {
MoPubLog.d("Error executing FetchAdvertisingInfoTask", exception);
if (gpsHelperListener != null) {
gpsHelperListener.onFetchAdInfoCompleted();
}
}
}
static private class FetchAdvertisingInfoTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> mContextWeakReference;
private WeakReference<GpsHelperListener> mGpsHelperListenerWeakReference;
public FetchAdvertisingInfoTask(Context context, GpsHelperListener gpsHelperListener) {
mContextWeakReference = new WeakReference<Context>(context);
mGpsHelperListenerWeakReference = new WeakReference<GpsHelperListener>(gpsHelperListener);
}
@Override
protected Void doInBackground(Void... voids) {
try {
Context context = mContextWeakReference.get();
if (context == null) {
return null;
}
MethodBuilder methodBuilder = MethodBuilderFactory.create(null, "getAdvertisingIdInfo")
.setStatic(Class.forName(sAdvertisingIdClientClassName))
.addParam(Context.class, context);
Object adInfo = methodBuilder.execute();
if (adInfo != null) {
updateClientMetadata(context, adInfo);
}
} catch (Exception exception) {
MoPubLog.d("Unable to obtain Google AdvertisingIdClient.Info via reflection.");
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
GpsHelperListener gpsHelperListener = mGpsHelperListenerWeakReference.get();
if (gpsHelperListener != null) {
gpsHelperListener.onFetchAdInfoCompleted();
}
}
}
static void updateClientMetadata(final Context context, final Object adInfo) {
String advertisingId = reflectedGetAdvertisingId(adInfo, null);
boolean isLimitAdTrackingEnabled = reflectedIsLimitAdTrackingEnabled(adInfo, false);
/*
* Committing using the editor is atomic; a single editor must always commit
* to ensure that the state of the GPS variables are in sync.
*/
ClientMetadata clientMetadata = ClientMetadata.getInstance(context);
clientMetadata.setAdvertisingInfo(advertisingId, isLimitAdTrackingEnabled);
}
static String reflectedGetAdvertisingId(final Object adInfo, final String defaultValue) {
try {
return (String) MethodBuilderFactory.create(adInfo, "getId").execute();
} catch (Exception exception) {
return defaultValue;
}
}
static boolean reflectedIsLimitAdTrackingEnabled(final Object adInfo, final boolean defaultValue) {
try {
Boolean result = (Boolean) MethodBuilderFactory.create(adInfo, "isLimitAdTrackingEnabled").execute();
return (result != null) ? result : defaultValue;
} catch (Exception exception) {
return defaultValue;
}
}
@Deprecated
static public void setClassNamesForTesting() {
// This method is used for testing only to help alleviate pain with testing
// unlinked libraries via reflection
// Set class names to something that is linked so Class.forName method doesn't throw
String className = "java.lang.Class";
sPlayServicesUtilClassName = className;
sAdvertisingIdClientClassName = className;
}
}