Using the new Android Advertiser id inside an SDK
Asked Answered
V

5

27

It makes a lot of sense that Android ad SDKs will use Android's the new advertiser id.

It seems that you can only get the id by using the google services sdk, as mentioned here: http://developer.android.com/google/play-services/id.html.

Using the google play services sdk, requires referencing the google-play-services_lib project, which causes several problems:

  1. A lot of SDKs are jars, meaning they can't use google-play-services_lib as is (because they can't include resources).
  2. If I only want the advertiser ID, I need to add google-play-services_lib to my project, which weights almost 1 MB.

Is there a way to only get the advertiser id, without using resources?

Vasilek answered 20/11, 2013 at 13:38 Comment(0)
F
45

I ran into the same issue, if you just need the advertiserId you could interact with the Google Play Service directly using an Intent. Example of custom class:

import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Parcel;
import android.os.RemoteException;

public final class AdvertisingIdClient {

public static final class AdInfo {
    private final String advertisingId;
    private final boolean limitAdTrackingEnabled;

    AdInfo(String advertisingId, boolean limitAdTrackingEnabled) {
        this.advertisingId = advertisingId;
        this.limitAdTrackingEnabled = limitAdTrackingEnabled;
    }

    public String getId() {
        return this.advertisingId;
    }

    public boolean isLimitAdTrackingEnabled() {
        return this.limitAdTrackingEnabled;
    }
}

public static AdInfo getAdvertisingIdInfo(Context context) throws Exception {
    if(Looper.myLooper() == Looper.getMainLooper()) throw new IllegalStateException("Cannot be called from the main thread");

    try { PackageManager pm = context.getPackageManager(); pm.getPackageInfo("com.android.vending", 0); }  
    catch (Exception e) { throw e; }

    AdvertisingConnection connection = new AdvertisingConnection();
    Intent intent = new Intent("com.google.android.gms.ads.identifier.service.START");
    intent.setPackage("com.google.android.gms");
    if(context.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
        try {
            AdvertisingInterface adInterface = new AdvertisingInterface(connection.getBinder());
            AdInfo adInfo = new AdInfo(adInterface.getId(), adInterface.isLimitAdTrackingEnabled(true));
            return adInfo;
        } catch (Exception exception) {
            throw exception;
        } finally {
            context.unbindService(connection);
        }
    }       
    throw new IOException("Google Play connection failed");     
}

private static final class AdvertisingConnection implements ServiceConnection {
    boolean retrieved = false;
    private final LinkedBlockingQueue<IBinder> queue = new LinkedBlockingQueue<IBinder>(1);

    public void onServiceConnected(ComponentName name, IBinder service) {
        try { this.queue.put(service); }
        catch (InterruptedException localInterruptedException){}
    }

    public void onServiceDisconnected(ComponentName name){}

    public IBinder getBinder() throws InterruptedException {
        if (this.retrieved) throw new IllegalStateException();
        this.retrieved = true;
        return (IBinder)this.queue.take();
    }
}

private static final class AdvertisingInterface implements IInterface {
    private IBinder binder;

    public AdvertisingInterface(IBinder pBinder) {
        binder = pBinder;
    }

    public IBinder asBinder() {
        return binder;
    }

    public String getId() throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        String id;
        try {
            data.writeInterfaceToken("com.google.android.gms.ads.identifier.internal.IAdvertisingIdService");
            binder.transact(1, data, reply, 0);
            reply.readException();
            id = reply.readString();
        } finally {
            reply.recycle();
            data.recycle();
        }
        return id;
    }

    public boolean isLimitAdTrackingEnabled(boolean paramBoolean) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        boolean limitAdTracking;
        try {
            data.writeInterfaceToken("com.google.android.gms.ads.identifier.internal.IAdvertisingIdService");
            data.writeInt(paramBoolean ? 1 : 0);
            binder.transact(2, data, reply, 0);
            reply.readException();
            limitAdTracking = 0 != reply.readInt();
        } finally {
            reply.recycle();
            data.recycle();
        }
        return limitAdTracking;
    }
}
}

Make sure that you are not calling this from the main UI thread. For example, use something like:

new Thread(new Runnable() {        
    public void run() {
        try {
            AdInfo adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context);
            advertisingId = adInfo.getId();
            optOutEnabled = adInfo.isLimitAdTrackingEnabled();
        } catch (Exception e) {
            e.printStackTrace();                            
        }                       
    }
}).start();
Fotinas answered 5/2, 2014 at 3:20 Comment(3)
This is dangerous to use i feel. Is something like this documented anywhere ?Herd
I get the following error at com.google.android.gms.ads.identifier.AdvertisingIdClient.getAdvertisingIdInfo : java.lang.NoSuchFieldError:android.content.pm.PackageInfo.signatures How can I avoid it?Blanks
working example here: github.com/facebook/facebook-android-sdk/blob/…Turman
G
9

Adrian's solution is excellent, and I use it myself.

