package com.o3dr.services.android.lib.util.googleApi; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; /** * Handles the lifecycle for the google api client. Also takes care of running submitted tasks * when the google api client is connected. */ public class GoogleApiClientManager implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private final static String TAG = GoogleApiClientManager.class.getSimpleName(); public interface ManagerListener { void onGoogleApiConnectionError(ConnectionResult result); void onUnavailableGooglePlayServices(int status); void onManagerStarted(); void onManagerStopped(); } /** * Manager background thread used to run the submitted google api client tasks. */ private final Runnable mDriverRunnable = new Runnable() { @Override public void run() { try { while (isStarted.get()) { if (!mGoogleApiClient.isConnected()) { stop(); continue; } final GoogleApiClientTask task = mTaskQueue.take(); if (task == null) continue; if (task.mRunOnBackgroundThread) { mBgHandler.post(task); } else { mMainHandler.post(task); } } } catch (InterruptedException e) { Log.v(TAG, e.getMessage(), e); } } }; private final GoogleApiClientTask stopTask = new GoogleApiClientTask() { @Override protected void doRun() { stop(); } }; private final AtomicBoolean isStarted = new AtomicBoolean(false); private Thread mDriverThread; /** * This handler is in charge of running google api client tasks on the calling thread. */ private final Handler mMainHandler; /** * This handler is in charge of running google api client tasks on the background thread. */ private Handler mBgHandler; private HandlerThread mBgHandlerThread; /** * Application context. */ private final Context mContext; /** * Handle to the google api client. */ private final GoogleApiClient mGoogleApiClient; private ManagerListener listener; /** * Holds tasks that needs to be run using the google api client. * A background thread will be blocking on this queue until new tasks are inserted. In which * case, it will retrieve the new task, and process it. */ private final LinkedBlockingQueue<GoogleApiClientTask> mTaskQueue = new LinkedBlockingQueue<>(); public GoogleApiClientManager(Context context, Handler handler, Api<? extends Api.ApiOptions.NotRequiredOptions>[] apis) { mContext = context; mMainHandler = handler; final GoogleApiClient.Builder apiBuilder = new GoogleApiClient.Builder(context); for (Api<? extends Api.ApiOptions.NotRequiredOptions> api : apis) { apiBuilder.addApi(api); } mGoogleApiClient = apiBuilder .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); } public void setManagerListener(ManagerListener listener) { this.listener = listener; } private void destroyBgHandler() { if (mBgHandlerThread != null && mBgHandlerThread.isAlive()) { mBgHandlerThread.quit(); mBgHandlerThread.interrupt(); mBgHandlerThread = null; } mBgHandler = null; } private void destroyDriverThread() { if (mDriverThread != null && mDriverThread.isAlive()) { mDriverThread.interrupt(); mDriverThread = null; } } private void initializeBgHandler() { if (mBgHandlerThread == null || mBgHandlerThread.isInterrupted()) { mBgHandlerThread = new HandlerThread("GAC Manager Background Thread"); mBgHandlerThread.start(); mBgHandler = null; } if (mBgHandler == null) { mBgHandler = new Handler(mBgHandlerThread.getLooper()); } } private void initializeDriverThread() { if (mDriverThread == null || mDriverThread.isInterrupted()) { mDriverThread = new Thread(mDriverRunnable, "GAC Manager Driver Thread"); mDriverThread.start(); } } /** * Adds a task to the google api client manager tasks queue. This task will be scheduled to * run on the calling thread. * * @param task task making use of the google api client. * @return true if the task was successfully added to the queue. * @throws IllegalStateException is the start() method was not called. */ public boolean addTask(GoogleApiClientTask task) { if (!isStarted()) { Log.d(TAG, "GoogleApiClientManager is not started."); return false; } task.gApiClient = mGoogleApiClient; task.taskQueue = mTaskQueue; task.mRunOnBackgroundThread = false; return mTaskQueue.offer(task); } /** * Adds a task to the google api client manager tasks queue. This task will be scheduled to * run on a background thread. * * @param task task making use of the google api client. * @return true if the task was successfully added to the queue. * @throws IllegalStateException is the start() method was not called. */ public boolean addTaskToBackground(GoogleApiClientTask task) { if (!isStarted()) { Log.d(TAG, "GoogleApiClientManager is not started."); return false; } task.gApiClient = mGoogleApiClient; task.taskQueue = mTaskQueue; task.mRunOnBackgroundThread = true; return mTaskQueue.offer(task); } /** * @return true the google api client manager was started. */ private boolean isStarted() { return isStarted.get(); } /** * Activates the google api client manager. */ public void start() { //Check if google play services is available. final int playStatus = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mContext); final boolean isValid = playStatus == ConnectionResult.SUCCESS; if (isValid) { //Clear the queue mTaskQueue.clear(); //Toggle the started flag isStarted.set(true); if (mGoogleApiClient.isConnected()) { onConnected(null); } else if (!mGoogleApiClient.isConnecting()) { //Connect to the google api. mGoogleApiClient.connect(); } } else { Log.e(TAG, "Google Play Services is unavailable."); if (listener != null) listener.onUnavailableGooglePlayServices(playStatus); } } // private boolean isGooglePlayServicesValid(){ // // Check for the google play services is available // // if(!isValid){ // PendingIntent errorPI = GooglePlayServicesUtil.getErrorPendingIntent(playStatus, mContext, 0); // if(errorPI != null){ // try { // errorPI.send(); // } catch (PendingIntent.CanceledException e) { // Log.e(TAG, "Seems the pending intent was cancelled.", e); // } // } // } // // return isValid; // } /** * Release the resources used by this manager. * After calling this method, start() needs to be called again to use that manager again. */ private void stop() { isStarted.set(false); destroyDriverThread(); destroyBgHandler(); mTaskQueue.clear(); if (mGoogleApiClient.isConnected() || mGoogleApiClient.isConnecting()) { mGoogleApiClient.disconnect(); } if(listener != null) listener.onManagerStopped(); } public void stopSafely(){ addTask(stopTask); } @Override public void onConnected(Bundle bundle) { initializeBgHandler(); initializeDriverThread(); if (listener != null) listener.onManagerStarted(); } @Override public void onConnectionSuspended(int i) { } @Override public void onConnectionFailed(ConnectionResult connectionResult) { if(listener != null) listener.onGoogleApiConnectionError(connectionResult); stop(); } /** * Type for the google api client tasks. */ public static abstract class GoogleApiClientTask implements Runnable { /** * If true, this task will be scheduled to run on a background thread. * Otherwise, it will run on the calling thread. */ private boolean mRunOnBackgroundThread = false; private GoogleApiClient gApiClient; private LinkedBlockingQueue<GoogleApiClientTask> taskQueue; protected GoogleApiClient getGoogleApiClient() { return gApiClient; } @Override public void run() { if (!gApiClient.isConnected()) { //Add the task back to the queue. taskQueue.offer(this); return; } //Run the task doRun(); } protected abstract void doRun(); } }