/* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.common.api; import android.content.Context; import android.os.Bundle; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApi; import com.google.android.gms.tasks.TaskCompletionSource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; public class GoogleApiManager { private static GoogleApiManager instance; private final Context context; private final Map clientMap = new HashMap<>(); private final Map>> waitingApiCallMap = new HashMap<>(); private GoogleApiManager(Context context) { this.context = context; } public synchronized static GoogleApiManager getInstance(Context context) { if (instance == null) instance = new GoogleApiManager(context); return instance; } private synchronized A clientForApi(GoogleApi api) { ApiInstance apiInstance = new ApiInstance(api); if (clientMap.containsKey(apiInstance)) { return (A) clientMap.get(apiInstance); } else { ApiClient client = api.api.getBuilder().build(api.getOptions(), context, context.getMainLooper(), null, new ConnectionCallback(apiInstance), new ConnectionFailedListener(apiInstance)); clientMap.put(apiInstance, client); waitingApiCallMap.put(apiInstance, new ArrayList<>()); return (A) client; } } public synchronized void scheduleTask(GoogleApi api, PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { A client = clientForApi(api); boolean connecting = client.isConnecting(); boolean connected = client.isConnected(); if (connected) { apiCall.execute(client, completionSource); } else { waitingApiCallMap.get(new ApiInstance(api)).add(new WaitingApiCall<>((PendingGoogleApiCall) apiCall, completionSource)); if (!connecting) { client.connect(); } } } private synchronized void onInstanceConnected(ApiInstance apiInstance, Bundle connectionHint) { List> waitingApiCalls = waitingApiCallMap.get(apiInstance); for (WaitingApiCall waitingApiCall : waitingApiCalls) { waitingApiCall.execute(clientMap.get(apiInstance)); } waitingApiCalls.clear(); } private synchronized void onInstanceSuspended(ApiInstance apiInstance, int cause) { } private synchronized void onInstanceFailed(ApiInstance apiInstance, ConnectionResult result) { List> waitingApiCalls = waitingApiCallMap.get(apiInstance); for (WaitingApiCall waitingApiCall : waitingApiCalls) { waitingApiCall.failed(new RuntimeException(result.getErrorMessage())); } waitingApiCalls.clear(); } private class ConnectionCallback implements ConnectionCallbacks { private final ApiInstance apiInstance; public ConnectionCallback(ApiInstance apiInstance) { this.apiInstance = apiInstance; } @Override public void onConnected(Bundle connectionHint) { onInstanceConnected(apiInstance, connectionHint); } @Override public void onConnectionSuspended(int cause) { onInstanceSuspended(apiInstance, cause); } } private class ConnectionFailedListener implements OnConnectionFailedListener { private final ApiInstance apiInstance; public ConnectionFailedListener(ApiInstance apiInstance) { this.apiInstance = apiInstance; } @Override public void onConnectionFailed(ConnectionResult result) { onInstanceFailed(apiInstance, result); } } private static class WaitingApiCall { private final PendingGoogleApiCall apiCall; private final TaskCompletionSource completionSource; public WaitingApiCall(PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { this.apiCall = apiCall; this.completionSource = completionSource; } public void execute(ApiClient client) { apiCall.execute(client, completionSource); } public void failed(Exception e) { completionSource.setException(e); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WaitingApiCall that = (WaitingApiCall) o; if (!Objects.equals(apiCall, that.apiCall)) return false; return Objects.equals(completionSource, that.completionSource); } @Override public int hashCode() { int result = apiCall != null ? apiCall.hashCode() : 0; result = 31 * result + (completionSource != null ? completionSource.hashCode() : 0); return result; } } private static class ApiInstance { private final Class apiClass; private final Api.ApiOptions apiOptions; public ApiInstance(Class apiClass, Api.ApiOptions apiOptions) { this.apiClass = apiClass; this.apiOptions = apiOptions; } public ApiInstance(GoogleApi api) { this(api.getClass(), api.getOptions()); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ApiInstance that = (ApiInstance) o; if (!Objects.equals(apiClass, that.apiClass)) return false; return Objects.equals(apiOptions, that.apiOptions); } @Override public int hashCode() { int result = apiClass != null ? apiClass.hashCode() : 0; result = 31 * result + (apiOptions != null ? apiOptions.hashCode() : 0); return result; } } }