However, today I discovered that it has a bug when Google Play Services is not installed on the device. You will get a message about leaking a ServiceConnection when your activity/service is stopped. This is actually a bug in Context.bindService: when binding to the service fails (in this case because Google Play Services is not installed), Context.bindService returns false, but it doesn't clear the reference to the ServiceConnection, and expects you to call Context.unbindService even though the service doesn't exist!

The workaround is to change the code of getAdvertisingIdInfo like this:

public static AdInfo getAdvertisingIdInfo(Context context) throws Exception {
    if(Looper.myLooper() == Looper.getMainLooper())
        throw new IllegalStateException("Cannot be called from the main thread");

    try {
        PackageManager pm = context.getPackageManager();
        pm.getPackageInfo("com.android.vending", 0);
    } catch(Exception e) {
        throw e;
    }

    AdvertisingConnection connection = new AdvertisingConnection();
    Intent intent = new Intent("com.google.android.gms.ads.identifier.service.START");
    intent.setPackage("com.google.android.gms");
    try {
        if(context.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
            AdvertisingInterface adInterface = new AdvertisingInterface(connection.getBinder());
            AdInfo adInfo = new AdInfo(adInterface.getId(), adInterface.isLimitAdTrackingEnabled(true));
            return adInfo;
        }
    } catch(Exception exception) {
        throw exception;
    } finally {
        context.unbindService(connection);
    }
    throw new IOException("Google Play connection failed");
}

That way Context.unbindService will be called even if Context.bindService returns false.

Goggles answered 27/4, 2015 at 14:17 Comment(0)
L
6

NOTE: My answer is outdated for Gradle since now you can choose which parts of the GooglePlayServices library you want to include in your project

I ran into the same problem lately when the project I was working on reached the 65k dex limit.

Here's how i solved it:

  • Go to https://code.google.com/p/jarjar/downloads/list and download the latest Jar jar Links in .jar format. Put the file in a work folder. For this example I'll use the desktop.

  • Go to [Android SDK Path]\extras\google\google_play_services\libproject\google-play-services_lib\libs and copy google-play-services.jar to the same work folder.

  • In the same folder make a text file named rules.txt (the name doesn't really matter).

  • Inside the rules.txt paste the text (without the quotes):

"keep com.google.android.gms.ads.identifier.AdvertisingIdClient"

  • If you want other classes you want to keep, you can add them here.

  • Open a command prompt file and change the path to your working folder. On Windows use the [cd] command.

  • Write the following command:

java -jar [jarjar archive] process [rulesFile] [inJar] [outJar]

java -jar jarjar-1.4.jar process rules.txt google-play-services.jar google-play-services-lite.jar

  • Execute the command.

WHAT IT DOES:

  • The command will generate a new java archive (*.jar) in the working folder that will contain only the class needed to get your advertiser id and its dependencies. So, the google-play-services.jar will go down from 2.2 Mb to ~50kb

HOW TO USE IT:

  • Import google play services from the sdk into your project as usual, make sure to copy it into your workspace. In the libs folder, replace the google-play-services.jar with the jar you generated earlier.

  • If you're there, you can delete the resources too to free another 0.5 mb. Make sure to keep values/common_strings.xml and values/version.xml.

  • Don't forget to add manifest metadata for google play services.

This helped me to reduce the project size with more than 2.5 mb and to stay under the 65k dex classes and methods limit while being able to access the Google advertiser id.

Hope it'll help you too.

Leroy answered 3/7, 2014 at 8:55 Comment(0)
C
1

MoPub and a few other big players are not including GPS into their SDKs. From MoPub's help page:

the MoPub SDK does not require Google Play Services. If you have it installed, we will automatically use the new Google Advertising ID. If you do NOT install Google Play Services, we will continue to pass the old Android ID. Note that all publishers need to use GPS in their app by August 1 to prevent their apps from being rejected by the Google Play Store

Check this link for a lot more detail:

http://help.mopub.com/customer/portal/articles/1523610-google-advertising-id-faqs

Hope this helps.

Continuo answered 1/7, 2014 at 7:0 Comment(0)
F
0

The only supported method of accessing Advertising ID is by directly linking to the Play Services SDK and accessing Advertising ID via those APIs. Google does not recommend or support any workaround that avoids direct access to the Play Services APIs because it breaks user facing functionality (such as error handling in cases where the Play Services app on the device is outdated) and its behavior will be unpredictable with future Play Services releases.

The Google Play Developer Program Policies require that you access the Google Play APIs only in an authorized manner.

Frankpledge answered 2/4, 2014 at 21:18 Comment(2)
Thanks Eric, that's good to know. However your response doesn't provide a solution to the 2 issues @Vasilek and most of us are having. Please let us know if there's a better approach.Fotinas
Facebook uses it in its sdk: github.com/facebook/facebook-android-sdk/blob/…Turman

© 2022 - 2024 — McMap. All rights reserved.