public class

HoldingConnectionClient

extends java.lang.Object

 java.lang.Object

↳androidx.ads.identifier.internal.HoldingConnectionClient

Gradle dependencies

compile group: 'androidx.ads', name: 'ads-identifier', version: '1.0.0-alpha04'

  • groupId: androidx.ads
  • artifactId: ads-identifier
  • version: 1.0.0-alpha04

Artifact androidx.ads:ads-identifier:1.0.0-alpha04 it located at Google repository (https://maven.google.com/)

Overview

A client which keeps the ServiceConnection to the IAdvertisingIdService.

Summary

Constructors
publicHoldingConnectionClient(Context context)

Methods
public longaskConnectionId()

Gets a connection ID before using this client which prevents race condition with the auto disconnection task.

public IAdvertisingIdServicegetIdService()

Gets the connected IAdvertisingIdService.

public java.lang.StringgetPackageName()

Gets the connected service's package name.

public booleanisConnected()

Gets whether the client is connected to the IAdvertisingIdService.

public booleantryFinish(long connectionId)

Tries to close the connection to the Advertising ID Provider Service if no one is using the client.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public HoldingConnectionClient(Context context)

Methods

public IAdvertisingIdService getIdService()

Gets the connected IAdvertisingIdService.

public java.lang.String getPackageName()

Gets the connected service's package name.

public boolean isConnected()

Gets whether the client is connected to the IAdvertisingIdService.

public long askConnectionId()

Gets a connection ID before using this client which prevents race condition with the auto disconnection task.

Returns:

connection ID, >= 0 indicates this client is connected, otherwise this client has already been disconnected.

public boolean tryFinish(long connectionId)

Tries to close the connection to the Advertising ID Provider Service if no one is using the client.

Returns:

true if this client is disconnected after this method returns.

Source

/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.ads.identifier.internal;

import static androidx.ads.identifier.AdvertisingIdUtils.GET_AD_ID_ACTION;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.IBinder;

import androidx.ads.identifier.AdvertisingIdNotAvailableException;
import androidx.ads.identifier.AdvertisingIdUtils;
import androidx.ads.identifier.provider.IAdvertisingIdService;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

/** A client which keeps the ServiceConnection to the {@link IAdvertisingIdService}. */
public class HoldingConnectionClient {

    private static final long SERVICE_CONNECTION_TIMEOUT_SECONDS = 10;

    private final Context mContext;

    @NonNull
    private final BlockingServiceConnection mConnection;

    @NonNull
    private final String mPackageName;

    @NonNull
    private final IAdvertisingIdService mIdService;

    /**
     * The last connection ID which assign to the users of this client.
     *
     * <p>This also indicates the connection status, >= 0 indicates this client is connected,
     * otherwise this client has already been disconnected.
     * <p>It helps to synchronize between the usages of this client and auto disconnection task by
     * using this single atomic, which supports 3 kinds of atomic operations:
     * <ul>
     *     <li>Checks whether this client is connected, if yes, increment and get a connection ID.
     *     <li>When an auto disconnect task is due, it compares its connection ID to this value, if
     *     same, unbind the service and sets this atomic to {@link Long#MIN_VALUE}.
     *     <li>When this client's connection has lost and
     *     {@link BlockingServiceConnection#onServiceDisconnected} is called, unbind the service
     *     and sets this atomic to {@link Long#MIN_VALUE}.
     * </ul>
     * <p>This ID is monotonically increasing, except when this client is disconnected, this ID
     * sets to {@link Long#MIN_VALUE}.
     */
    private final AtomicLong mLastConnectionId = new AtomicLong(0);

    @WorkerThread
    public HoldingConnectionClient(@NonNull Context context)
            throws AdvertisingIdNotAvailableException, IOException, TimeoutException,
            InterruptedException {
        mContext = context;
        ComponentName componentName = getProviderComponentName(mContext);
        mConnection = getServiceConnection(componentName);
        mIdService = getIdServiceFromConnection();
        mPackageName = componentName.getPackageName();
    }

    /** Gets the connected {@link IAdvertisingIdService}. */
    @NonNull
    public IAdvertisingIdService getIdService() {
        return mIdService;
    }

    /** Gets the connected service's package name. */
    @NonNull
    public String getPackageName() {
        return mPackageName;
    }

    /** Gets whether the client is connected to the {@link IAdvertisingIdService}. */
    public boolean isConnected() {
        return mLastConnectionId.get() >= 0;
    }

    /**
     * Gets a connection ID before using this client which prevents race condition with the auto
     * disconnection task.
     *
     * @return connection ID, >= 0 indicates this client is connected, otherwise this client has
     * already been disconnected.
     */
    public long askConnectionId() {
        return mLastConnectionId.incrementAndGet();
    }

    /**
     * Closes the connection to the Advertising ID Provider Service.
     *
     * <p>Note: If the connection has already been closed, does nothing.
     */
    void finish() {
        if (mLastConnectionId.getAndSet(Long.MIN_VALUE) >= 0) {
            mContext.unbindService(mConnection);
        }
    }

    /**
     * Tries to close the connection to the Advertising ID Provider Service if no one is using the
     * client.
     *
     * @return true if this client is disconnected after this method returns.
     */
    public boolean tryFinish(long connectionId) {
        if (mLastConnectionId.compareAndSet(connectionId, Long.MIN_VALUE)) {
            mContext.unbindService(mConnection);
            return true;
        }
        return !isConnected();
    }

    private static ComponentName getProviderComponentName(Context context)
            throws AdvertisingIdNotAvailableException {
        PackageManager packageManager = context.getPackageManager();
        List<ServiceInfo> serviceInfos =
                AdvertisingIdUtils.getAdvertisingIdProviderServices(packageManager);
        ServiceInfo serviceInfo =
                AdvertisingIdUtils.selectServiceByPriority(serviceInfos, packageManager);
        if (serviceInfo == null) {
            throw new AdvertisingIdNotAvailableException(
                    "No compatible AndroidX Advertising ID Provider available.");
        }
        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
    }

    /**
     * Retrieves BlockingServiceConnection which must be unbound after use.
     *
     * @throws IOException when unable to bind service successfully.
     */
    @VisibleForTesting
    BlockingServiceConnection getServiceConnection(ComponentName componentName) throws IOException {
        Intent intent = new Intent(GET_AD_ID_ACTION);
        intent.setComponent(componentName);

        BlockingServiceConnection bsc = new BlockingServiceConnection();
        if (mContext.bindService(intent, bsc, Service.BIND_AUTO_CREATE)) {
            return bsc;
        } else {
            throw new IOException("Connection failure");
        }
    }

    /**
     * Gets the {@link IAdvertisingIdService} from the blocking queue. This should wait until
     * {@link ServiceConnection#onServiceConnected} event with a
     * {@link #SERVICE_CONNECTION_TIMEOUT_SECONDS} second timeout.
     *
     * @throws TimeoutException     if connection timeout period has expired.
     * @throws InterruptedException if connection has been interrupted before connected.
     */
    @VisibleForTesting
    @WorkerThread
    IAdvertisingIdService getIdServiceFromConnection()
            throws TimeoutException, InterruptedException {
        // Block until the bind is complete, or timeout period is over.
        return IAdvertisingIdService.Stub.asInterface(mConnection.getServiceWithTimeout());
    }

    /**
     * A one-time use ServiceConnection that facilitates waiting for the bind to complete and the
     * passing of the IBinder from the callback thread to the waiting thread.
     */
    class BlockingServiceConnection implements ServiceConnection {
        // Facilitates passing of the IBinder across threads
        private final BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBlockingQueue.add(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            finish();
        }

        /**
         * Blocks until the bind is complete with a timeout and returns the bound IBinder. This must
         * only be called once.
         *
         * @return the IBinder of the bound service
         * @throws InterruptedException if the current thread is interrupted while waiting for
         *                              the bind
         * @throws TimeoutException     if the timeout period has elapsed
         */
        @WorkerThread
        @NonNull
        IBinder getServiceWithTimeout() throws InterruptedException, TimeoutException {
            IBinder binder =
                    mBlockingQueue.poll(SERVICE_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            if (binder == null) {
                throw new TimeoutException("Timed out waiting for the service connection");
            }
            return binder;
        }
    }
}