Update lib for API changes, add support for wearable listening and sending messages

This commit is contained in:
Marvin W 2016-08-14 10:29:03 +02:00
parent 25d0e3be42
commit 29d8b8fbb5
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
42 changed files with 2033 additions and 1459 deletions

View File

@ -18,4 +18,7 @@
subprojects {
group = 'org.microg'
repositories {
jcenter()
}
}

2
extern/GmsApi vendored

@ -1 +1 @@
Subproject commit be6af2eeb76ba4bcc1700285f2ba87f217ab6ac2
Subproject commit b9cb95d39bdb4bbac6dd0d2b0405e4d5a23717c2

View File

@ -48,7 +48,6 @@ android {
}
dependencies {
compile 'com.android.support:support-v4:23.4.0'
compile project(':play-services-common-api')
compile project(':play-services-basement')
compile project(':play-services-tasks')
}

View File

@ -15,7 +15,7 @@
~ limitations under the License.
-->
<manifest package="com.google.android.gms"
<manifest package="org.microg.gms.base"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="9"/>

View File

@ -1,310 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 com.google.android.gms.common;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.text.TextUtils;
import com.google.android.gms.common.api.GoogleApiClient;
import java.util.Arrays;
/**
* Contains all possible error codes for when a client fails to connect to Google Play services.
* These error codes are used by {@link GoogleApiClient.OnConnectionFailedListener}.
*/
public class ConnectionResult {
/**
* The connection was successful.
*/
public static final int SUCCESS = 0;
/**
* Google Play services is missing on this device. The calling activity should pass this error
* code to {@link GooglePlayServicesUtil#getErrorDialog(int, Activity, int)} to get a localized
* error dialog that will resolve the error when shown.
*/
public static final int SERVICE_MISSING = 1;
/**
* The installed version of Google Play services is out of date. The calling activity should
* pass this error code to {@link GooglePlayServicesUtil#getErrorDialog(int, Activity, int)} to
* get a localized error dialog that will resolve the error when shown.
*/
public static final int SERVICE_VERSION_UPDATE_REQUIRED = 2;
/**
* The installed version of Google Play services has been disabled on this device. The calling
* activity should pass this error code to
* {@link GooglePlayServicesUtil#getErrorDialog(int, Activity, int)} to get a localized error
* dialog that will resolve the error when shown.
*/
public static final int SERVICE_DISABLED = 3;
/**
* The client attempted to connect to the service but the user is not signed in. The client may
* choose to continue without using the API or it may call
* {@link #startResolutionForResult(Activity, int)} to prompt the user to sign in. After the
* sign in activity returns with {@link Activity#RESULT_OK} further attempts to connect should
* succeed.
*/
public static final int SIGN_IN_REQUIRED = 4;
/**
* The client attempted to connect to the service with an invalid account name specified.
*/
public static final int INVALID_ACCOUNT = 5;
/**
* Completing the connection requires some form of resolution. A resolution will be available
* to be started with {@link #startResolutionForResult(Activity, int)}. If the result returned
* is {@link Activity#RESULT_OK}, then further attempts to connect should either complete or
* continue on to the next issue that needs to be resolved.
*/
public static final int RESOLUTION_REQUIRED = 6;
/**
* A network error occurred. Retrying should resolve the problem.
*/
public static final int NETWORK_ERROR = 7;
/**
* An internal error occurred. Retrying should resolve the problem.
*/
public static final int INTERNAL_ERROR = 8;
/**
* The version of the Google Play services installed on this device is not authentic.
*/
public static final int SERVICE_INVALID = 9;
/**
* The application is misconfigured. This error is not recoverable and will be treated as
* fatal. The developer should look at the logs after this to determine more actionable
* information.
*/
public static final int DEVELOPER_ERROR = 10;
/**
* The application is not licensed to the user. This error is not recoverable and will be
* treated as fatal.
*/
public static final int LICENSE_CHECK_FAILED = 11;
/**
* The client canceled the connection by calling {@link GoogleApiClient#disconnect()}.
* Only returned by {@link GoogleApiClient#blockingConnect()}.
*/
public static final int CANCELED = 13;
/**
* The timeout was exceeded while waiting for the connection to complete. Only returned by
* {@link GoogleApiClient#blockingConnect()}.
*/
public static final int TIMEOUT = 14;
/**
* An interrupt occurred while waiting for the connection complete. Only returned by
* {@link GoogleApiClient#blockingConnect()}.
*/
public static final int INTERRUPTED = 15;
/**
* One of the API components you attempted to connect to is not available. The API will not
* work on this device, and updating Google Play services will not likely solve the problem.
* Using the API on the device should be avoided.
*/
public static final int API_UNAVAILABLE = 16;
/**
* The Drive API requires external storage (such as an SD card), but no external storage is
* mounted. This error is recoverable if the user installs external storage (if none is
* present) and ensures that it is mounted (which may involve disabling USB storage mode,
* formatting the storage, or other initialization as required by the device).
* <p/>
* This error should never be returned on a device with emulated external storage. On devices
* with emulated external storage, the emulated "external storage" is always present regardless
* of whether the device also has removable storage.
*/
@Deprecated
public static final int DRIVE_EXTERNAL_STORAGE_REQUIRED = 1500;
private final int statusCode;
private final PendingIntent pendingIntent;
private final String message;
/**
* Creates a connection result.
*
* @param statusCode The status code.
*/
public ConnectionResult(int statusCode) {
this(statusCode, null);
}
/**
* Creates a connection result.
*
* @param statusCode The status code.
* @param pendingIntent A pending intent that will resolve the issue when started, or null.
*/
public ConnectionResult(int statusCode, PendingIntent pendingIntent) {
this(statusCode, pendingIntent, getStatusString(statusCode));
}
/**
* Creates a connection result.
*
* @param statusCode The status code.
* @param pendingIntent A pending intent that will resolve the issue when started, or null.
* @param message An additional error message for the connection result, or null.
*/
public ConnectionResult(int statusCode, PendingIntent pendingIntent, String message) {
this.statusCode = statusCode;
this.pendingIntent = pendingIntent;
this.message = message;
}
static String getStatusString(int statusCode) {
switch (statusCode) {
case -1:
return "UNKNOWN";
case 0:
return "SUCCESS";
case 1:
return "SERVICE_MISSING";
case 2:
return "SERVICE_VERSION_UPDATE_REQUIRED";
case 3:
return "SERVICE_DISABLED";
case 4:
return "SIGN_IN_REQUIRED";
case 5:
return "INVALID_ACCOUNT";
case 6:
return "RESOLUTION_REQUIRED";
case 7:
return "NETWORK_ERROR";
case 8:
return "INTERNAL_ERROR";
case 9:
return "SERVICE_INVALID";
case 10:
return "DEVELOPER_ERROR";
case 11:
return "LICENSE_CHECK_FAILED";
case 13:
return "CANCELED";
case 14:
return "TIMEOUT";
case 15:
return "INTERRUPTED";
case 16:
return "API_UNAVAILABLE";
case 17:
return "SIGN_IN_FAILED";
case 18:
return "SERVICE_UPDATING";
case 19:
return "SERVICE_MISSING_PERMISSION";
case 20:
return "RESTRICTED_PROFILE";
case 21:
return "API_VERSION_UPDATE_REQUIRED";
case 42:
return "UPDATE_ANDROID_WEAR";
case 99:
return "UNFINISHED";
case 1500:
return "DRIVE_EXTERNAL_STORAGE_REQUIRED";
default:
return "UNKNOWN_ERROR_CODE(" + statusCode + ")";
}
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof ConnectionResult)) {
return false;
} else {
ConnectionResult r = (ConnectionResult)o;
return statusCode == r.statusCode && pendingIntent == null ? r.pendingIntent == null : pendingIntent.equals(r.pendingIntent) && TextUtils.equals(message, r.message);
}
}
/**
* Indicates the type of error that interrupted connection.
*
* @return the error code, or {@link #SUCCESS} if no error occurred.
*/
public int getErrorCode() {
return statusCode;
}
/**
* Returns an error message for connection result.
*
* @return the message
*/
public String getErrorMessage() {
return message;
}
/**
* A pending intent to resolve the connection failure. This intent can be started with
* {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to
* present UI to solve the issue.
*
* @return The pending intent to resolve the connection failure.
*/
public PendingIntent getResolution() {
return pendingIntent;
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[]{statusCode, pendingIntent, message});
}
/**
* Returns {@code true} if calling {@link #startResolutionForResult(Activity, int)} will start
* any intents requiring user interaction.
*
* @return {@code true} if there is a resolution that can be started.
*/
public boolean hasResolution() {
return statusCode != 0 && pendingIntent != null;
}
/**
* Returns {@code true} if the connection was successful.
*
* @return {@code true} if the connection was successful, {@code false} if there was an error.
*/
public boolean isSuccess() {
return statusCode == 0;
}
/**
* Resolves an error by starting any intents requiring user interaction. See
* {@link #SIGN_IN_REQUIRED}, and {@link #RESOLUTION_REQUIRED}.
*
* @param activity An Activity context to use to resolve the issue. The activity's
* {@link Activity#onActivityResult} method will be invoked after the user
* is done. If the resultCode is {@link Activity#RESULT_OK}, the application
* should try to connect again.
* @param requestCode The request code to pass to {@link Activity#onActivityResult}.
* @throws IntentSender.SendIntentException If the resolution intent has been canceled or is no
* longer able to execute the request.
*/
public void startResolutionForResult(Activity activity, int requestCode) throws
IntentSender.SendIntentException {
if (hasResolution()) {
activity.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0);
}
}
}

View File

@ -41,13 +41,13 @@ public interface GooglePlayServicesClient {
void unregisterConnectionFailedListener(OnConnectionFailedListener listener);
@Deprecated
public interface OnConnectionFailedListener {
interface OnConnectionFailedListener {
void onConnectionFailed(ConnectionResult result);
}
@Deprecated
public interface ConnectionCallbacks {
interface ConnectionCallbacks {
void onConnected(Bundle connectionHint);

View File

@ -1,84 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 com.google.android.gms.common.api;
import org.microg.gms.common.PublicApi;
import org.microg.gms.common.api.ApiBuilder;
/**
* Describes a section of the Google Play Services API that should be made available. Instances of
* this should be passed into {@link GoogleApiClient.Builder#addApi(Api)} to enable the appropriate
* parts of Google Play Services.
* <p/>
* Google APIs are partitioned into sections which allow your application to configure only the
* services it requires. Each Google API provides an API object which can be passed to
* {@link GoogleApiClient.Builder#addApi(Api)} in order to configure and enable that functionality
* in your {@link GoogleApiClient} instance.
* <p/>
* See {@link GoogleApiClient.Builder} for usage examples.
*/
@PublicApi
public final class Api<O extends Api.ApiOptions> {
private final ApiBuilder<O> builder;
@PublicApi(exclude = true)
public Api(ApiBuilder<O> builder) {
this.builder = builder;
}
@PublicApi(exclude = true)
public ApiBuilder<O> getBuilder() {
return builder;
}
/**
* Base interface for API options. These are used to configure specific parameters for
* individual API surfaces. The default implementation has no parameters.
*/
@PublicApi
public interface ApiOptions {
/**
* Base interface for {@link ApiOptions} in {@link Api}s that have options.
*/
@PublicApi
interface HasOptions extends ApiOptions {
}
/**
* Base interface for {@link ApiOptions} that are not required, don't exist.
*/
@PublicApi
interface NotRequiredOptions extends ApiOptions {
}
/**
* {@link ApiOptions} implementation for {@link Api}s that do not take any options.
*/
@PublicApi
final class NoOptions implements NotRequiredOptions {
}
/**
* Base interface for {@link ApiOptions} that are optional.
*/
@PublicApi
interface Optional extends HasOptions, NotRequiredOptions {
}
}
}

View File

@ -1,494 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 com.google.android.gms.common.api;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import com.google.android.gms.common.ConnectionResult;
import org.microg.gms.auth.AuthConstants;
import org.microg.gms.common.PublicApi;
import org.microg.gms.common.api.GoogleApiClientImpl;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* The main entry point for Google Play services integration.
* <p/>
* GoogleApiClient is used with a variety of static methods. Some of these methods require that
* GoogleApiClient be connected, some will queue up calls before GoogleApiClient is connected;
* check the specific API documentation to determine whether you need to be connected.
* <p/>
* Before any operation is executed, the GoogleApiClient must be connected using the
* {@link #connect()} method. The client is not considered connected until the
* {@link ConnectionCallbacks#onConnected(Bundle)} callback has been called.
* <p/>
* When your app is done using this client, call {@link #disconnect()}, even if the async result
* from {@link #connect()} has not yet been delivered.
* <p/>
* You should instantiate a client object in your Activity's {@link Activity#onCreate(Bundle)}
* method and then call {@link #connect()} in {@link Activity#onStart()} and {@link #disconnect()}
* in {@link Activity#onStop()}, regardless of the state.
*/
@PublicApi
public interface GoogleApiClient {
/**
* Connects the client to Google Play services. Blocks until the connection either succeeds or
* fails. This is not allowed on the UI thread.
*
* @return the result of the connection
*/
public ConnectionResult blockingConnect();
/**
* Connects the client to Google Play services. Blocks until the connection is set or failed or
* has timed out. This is not allowed on the UI thread.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the {@code timeout} argument
* @return the result of the connection
*/
public ConnectionResult blockingConnect(long timeout, TimeUnit unit);
/**
* Clears the account selected by the user and reconnects the client asking the user to pick an
* account again if {@link Builder#useDefaultAccount()} was set.
*
* @return the pending result is fired once the default account has been cleared, but before
* the client is reconnected - for that {@link ConnectionCallbacks} can be used.
*/
public PendingResult<Status> clearDefaultAccountAndReconnect();
/**
* Connects the client to Google Play services. This method returns immediately, and connects
* to the service in the background. If the connection is successful,
* {@link ConnectionCallbacks#onConnected(Bundle)} is called and enqueued items are executed.
* On a failure, {@link OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} is
* called.
*/
public void connect();
/**
* Closes the connection to Google Play services. No calls can be made using this client after
* calling this method. Any method calls that haven't executed yet will be canceled. That is
* {@link ResultCallback#onResult(Result)} won't be called, if connection to the service hasn't
* been established yet all calls already made will be canceled.
*
* @see #connect()
*/
public void disconnect();
/**
* Checks if the client is currently connected to the service, so that requests to other
* methods will succeed. Applications should guard client actions caused by the user with a
* call to this method.
*
* @return {@code true} if the client is connected to the service.
*/
public boolean isConnected();
/**
* Checks if the client is attempting to connect to the service.
*
* @return {@code true} if the client is attempting to connect to the service.
*/
public boolean isConnecting();
/**
* Returns {@code true} if the specified listener is currently registered to receive connection
* events.
*
* @param listener The listener to check for.
* @return {@code true} if the specified listener is currently registered to receive connection
* events.
* @see #registerConnectionCallbacks(ConnectionCallbacks)
* @see #unregisterConnectionCallbacks(ConnectionCallbacks)
*/
public boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener);
/**
* Returns {@code true} if the specified listener is currently registered to receive connection
* failed events.
*
* @param listener The listener to check for.
* @return {@code true} if the specified listener is currently registered to receive connection
* failed events.
* @see #registerConnectionFailedListener(OnConnectionFailedListener)
* @see #unregisterConnectionFailedListener(OnConnectionFailedListener)
*/
public boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener);
/**
* Closes the current connection to Google Play services and creates a new connection.
* <p/>
* This method closes the current connection then returns immediately and reconnects to the
* service in the background.
* <p/>
* After calling this method, your application will receive
* {@link ConnectionCallbacks#onConnected(Bundle)} if the connection is successful, or
* {@link OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} if the connection
* failed.
*
* @see #connect()
* @see #disconnect()
*/
public void reconnect();
/**
* Registers a listener to receive connection events from this {@link GoogleApiClient}. If the
* service is already connected, the listener's {@link ConnectionCallbacks#onConnected(Bundle)}
* method will be called immediately. Applications should balance calls to this method with
* calls to {@link #unregisterConnectionCallbacks(ConnectionCallbacks)} to avoid leaking
* resources.
* <p/>
* If the specified listener is already registered to receive connection events, this method
* will not add a duplicate entry for the same listener, but will still call the listener's
* {@link ConnectionCallbacks#onConnected(Bundle)} method if currently connected.
* <p/>
* Note that the order of messages received here may not be stable, so clients should not rely
* on the order that multiple listeners receive events in.
*
* @param listener the listener where the results of the asynchronous {@link #connect()} call
* are delivered.
*/
public void registerConnectionCallbacks(ConnectionCallbacks listener);
/**
* Registers a listener to receive connection failed events from this {@link GoogleApiClient}.
* Unlike {@link #registerConnectionCallbacks(ConnectionCallbacks)}, if the service is not
* already connected, the listener's
* {@link OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} method will not be
* called immediately. Applications should balance calls to this method with calls to
* {@link #unregisterConnectionFailedListener(OnConnectionFailedListener)} to avoid leaking
* resources.
* <p/>
* If the specified listener is already registered to receive connection failed events, this
* method will not add a duplicate entry for the same listener.
* <p/>
* Note that the order of messages received here may not be stable, so clients should not rely
* on the order that multiple listeners receive events in.
*
* @param listener the listener where the results of the asynchronous {@link #connect()} call
* are delivered.
*/
public void registerConnectionFailedListener(OnConnectionFailedListener listener);
/**
* Disconnects the client and stops automatic lifecycle management. Use this before creating a
* new client (which might be necessary when switching accounts, changing the set of used APIs
* etc.).
* <p/>
* This method must be called from the main thread.
*
* @param lifecycleActivity the activity managing the client's lifecycle.
* @throws IllegalStateException if called from outside of the main thread.
* @see Builder#enableAutoManage(FragmentActivity, int, OnConnectionFailedListener)
*/
public void stopAutoManager(FragmentActivity lifecycleActivity) throws IllegalStateException;
/**
* Removes a connection listener from this {@link GoogleApiClient}. Note that removing a
* listener does not generate any callbacks.
* <p/>
* If the specified listener is not currently registered to receive connection events, this
* method will have no effect.
*
* @param listener the listener to unregister.
*/
public void unregisterConnectionCallbacks(ConnectionCallbacks listener);
/**
* Removes a connection failed listener from the {@link GoogleApiClient}. Note that removing a
* listener does not generate any callbacks.
* <p/>
* If the specified listener is not currently registered to receive connection failed events,
* this method will have no effect.
*
* @param listener the listener to unregister.
*/
public void unregisterConnectionFailedListener(OnConnectionFailedListener listener);
/**
* Builder to configure a {@link GoogleApiClient}.
*/
@PublicApi
public class Builder {
private final Context context;
private final Map<Api, Api.ApiOptions> apis = new HashMap<Api, Api.ApiOptions>();
private final Set<ConnectionCallbacks> connectionCallbacks = new HashSet<ConnectionCallbacks>();
private final Set<OnConnectionFailedListener> connectionFailedListeners = new HashSet<OnConnectionFailedListener>();
private final Set<String> scopes = new HashSet<String>();
private String accountName;
private int clientId = -1;
private FragmentActivity fragmentActivity;
private Looper looper;
private int gravityForPopups;
private OnConnectionFailedListener unresolvedConnectionFailedListener;
private View viewForPopups;
/**
* Builder to help construct the {@link GoogleApiClient} object.
*
* @param context The context to use for the connection.
*/
public Builder(Context context) {
this.context = context;
this.looper = context.getMainLooper();
}
/**
* Builder to help construct the {@link GoogleApiClient} object.
*
* @param context The context to use for the connection.
* @param connectedListener The listener where the results of the asynchronous
* {@link #connect()} call are delivered.
* @param connectionFailedListener The listener which will be notified if the connection
* attempt fails.
*/
public Builder(Context context, ConnectionCallbacks connectedListener,
OnConnectionFailedListener connectionFailedListener) {
this(context);
addConnectionCallbacks(connectedListener);
addOnConnectionFailedListener(connectionFailedListener);
}
/**
* Specify which Apis are requested by your app. See {@link Api} for more information.
*
* @param api The Api requested by your app.
* @param options Any additional parameters required for the specific AP
* @see Api
*/
public <O extends Api.ApiOptions.HasOptions> Builder addApi(Api<O> api, O options) {
apis.put(api, options);
return this;
}
/**
* Specify which Apis are requested by your app. See {@link Api} for more information.
*
* @param api The Api requested by your app.
* @see Api
*/
public Builder addApi(Api<? extends Api.ApiOptions.NotRequiredOptions> api) {
apis.put(api, null);
return this;
}
/**
* Registers a listener to receive connection events from this {@link GoogleApiClient}.
* Applications should balance calls to this method with calls to
* {@link #unregisterConnectionCallbacks(ConnectionCallbacks)} to avoid
* leaking resources.
* <p/>
* If the specified listener is already registered to receive connection events, this
* method will not add a duplicate entry for the same listener.
* <p/>
* Note that the order of messages received here may not be stable, so clients should not
* rely on the order that multiple listeners receive events in.
*
* @param listener the listener where the results of the asynchronous {@link #connect()}
* call are delivered.
*/
public Builder addConnectionCallbacks(ConnectionCallbacks listener) {
connectionCallbacks.add(listener);
return this;
}
/**
* Adds a listener to register to receive connection failed events from this
* {@link GoogleApiClient}. Applications should balance calls to this method with calls to
* {@link #unregisterConnectionFailedListener(OnConnectionFailedListener)} to avoid
* leaking resources.
* <p/>
* If the specified listener is already registered to receive connection failed events,
* this method will not add a duplicate entry for the same listener.
* <p/>
* Note that the order of messages received here may not be stable, so clients should not
* rely on the order that multiple listeners receive events in.
*
* @param listener the listener where the results of the asynchronous {@link #connect()}
* call are delivered.
*/
public Builder addOnConnectionFailedListener(OnConnectionFailedListener listener) {
connectionFailedListeners.add(listener);
return this;
}
/**
* Specify the OAuth 2.0 scopes requested by your app. See
* {@link com.google.android.gms.common.Scopes} for more information.
*
* @param scope The OAuth 2.0 scopes requested by your app.
* @see com.google.android.gms.common.Scopes
*/
public Builder addScope(Scope scope) {
scopes.add(scope.getScopeUri());
return this;
}
/**
* Builds a new {@link GoogleApiClient} object for communicating with the Google APIs.
*
* @return The {@link GoogleApiClient} object.
*/
public GoogleApiClient build() {
return new GoogleApiClientImpl(context, looper, getAccountInfo(), apis,
connectionCallbacks, connectionFailedListeners, clientId);
}
private AccountInfo getAccountInfo() {
return null;
}
public Builder enableAutoManage(FragmentActivity fragmentActivity, int cliendId,
OnConnectionFailedListener unresolvedConnectionFailedListener)
throws NullPointerException, IllegalArgumentException, IllegalStateException {
this.fragmentActivity = fragmentActivity;
this.clientId = cliendId;
this.unresolvedConnectionFailedListener = unresolvedConnectionFailedListener;
return this;
}
/**
* Specify an account name on the device that should be used. If this is never called, the
* client will use the current default account for Google Play services for this
* application.
*
* @param accountName The account name on the device that should be used by
* {@link GoogleApiClient}.
*/
public Builder setAccountName(String accountName) {
this.accountName = accountName;
return this;
}
/**
* Specifies the part of the screen at which games service popups (for example,
* "welcome back" or "achievement unlocked" popups) will be displayed using gravity.
*
* @param gravityForPopups The gravity which controls the placement of games service popups.
*/
public Builder setGravityForPopups(int gravityForPopups) {
this.gravityForPopups = gravityForPopups;
return this;
}
/**
* Sets a {@link Handler} to indicate which thread to use when invoking callbacks. Will not
* be used directly to handle callbacks. If this is not called then the application's main
* thread will be used.
*/
public Builder setHandler(Handler handler) {
this.looper = handler.getLooper();
return this;
}
/**
* Sets the {@link View} to use as a content view for popups.
*
* @param viewForPopups The view to use as a content view for popups. View cannot be null.
*/
public Builder setViewForPopups(View viewForPopups) {
this.viewForPopups = viewForPopups;
return this;
}
/**
* Specify that the default account should be used when connecting to services.
*/
public Builder useDefaultAccount() {
this.accountName = AuthConstants.DEFAULT_ACCOUNT;
return this;
}
}
/**
* Provides callbacks that are called when the client is connected or disconnected from the
* service. Most applications implement {@link #onConnected(Bundle)} to start making requests.
*/
@PublicApi
public interface ConnectionCallbacks {
/**
* A suspension cause informing that the service has been killed.
*/
int CAUSE_SERVICE_DISCONNECTED = 1;
/**
* A suspension cause informing you that a peer device connection was lost.
*/
int CAUSE_NETWORK_LOST = 2;
/**
* After calling {@link #connect()}, this method will be invoked asynchronously when the
* connect request has successfully completed. After this callback, the application can
* make requests on other methods provided by the client and expect that no user
* intervention is required to call methods that use account and scopes provided to the
* client constructor.
* <p/>
* Note that the contents of the {@code connectionHint} Bundle are defined by the specific
* services. Please see the documentation of the specific implementation of
* {@link GoogleApiClient} you are using for more information.
*
* @param connectionHint Bundle of data provided to clients by Google Play services. May
* be null if no content is provided by the service.
*/
void onConnected(Bundle connectionHint);
/**
* Called when the client is temporarily in a disconnected state. This can happen if there
* is a problem with the remote service (e.g. a crash or resource problem causes it to be
* killed by the system). When called, all requests have been canceled and no outstanding
* listeners will be executed. GoogleApiClient will automatically attempt to restore the
* connection. Applications should disable UI components that require the service, and wait
* for a call to {@link #onConnected(Bundle)} to re-enable them.
*
* @param cause The reason for the disconnection. Defined by constants {@code CAUSE_*}.
*/
void onConnectionSuspended(int cause);
}
/**
* Provides callbacks for scenarios that result in a failed attempt to connect the client to
* the service. See {@link ConnectionResult} for a list of error codes and suggestions for
* resolution.
*/
@PublicApi
public interface OnConnectionFailedListener {
/**
* Called when there was an error connecting the client to the service.
*
* @param result A {@link ConnectionResult} that can be used for resolving the error, and
* deciding what sort of error occurred. To resolve the error, the resolution
* must be started from an activity with a non-negative {@code requestCode}
* passed to {@link ConnectionResult#startResolutionForResult(Activity, int)}.
* Applications should implement {@link Activity#onActivityResult} in their
* Activity to call {@link #connect()} again if the user has resolved the
* issue (resultCode is {@link Activity#RESULT_OK}).
*/
void onConnectionFailed(ConnectionResult result);
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 com.google.android.gms.common.api;
import java.util.concurrent.TimeUnit;
/**
* Represents a pending result from calling an API method in Google Play services. The final result
* object from a PendingResult is of type R, which can be retrieved in one of two ways.
* <p/>
* <ul>
* <li>via blocking calls to {@link #await()}, or {@link #await(long, TimeUnit)}, or</li>
* <li>via a callback by passing in an object implementing interface {@link ResultCallback} to
* {@link #setResultCallback(ResultCallback)}.</li>
* </ul>
* After the result has been retrieved using {@link #await()} or delivered to the result callback,
* it is an error to attempt to retrieve the result again. It is the responsibility of the caller
* or callback receiver to release any resources associated with the returned result. Some result
* types may implement {@link Releasable}, in which case {@link Releasable#release()} should be
* used to free the associated resources.
* <p/>
* TODO: Docs
*/
public interface PendingResult<R extends Result> {
/**
* Blocks until the task is completed. This is not allowed on the UI thread. The returned
* result object can have an additional failure mode of INTERRUPTED.
*/
public R await();
/**
* Blocks until the task is completed or has timed out waiting for the result. This is not
* allowed on the UI thread. The returned result object can have an additional failure mode
* of either INTERRUPTED or TIMEOUT.
*/
public R await(long time, TimeUnit unit);
public void cancel();
public boolean isCanceled();
public void setResultCallback(ResultCallback<R> callback, long time, TimeUnit unit);
public void setResultCallback(ResultCallback<R> callback);
}

View File

@ -1,25 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 com.google.android.gms.common.api;
/**
* Represents a resource, or a holder of resources, which may be released once they are no longer
* needed.
*/
public interface Releasable {
public void release();
}

View File

@ -1,33 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 com.google.android.gms.common.api;
/**
* An interface for receiving a {@link Result} from a {@link PendingResult} as an asynchronous
* callback.
*/
public interface ResultCallback<R extends Result> {
/**
* Called when the {@link Result} is ready. It is the responsibility of each callback to
* release any resources associated with the result. Some result types may implement
* {@link Releasable}, in which case {@link Releasable#release()} should be used to free the
* associated resources.
*
* @param result The result from the API call. May not be null.
*/
public void onResult(R result);
}

View File

@ -16,6 +16,7 @@
package org.microg.gms.common;
import android.accounts.Account;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
@ -27,6 +28,7 @@ import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.IGmsCallbacks;
import com.google.android.gms.common.internal.IGmsServiceBroker;
@ -41,18 +43,31 @@ public abstract class GmsClient<I extends IInterface> implements ApiConnection {
protected ConnectionState state = ConnectionState.NOT_CONNECTED;
private ServiceConnection serviceConnection;
private I serviceInterface;
private String actionString;
protected int serviceId = -1;
protected Account account = null;
protected Bundle extras = new Bundle();
public GmsClient(Context context, GoogleApiClient.ConnectionCallbacks callbacks,
GoogleApiClient.OnConnectionFailedListener connectionFailedListener) {
GoogleApiClient.OnConnectionFailedListener connectionFailedListener, String actionString) {
this.context = context;
this.callbacks = callbacks;
this.connectionFailedListener = connectionFailedListener;
this.actionString = actionString;
}
protected abstract String getActionString();
protected abstract void onConnectedToBroker(IGmsServiceBroker broker, GmsCallbacks callbacks)
throws RemoteException;
protected void onConnectedToBroker(IGmsServiceBroker broker, GmsCallbacks callbacks) throws RemoteException {
if (serviceId == -1) {
throw new IllegalStateException("Service ID not set in constructor and onConnectedToBroker not implemented");
}
GetServiceRequest request = new GetServiceRequest(serviceId);
request.extras = new Bundle();
request.packageName = context.getPackageName();
request.account = account;
request.extras = extras;
broker.getService(callbacks, request);
}
protected abstract I interfaceFromBinder(IBinder binder);
@ -64,12 +79,10 @@ public abstract class GmsClient<I extends IInterface> implements ApiConnection {
}
state = ConnectionState.CONNECTING;
if (serviceConnection != null) {
MultiConnectionKeeper.getInstance(context)
.unbind(getActionString(), serviceConnection);
MultiConnectionKeeper.getInstance(context).unbind(actionString, serviceConnection);
}
serviceConnection = new GmsServiceConnection();
if (!MultiConnectionKeeper.getInstance(context).bind(getActionString(),
serviceConnection)) {
if (!MultiConnectionKeeper.getInstance(context).bind(actionString, serviceConnection)) {
state = ConnectionState.ERROR;
handleConnectionFailed();
}
@ -90,7 +103,7 @@ public abstract class GmsClient<I extends IInterface> implements ApiConnection {
}
serviceInterface = null;
if (serviceConnection != null) {
MultiConnectionKeeper.getInstance(context).unbind(getActionString(), serviceConnection);
MultiConnectionKeeper.getInstance(context).unbind(actionString, serviceConnection);
serviceConnection = null;
}
state = ConnectionState.NOT_CONNECTED;

View File

@ -23,25 +23,29 @@ import android.util.Log;
import com.google.android.gms.common.api.Api;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Result;
import org.microg.gms.common.api.AbstractPendingResult;
import org.microg.gms.common.api.ApiConnection;
import org.microg.gms.common.api.GoogleApiClientImpl;
public class GmsConnector<C extends ApiConnection, R extends Result, O extends Api.ApiOptions> {
public class GmsConnector<C extends ApiConnection, R extends Result> {
private static final String TAG = "GmsConnector";
private final GoogleApiClientImpl apiClient;
private final Api<O> api;
private final Api api;
private final Callback<C, R> callback;
public GmsConnector(GoogleApiClient apiClient, Api<O> api, Callback<C, R> callback) {
public GmsConnector(GoogleApiClient apiClient, Api api, Callback<C, R> callback) {
this.apiClient = (GoogleApiClientImpl) apiClient;
this.api = api;
this.callback = callback;
}
public static <C extends ApiConnection, R extends Result> PendingResult<R> call(GoogleApiClient client, Api api, GmsConnector.Callback<C, R> callback) {
return new GmsConnector<C, R>(client, api, callback).connect();
}
public AbstractPendingResult<R> connect() {
Log.d(TAG, "connect()");
@ -54,8 +58,12 @@ public class GmsConnector<C extends ApiConnection, R extends Result, O extends A
return result;
}
public static interface Callback<C, R> {
public R onClientAvailable(C client) throws RemoteException;
public interface Callback<C, R> {
void onClientAvailable(C client, ResultProvider<R> resultProvider) throws RemoteException;
interface ResultProvider<R> {
void onResultAvailable(R result);
}
}
private class Handler extends android.os.Handler {
@ -66,10 +74,15 @@ public class GmsConnector<C extends ApiConnection, R extends Result, O extends A
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "Handler : handleMessage");
AbstractPendingResult<R> result = (AbstractPendingResult<R>) msg.obj;
final AbstractPendingResult<R> result = (AbstractPendingResult<R>) msg.obj;
try {
C connection = (C)apiClient.getApiConnection(api);
result.deliverResult(callback.onClientAvailable(connection));
C connection = (C) apiClient.getApiConnection(api);
callback.onClientAvailable(connection, new GmsConnector.Callback.ResultProvider<R>() {
@Override
public void onResultAvailable(R realResult) {
result.deliverResult(realResult);
}
});
} catch (RemoteException ignored) {
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 org.microg.gms.common.api;
import android.content.Context;
import android.os.Looper;
import com.google.android.gms.common.api.AccountInfo;
import com.google.android.gms.common.api.Api;
import com.google.android.gms.common.api.GoogleApiClient;
public interface ApiBuilder<O extends Api.ApiOptions> {
ApiConnection build(Context context, Looper looper, O options, AccountInfo accountInfo,
GoogleApiClient.ConnectionCallbacks callbacks,
GoogleApiClient.OnConnectionFailedListener connectionFailedListener);
}

View File

@ -1,27 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 org.microg.gms.common.api;
public interface ApiConnection {
void connect();
void disconnect();
boolean isConnected();
boolean isConnecting();
}

View File

@ -1,223 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 org.microg.gms.common.api;
import android.content.Context;
import android.os.Bundle;
import android.os.Looper;
import android.os.Message;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.AccountInfo;
import com.google.android.gms.common.api.Api;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Status;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class GoogleApiClientImpl implements GoogleApiClient {
private static final String TAG = "GmsApiClientImpl";
private final Context context;
private final Looper looper;
private final AccountInfo accountInfo;
private final Map<Api, Api.ApiOptions> apis = new HashMap<Api, Api.ApiOptions>();
private final Map<Api, ApiConnection> apiConnections = new HashMap<Api, ApiConnection>();
private final Handler handler;
private final Set<ConnectionCallbacks> connectionCallbacks = new HashSet<ConnectionCallbacks>();
private final Set<OnConnectionFailedListener> connectionFailedListeners = new HashSet<OnConnectionFailedListener>();
private final int clientId;
private final ConnectionCallbacks baseConnectionCallbacks = new ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "ConnectionCallbacks : onConnected()");
for (ConnectionCallbacks callback : connectionCallbacks) {
callback.onConnected(connectionHint);
}
}
@Override
public void onConnectionSuspended(int cause) {
Log.d(TAG, "ConnectionCallbacks : onConnectionSuspended()");
for (ConnectionCallbacks callback : connectionCallbacks) {
callback.onConnectionSuspended(cause);
}
}
};
private final OnConnectionFailedListener baseConnectionFailedListener = new
OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.d(TAG, "OnConnectionFailedListener : onConnectionFailed()");
for (OnConnectionFailedListener listener : connectionFailedListeners) {
listener.onConnectionFailed(result);
}
}
};
public GoogleApiClientImpl(Context context, Looper looper, AccountInfo accountInfo,
Map<Api, Api.ApiOptions> apis,
Set<ConnectionCallbacks> connectionCallbacks,
Set<OnConnectionFailedListener> connectionFailedListeners, int clientId) {
this.context = context;
this.looper = looper;
this.handler = new Handler(looper);
this.accountInfo = accountInfo;
this.apis.putAll(apis);
this.connectionCallbacks.addAll(connectionCallbacks);
this.connectionFailedListeners.addAll(connectionFailedListeners);
this.clientId = clientId;
for (Api api : apis.keySet()) {
apiConnections.put(api, api.getBuilder().build(context, looper,
apis.get(api), accountInfo, baseConnectionCallbacks,
baseConnectionFailedListener));
}
}
public Looper getLooper() {
return looper;
}
public ApiConnection getApiConnection(Api api) {
return apiConnections.get(api);
}
@Override
public ConnectionResult blockingConnect() {
return null;
}
@Override
public ConnectionResult blockingConnect(long timeout, TimeUnit unit) {
return null;
}
@Override
public PendingResult<Status> clearDefaultAccountAndReconnect() {
return null;
}
@Override
public synchronized void connect() {
Log.d(TAG, "connect()");
if (isConnected() || isConnecting()) {
Log.d(TAG, "Already connected/connecting, nothing to do");
return;
}
for (ApiConnection connection : apiConnections.values()) {
if (!connection.isConnected()) {
connection.connect();
}
}
}
@Override
public synchronized void disconnect() {
Log.d(TAG, "disconnect()");
for (ApiConnection connection : apiConnections.values()) {
if (connection.isConnected()) {
connection.disconnect();
}
}
}
@Override
public synchronized boolean isConnected() {
for (ApiConnection connection : apiConnections.values()) {
if (!connection.isConnected()) return false;
}
return true;
}
@Override
public synchronized boolean isConnecting() {
for (ApiConnection connection : apiConnections.values()) {
if (connection.isConnecting()) return true;
}
return false;
}
@Override
public boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener) {
return connectionCallbacks.contains(listener);
}
@Override
public boolean isConnectionFailedListenerRegistered(
OnConnectionFailedListener listener) {
return connectionFailedListeners.contains(listener);
}
@Override
public synchronized void reconnect() {
Log.d(TAG, "reconnect()");
disconnect();
connect();
}
@Override
public void registerConnectionCallbacks(ConnectionCallbacks listener) {
connectionCallbacks.add(listener);
}
@Override
public void registerConnectionFailedListener(OnConnectionFailedListener listener) {
connectionFailedListeners.add(listener);
}
@Override
public void stopAutoManager(FragmentActivity lifecycleActivity) throws IllegalStateException {
}
@Override
public void unregisterConnectionCallbacks(ConnectionCallbacks listener) {
connectionCallbacks.remove(listener);
}
@Override
public void unregisterConnectionFailedListener(OnConnectionFailedListener listener) {
connectionFailedListeners.remove(listener);
}
private class Handler extends android.os.Handler {
private Handler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
if (msg.what == 0 && msg.obj instanceof Runnable) {
((Runnable) msg.obj).run();
} else {
super.handleMessage(msg);
}
}
public void sendRunnable(Runnable runnable) {
sendMessage(obtainMessage(1, runnable));
}
}
}

1
play-services-basement Symbolic link
View File

@ -0,0 +1 @@
extern/GmsApi/play-services-basement

View File

@ -15,10 +15,10 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.microg.gms.cast">
<manifest package="org.microg.gms.cast"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="9" />
<uses-sdk android:minSdkVersion="9"/>
<application />
<application/>
</manifest>

View File

@ -1 +0,0 @@
extern/GmsApi/play-services-common-api/

View File

@ -22,7 +22,6 @@ import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.common.api.Api;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Result;
@ -33,6 +32,7 @@ import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import org.microg.gms.common.GmsConnector;
import org.microg.gms.common.api.ApiConnection;
public class FusedLocationProviderApiImpl implements FusedLocationProviderApi {
private static final String TAG = "GmsFusedApiImpl";
@ -50,7 +50,7 @@ public class FusedLocationProviderApiImpl implements FusedLocationProviderApi {
@Override
public PendingResult requestLocationUpdates(GoogleApiClient client,
final LocationRequest request, final LocationListener listener) {
final LocationRequest request, final LocationListener listener) {
return callVoid(client, new Runnable() {
@Override
public void run(LocationClientImpl client) throws RemoteException {
@ -61,8 +61,8 @@ public class FusedLocationProviderApiImpl implements FusedLocationProviderApi {
@Override
public PendingResult requestLocationUpdates(GoogleApiClient client,
final LocationRequest request, final LocationListener listener,
final Looper looper) {
final LocationRequest request, final LocationListener listener,
final Looper looper) {
return callVoid(client, new Runnable() {
@Override
public void run(LocationClientImpl client) throws RemoteException {
@ -73,7 +73,7 @@ public class FusedLocationProviderApiImpl implements FusedLocationProviderApi {
@Override
public PendingResult requestLocationUpdates(GoogleApiClient client,
final LocationRequest request, final PendingIntent callbackIntent) {
final LocationRequest request, final PendingIntent callbackIntent) {
return callVoid(client, new Runnable() {
@Override
public void run(LocationClientImpl client) throws RemoteException {
@ -84,7 +84,7 @@ public class FusedLocationProviderApiImpl implements FusedLocationProviderApi {
@Override
public PendingResult removeLocationUpdates(GoogleApiClient client,
final LocationListener listener) {
final LocationListener listener) {
return callVoid(client, new Runnable() {
@Override
public void run(LocationClientImpl client) throws RemoteException {
@ -95,7 +95,7 @@ public class FusedLocationProviderApiImpl implements FusedLocationProviderApi {
@Override
public PendingResult removeLocationUpdates(GoogleApiClient client,
final PendingIntent callbackIntent) {
final PendingIntent callbackIntent) {
return callVoid(client, new Runnable() {
@Override
public void run(LocationClientImpl client) throws RemoteException {
@ -125,15 +125,13 @@ public class FusedLocationProviderApiImpl implements FusedLocationProviderApi {
}
private PendingResult callVoid(GoogleApiClient client, final Runnable runnable) {
return new GmsConnector<LocationClientImpl, Result, Api.ApiOptions.NoOptions>(client, LocationServices.API,
new GmsConnector.Callback<LocationClientImpl, Result>() {
@Override
public Result onClientAvailable(LocationClientImpl client) throws
RemoteException {
runnable.run(client);
return Status.SUCCESS;
}
}).connect();
return GmsConnector.call(client, LocationServices.API, new GmsConnector.Callback<LocationClientImpl, Result>() {
@Override
public void onClientAvailable(LocationClientImpl client, ResultProvider<Result> resultProvider) throws RemoteException {
runnable.run(client);
resultProvider.onResultAvailable(Status.SUCCESS);
}
});
}
private interface Runnable {

View File

@ -32,12 +32,7 @@ import org.microg.gms.common.GmsService;
public abstract class GoogleLocationManagerClient extends GmsClient<IGoogleLocationManagerService> {
public GoogleLocationManagerClient(Context context, GoogleApiClient.ConnectionCallbacks
callbacks, GoogleApiClient.OnConnectionFailedListener connectionFailedListener) {
super(context, callbacks, connectionFailedListener);
}
@Override
protected String getActionString() {
return GmsService.LOCATION_MANAGER.ACTION;
super(context, callbacks, connectionFailedListener, GmsService.LOCATION_MANAGER.ACTION);
}
@Override

View File

@ -48,5 +48,5 @@ android {
}
dependencies {
compile project(':play-services-common-api')
compile project(':play-services-basement')
}

View File

@ -42,6 +42,12 @@ android {
versionName getMyVersionName()
}
sourceSets {
main {
java.srcDirs += 'src/main/protos-java'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
}
@ -50,4 +56,5 @@ android {
dependencies {
compile project(':play-services-base')
compile project(':play-services-wearable-api')
compile 'com.squareup.wire:wire-runtime:1.6.1'
}

View File

@ -0,0 +1,193 @@
/*
* Copyright 2013-2016 microG Project Team
*
* 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 com.google.android.gms.wearable;
import android.net.Uri;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.Builder;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Result;
import com.google.android.gms.common.api.Status;
import org.microg.gms.common.PublicApi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
/**
* Exposes an API to learn about capabilities provided by nodes on the Wear network.
* <p/>
* Capabilities are local to an application.
*/
@PublicApi
public interface CapabilityApi {
/**
* Capability changed action for use in manifest-based listener filters.
* <p/>
* Capability events do not support filtering by host, but can be filtered by path.
*
* @see WearableListenerService
*/
String ACTION_CAPABILITY_CHANGED = "com.google.android.gms.wearable.CAPABILITY_CHANGED";
/**
* Filter type for {@link #getCapability(GoogleApiClient, String, int)}, {@link #getAllCapabilities(GoogleApiClient, int)}:
* If this filter is set then the full set of nodes that declare the given capability will be
* included in the capability's CapabilityInfo.
*/
int FILTER_ALL = 0;
/**
* Filter type for {@link #addListener(GoogleApiClient, CapabilityListener, Uri, int)}, if this
* filter is set, the given URI will be taken as a literal path, and the operation will apply
* to the matching capability only.
*/
int FILTER_LITERAL = 0;
/**
* Filter type for {@link #addListener(GoogleApiClient, CapabilityListener, Uri, int)}, if this
* filter is set, the given URI will be taken as a path prefix, and the operation will apply
* to all matching capabilities.
*/
int FILTER_PREFIX = 1;
/**
* Filter type for {@link #getCapability(GoogleApiClient, String, int)}, {@link #getAllCapabilities(GoogleApiClient, int):
* If this filter is set then only reachable nodes that declare the given capability will be
* included in the capability's CapabilityInfo.
*/
int FILTER_REACHABLE = 1;
/**
* Registers a listener to be notified of a specific capability being added to or removed from
* the Wear network. Calls to this method should be balanced with {@link #removeCapabilityListener(GoogleApiClient, CapabilityListener, String)}
* to avoid leaking resources.
* <p/>
* Listener events will be called on the main thread, or the handler specified on {@code client}
* when it was built (using {@link Builder#setHandler(Handler)}).
* <p/>
* Callers wishing to be notified of events in the background should use {@link WearableListenerService}.
*/
PendingResult<Status> addCapabilityListener(GoogleApiClient client, CapabilityListener listener, String capability);
/**
* Registers a listener to be notified of capabilities being added to or removed from the Wear
* network. Calls to this method should be balanced with {@link #removeListener(GoogleApiClient, CapabilityListener)}
* to avoid leaking resources.
* <p/>
* {@code uri} and {@code filterType} can be used to filter the capability changes sent to the
* listener. For example, if {@code uri} and {@code filterType} create a prefix filter, then
* only capabilities matching that prefix will be notified. The {@code uri} follows the rules
* of the {@code <data>} element of {@code <intent-filter>}. The path is ignored if a URI host
* is not specified. To match capabilities by name or name prefix, the host must be {@code *}. i.e:
* <p/>
* <pre>wear://* /<capability_name></pre>
* Listener events will be called on the main thread, or the handler specified on {@code client}
* when it was built (using {@link Builder#setHandler(Handler)}).
*
* Callers wishing to be notified of events in the background should use WearableListenerService.
*/
PendingResult<Status> addListener(GoogleApiClient client, CapabilityListener listener, Uri uri, @CapabilityFilterType int filterType);
/**
* Announces that a capability has become available on the local node.
*/
PendingResult<AddLocalCapabilityResult> addLocalCapability(GoogleApiClient client, String capability);
/**
* Returns information about all capabilities, including the nodes that declare those
* capabilities. The filter parameter controls whether all nodes are returned, {@link #FILTER_ALL},
* or only those that are currently reachable by this node, {@link #FILTER_REACHABLE}.
* <p/>
* The local node will never be returned in the set of nodes.
*/
PendingResult<GetAllCapabilitiesResult> getAllCapabilities(GoogleApiClient client, @NodeFilterType int nodeFilter);
/**
* Returns information about a capabilities, including the nodes that declare this capability.
* The filter parameter controls whether all nodes are returned, {@link #FILTER_ALL}, or only
* those that are currently reachable by this node, {@link #FILTER_REACHABLE}.
* <p/>
* The local node will never be returned in the set of nodes.
*/
PendingResult<GetCapabilityResult> getCapability(GoogleApiClient client, String capability, @NodeFilterType int nodeFilter);
/**
* Removes a listener which was previously added through {@link #addCapabilityListener(GoogleApiClient, CapabilityListener, String)}.
* The listener is only removed from listening for the capability provided and will continue to
* receive messages for any other capabilities it was previously registered for that have not
* also been removed.
*/
PendingResult<Status> removeCapabilityListener(GoogleApiClient client, CapabilityListener listener, String capability);
/**
* Removes a listener which was previously added through {@link #addListener(GoogleApiClient, CapabilityListener, Uri, int)}.
* The listener is only removed from listening for the capability provided and will continue to
* receive messages for any other capabilities it was previously registered for that have not
* also been removed.
*/
PendingResult<Status> removeListener(GoogleApiClient client, CapabilityListener listener);
/**
* Announces that a capability is no longer available on the local node. Note: this will not
* remove any capabilities announced in the Manifest for an app.
*/
PendingResult<RemoveLocalCapabilityResult> removeLocalCapability(GoogleApiClient client, String capability);
/**
* Result returned from {@link #addLocalCapability(GoogleApiClient, String)}
*/
interface AddLocalCapabilityResult extends Result {
}
@Retention(RetentionPolicy.SOURCE)
@interface CapabilityFilterType {
}
/**
* Listener for changes in the reachable nodes providing a capability.
*/
interface CapabilityListener {
void onCapabilityChanged(CapabilityInfo capabilityInfo);
}
/**
* Result returned from {@link #getAllCapabilities(GoogleApiClient, int)}
*/
interface GetAllCapabilitiesResult extends Result {
Map<String, CapabilityInfo> getAllCapabilities();
}
/**
* Result returned from {@link #getCapability(GoogleApiClient, String, int)}
*/
interface GetCapabilityResult extends Result {
CapabilityInfo getCapability();
}
@Retention(RetentionPolicy.SOURCE)
@interface NodeFilterType {
}
/**
* Result returned from {@link #removeLocalCapability(GoogleApiClient, String)}
*/
interface RemoveLocalCapabilityResult extends Result {
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2013-2016 microG Project Team
*
* 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 com.google.android.gms.wearable;
import com.google.android.gms.wearable.ChannelApi.ChannelListener;
import java.io.IOException;
/**
* A subclass of {@link IOException} which can be thrown from the streams returned by
* {@link Channel#getInputStream(GoogleApiClient)} and {@link Channel#getOutputStream(GoogleApiClient)}.
*/
public class ChannelIOException extends IOException {
private int closeReason;
private int appSpecificErrorCode;
public ChannelIOException(String message, int closeReason, int appSpecificErrorCode) {
super(message);
this.closeReason = closeReason;
this.appSpecificErrorCode = appSpecificErrorCode;
}
/**
* Returns the app-specific error code passed to {@link Channel#close(GoogleApiClient, int)} if
* that's the reason for the stream closing, or {@code 0} otherwise.
*/
public int getAppSpecificErrorCode() {
return appSpecificErrorCode;
}
/**
* Returns one of {@link ChannelListener#CLOSE_REASON_NORMAL}, {@link ChannelListener#CLOSE_REASON_DISCONNECTED},
* {@link ChannelListener#CLOSE_REASON_REMOTE_CLOSE}, or {@link ChannelListener#CLOSE_REASON_LOCAL_CLOSE},
* to indicate the reason for the stream closing.
*/
public int getCloseReason() {
return closeReason;
}
}

View File

@ -19,12 +19,29 @@ package com.google.android.gms.wearable;
import android.os.Bundle;
import org.microg.gms.common.PublicApi;
import org.microg.gms.wearable.databundle.DataBundleUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.microg.gms.wearable.databundle.DataBundleUtil.ASSET_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.BOOLEAN_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.BYTE_ARRAY_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.BYTE_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.DATAMAP_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.DOUBLE_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.FLOAT_ARRAY_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.FLOAT_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.INTEGER_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.LIST_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.LONG_ARRAY_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.LONG_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.STRING_ARRAY_TYPE_CODE;
import static org.microg.gms.wearable.databundle.DataBundleUtil.STRING_TYPE_CODE;
/**
* A map of data supported by {@link PutDataMapRequest} and {@link DataMapItem}s. DataMap may
* convert to and from Bundles, but will drop any types not explicitly supported by DataMap in the
@ -74,6 +91,40 @@ public class DataMap {
return o instanceof DataMap && data.equals(((DataMap) o).data);
}
public StoredType getType(String key) {
return types.get(key);
}
@PublicApi(exclude = true)
public enum StoredType {
Asset(ASSET_TYPE_CODE), Boolean(BOOLEAN_TYPE_CODE), Byte(BYTE_TYPE_CODE),
ByteArray(BYTE_ARRAY_TYPE_CODE), DataMap(DATAMAP_TYPE_CODE), DataMapArrayList(DataMap),
Double(DOUBLE_TYPE_CODE), Float(FLOAT_TYPE_CODE), FloatArray(FLOAT_ARRAY_TYPE_CODE),
Integer(INTEGER_TYPE_CODE), IntegerArrayList(Integer), Long(LONG_TYPE_CODE),
LongArray(LONG_ARRAY_TYPE_CODE), String(STRING_TYPE_CODE),
StringArray(STRING_ARRAY_TYPE_CODE), StringArrayList(String);
private int typeCode;
private StoredType listType;
StoredType(int typeCode) {
this.typeCode = typeCode;
}
StoredType(StoredType listType) {
this.typeCode = LIST_TYPE_CODE;
this.listType = listType;
}
public int getTypeCode() {
return typeCode;
}
public StoredType getListType() {
return listType;
}
}
/**
* @return a DataMap from a Bundle. The input Bundle is expected to contain only elements
* supported by DataMap. Any elements in the Bundle not supported by DataMap will be dropped.
@ -82,7 +133,46 @@ public class DataMap {
DataMap res = new DataMap();
if (bundle != null) {
for (String key : bundle.keySet()) {
res.data.put(key, bundle.get(key));
Object val = bundle.get(key);
if (val instanceof Asset) {
res.putAsset(key, (Asset) val);
} else if (val instanceof Boolean) {
res.putBoolean(key, (Boolean) val);
} else if (val instanceof Byte) {
res.putByte(key, (Byte) val);
} else if (val instanceof byte[]) {
res.putByteArray(key, (byte[]) val);
} else if (val instanceof Bundle) {
res.putDataMap(key, DataMap.fromBundle((Bundle) val));
} else if (val instanceof Double) {
res.putDouble(key, (Double) val);
} else if (val instanceof Float) {
res.putFloat(key, (Float) val);
} else if (val instanceof float[]) {
res.putFloatArray(key, (float[]) val);
} else if (val instanceof Integer) {
res.putInt(key, (Integer) val);
} else if (val instanceof Long) {
res.putLong(key, (Long) val);
} else if (val instanceof long[]) {
res.putLongArray(key, (long[]) val);
} else if (val instanceof String) {
res.putString(key, (String) val);
} else if (val instanceof String[]) {
res.putStringArray(key, (String[]) val);
} else if (val instanceof ArrayList) {
if (((ArrayList) val).isEmpty() || ((ArrayList) val).get(0) instanceof String) {
res.putStringArrayList(key, (ArrayList<String>) val);
} else if (((ArrayList) val).get(0) instanceof Bundle) {
ArrayList<DataMap> dataMaps = new ArrayList<DataMap>();
for (Bundle b : ((ArrayList<Bundle>) val)) {
dataMaps.add(DataMap.fromBundle(b));
}
res.putDataMapArrayList(key, dataMaps);
} else if (((ArrayList) val).get(0) instanceof Integer) {
res.putIntegerArrayList(key, (ArrayList<Integer>) val);
}
}
}
}
return res;
@ -92,7 +182,7 @@ public class DataMap {
* @return a DataMap from a byte[].
*/
public static DataMap fromByteArray(byte[] bytes) {
return null; // TODO
return DataBundleUtil.readDataMap(bytes, Collections.<Asset>emptyList());
}
/**
@ -213,6 +303,11 @@ public class DataMap {
}
}
public void putAsset(String key, Asset value) {
data.put(key, value);
types.put(key, StoredType.Asset);
}
public void putBoolean(String key, boolean value) {
data.put(key, value);
types.put(key, StoredType.Boolean);
@ -302,7 +397,7 @@ public class DataMap {
for (String key : data.keySet()) {
switch (types.get(key)) {
case Asset:
bundle.putParcelable(key, (Asset)data.get(key));
bundle.putParcelable(key, (Asset) data.get(key));
break;
case Boolean:
bundle.putBoolean(key, (Boolean) data.get(key));
@ -317,7 +412,11 @@ public class DataMap {
bundle.putBundle(key, ((DataMap) data.get(key)).toBundle());
break;
case DataMapArrayList:
// TODO
ArrayList<Bundle> bundles = new ArrayList<Bundle>();
for (DataMap dataMap : ((ArrayList<DataMap>) data.get(key))) {
bundles.add(dataMap.toBundle());
}
bundle.putParcelableArrayList(key, bundles);
break;
case Double:
bundle.putDouble(key, (Double) data.get(key));
@ -355,15 +454,10 @@ public class DataMap {
}
public byte[] toByteArray() {
return null; // TODO
return DataBundleUtil.createBytes(this);
}
public String toString() {
return "DataMap{size=" + size() + "}";
}
private enum StoredType {
Asset, Boolean, Byte, ByteArray, DataMap, DataMapArrayList, Double, Float, FloatArray,
Integer, IntegerArrayList, Long, LongArray, String, StringArray, StringArrayList
}
}

View File

@ -1,88 +0,0 @@
/*
* Copyright 2013-2015 microG Project Team
*
* 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 com.google.android.gms.wearable;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Result;
import com.google.android.gms.common.api.Status;
import org.microg.gms.common.PublicApi;
@PublicApi
public interface MessageApi {
/**
* A value returned by {@link SendMessageResult#getRequestId()} when
* {@link #sendMessage(GoogleApiClient, String, String, byte[])} fails.
*/
int UNKNOWN_REQUEST_ID = -1;
/**
* Registers a listener to be notified of received messages. Calls to this method should
* balanced with {@link #removeListener(GoogleApiClient, MessageListener)} to avoid leaking
* resources.
* <p/>
* Callers wishing to be notified of events in the background should use {@link WearableListenerService}.
*/
PendingResult<Status> addListener(GoogleApiClient client, MessageListener listener);
/**
* Removes a message listener which was previously added through
* {@link #addListener(GoogleApiClient, MessageListener)}.
*/
PendingResult<Status> removeListener(GoogleApiClient client, MessageListener listener);
/**
* Sends {@code byte[]} data to the specified node.
*
* @param nodeId identifier for a particular node on the Android Wear network. Valid targets
* may be obtained through {@link NodeApi#getConnectedNodes(GoogleApiClient)}
* or from the host in {@link DataItem#getUri()}.
* @param path identifier used to specify a particular endpoint at the receiving node
* @param data small array of information to pass to the target node. Generally not larger
* than 100k
*/
PendingResult<SendMessageResult> sendMessage(GoogleApiClient client, String nodeId,
String path, byte[] data);
/**
* Used with {@link MessageApi#addListener(GoogleApiClient, MessageListener)} to receive
* message events.
* <p/>
* Callers wishing to be notified of events in the background should use
* {@link WearableListenerService}.
*/
interface MessageListener {
/**
* Notification that a message has been received.
*/
void onMessageReceived(MessageEvent messageEvent);
}
/**
* Contains the request id assigned to the message. On failure, the id will be
* {@link MessageApi#UNKNOWN_REQUEST_ID} and the status will be unsuccessful.
*/
interface SendMessageResult extends Result {
/**
* @return an ID used to identify the sent message. If {@link #getStatus()} is not
* successful, this value will be {@link MessageApi#UNKNOWN_REQUEST_ID}.
*/
int getRequestId();
}
}

View File

@ -19,6 +19,7 @@ package com.google.android.gms.wearable;
import com.google.android.gms.common.api.Api;
import com.google.android.gms.common.api.GoogleApiClient;
import org.microg.gms.common.PublicApi;
import org.microg.gms.wearable.DataApiImpl;
import org.microg.gms.wearable.MessageApiImpl;
import org.microg.gms.wearable.NodeApiImpl;
@ -27,6 +28,7 @@ import org.microg.gms.wearable.WearableApiBuilder;
/**
* An API for the Android Wear platform.
*/
@PublicApi
public class Wearable {
/**
* Token to pass to {@link GoogleApiClient.Builder#addApi(Api)} to enable the Wearable features.
@ -38,6 +40,12 @@ public class Wearable {
public static final NodeApi NodeApi = new NodeApiImpl();
public static class WearableOptions implements Api.ApiOptions.Optional {
/**
* Special option for microG to allow implementation of a FOSS first party Android Wear app
*/
@PublicApi(exclude = true)
public boolean firstPartyMode = false;
public static class Builder {
public WearableOptions build() {
return new WearableOptions();

View File

@ -18,41 +18,251 @@ package com.google.android.gms.wearable;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.common.data.DataHolder;
import com.google.android.gms.wearable.internal.AmsEntityUpdateParcelable;
import com.google.android.gms.wearable.internal.AncsNotificationParcelable;
import com.google.android.gms.wearable.internal.CapabilityInfoParcelable;
import com.google.android.gms.wearable.internal.ChannelEventParcelable;
import com.google.android.gms.wearable.internal.IWearableListener;
import com.google.android.gms.wearable.internal.MessageEventParcelable;
import com.google.android.gms.wearable.internal.NodeParcelable;
import org.microg.gms.common.PublicApi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
@PublicApi
public abstract class WearableListenerService extends Service implements CapabilityApi.CapabilityListener, ChannelApi.ChannelListener, DataApi.DataListener, MessageApi.MessageListener, NodeApi.NodeListener {
private static final String BIND_LISTENER_INTENT_ACTION = "com.google.android.gms.wearable.BIND_LISTENER";
private static final String TAG = "GmsWearListenerSvc";
private HandlerThread handlerThread;
private IWearableListener listener;
private ServiceHandler serviceHandler;
private Object lock = new Object();
private boolean disconnected = false;
public abstract class WearableListenerService extends Service implements DataApi.DataListener, MessageApi.MessageListener, NodeApi.NodeListener {
@Override
public IBinder onBind(Intent intent) {
if (BIND_LISTENER_INTENT_ACTION.equals(intent.getAction())) {
return listener.asBinder();
}
return null;
}
@Override
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
}
public void onConnectedNodes(List<Node> connectedNodes) {
}
@Override
public void onChannelClosed(Channel channel, int closeReason, int appSpecificErrorCode) {
}
@Override
public void onChannelOpened(Channel channel) {
}
@Override
public void onCreate() {
super.onCreate();
handlerThread = new HandlerThread("WearableListenerService");
handlerThread.start();
serviceHandler = new ServiceHandler(handlerThread.getLooper());
listener = new Listener();
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
}
@Override
public void onDestroy() {
synchronized (lock) {
if (serviceHandler == null) {
throw new IllegalStateException("serviceHandler not set, did you override onCreate() but forget to call super.onCreate()?");
}
serviceHandler.getLooper().quit();
}
super.onDestroy();
}
@PublicApi(exclude = true)
public void onEntityUpdate(AmsEntityUpdate entityUpdate) {
}
@Override
public void onInputClosed(Channel channel, @ChannelApi.CloseReason int closeReason, int appSpecificErrorCode) {
}
@Override
public void onMessageReceived(MessageEvent messageEvent) {
}
@PublicApi(exclude = true)
public void onNotificationReceived(AncsNotification notification) {
}
@Override
public void onOutputClosed(Channel channel, @ChannelApi.CloseReason int closeReason, int appSpecificErrorCode) {
}
@Override
public void onPeerConnected(Node peer) {
}
@Override
public void onPeerDisconnected(Node peer) {
}
private class Listener extends IWearableListener.Stub {
private int knownGoodUid = -1;
private boolean post(Runnable runnable) {
int callingUid = Binder.getCallingUid();
if (callingUid != knownGoodUid) {
// TODO: Verify Gms is calling
String[] packagesForUid = getPackageManager().getPackagesForUid(callingUid);
if (packagesForUid != null) {
if (Arrays.asList(packagesForUid).contains(GMS_PACKAGE_NAME)) {
knownGoodUid = callingUid;
} else {
throw new SecurityException("Caller is not Services Core");
}
}
}
synchronized (lock) {
if (disconnected) {
return false;
}
serviceHandler.post(runnable);
return true;
}
}
@Override
public void onDataChanged(final DataHolder data) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
WearableListenerService.this.onDataChanged(new DataEventBuffer(data));
}
});
}
@Override
public void onMessageReceived(final MessageEventParcelable messageEvent) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
WearableListenerService.this.onMessageReceived(messageEvent);
}
});
}
@Override
public void onPeerConnected(final NodeParcelable node) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
WearableListenerService.this.onPeerConnected(node);
}
});
}
@Override
public void onPeerDisconnected(final NodeParcelable node) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
WearableListenerService.this.onPeerDisconnected(node);
}
});
}
@Override
public void onConnectedNodes(final List<NodeParcelable> nodes) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
WearableListenerService.this.onConnectedNodes(new ArrayList<Node>(nodes));
}
});
}
@Override
public void onConnectedCapabilityChanged(final CapabilityInfoParcelable capabilityInfo) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
WearableListenerService.this.onCapabilityChanged(capabilityInfo);
}
});
}
@Override
public void onNotificationReceived(final AncsNotificationParcelable notification) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
WearableListenerService.this.onNotificationReceived(notification);
}
});
}
@Override
public void onEntityUpdate(final AmsEntityUpdateParcelable update) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
WearableListenerService.this.onEntityUpdate(update);
}
});
}
@Override
public void onChannelEvent(final ChannelEventParcelable channelEvent) throws RemoteException {
post(new Runnable() {
@Override
public void run() {
switch (channelEvent.eventType) {
case 1:
WearableListenerService.this.onChannelOpened(channelEvent.channel);
break;
case 2:
WearableListenerService.this.onChannelClosed(channelEvent.channel, channelEvent.closeReason, channelEvent.appSpecificErrorCode);
break;
case 3:
WearableListenerService.this.onInputClosed(channelEvent.channel, channelEvent.closeReason, channelEvent.appSpecificErrorCode);
break;
case 4:
WearableListenerService.this.onOutputClosed(channelEvent.channel, channelEvent.closeReason, channelEvent.appSpecificErrorCode);
break;
default:
Log.w(TAG, "Unknown ChannelEvent.eventType");
}
}
});
}
}
private class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright 2013-2016 microG Project Team
*
* 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 org.microg.gms.wearable;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.common.data.DataHolder;
import com.google.android.gms.wearable.internal.DeleteDataItemsResponse;
import com.google.android.gms.wearable.internal.GetCloudSyncSettingResponse;
import com.google.android.gms.wearable.internal.GetConfigResponse;
import com.google.android.gms.wearable.internal.GetConfigsResponse;
import com.google.android.gms.wearable.internal.GetConnectedNodesResponse;
import com.google.android.gms.wearable.internal.GetDataItemResponse;
import com.google.android.gms.wearable.internal.GetFdForAssetResponse;
import com.google.android.gms.wearable.internal.GetLocalNodeResponse;
import com.google.android.gms.wearable.internal.IWearableCallbacks;
import com.google.android.gms.wearable.internal.PutDataResponse;
import com.google.android.gms.wearable.internal.SendMessageResponse;
import com.google.android.gms.wearable.internal.StorageInfoResponse;
public class BaseWearableCallbacks extends IWearableCallbacks.Stub {
private static final String TAG = "GmsWearBaseCallback";
@Override
public void onGetConfigResponse(GetConfigResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onGetConfigResponse");
}
@Override
public void onPutDataResponse(PutDataResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onPutDataResponse");
}
@Override
public void onGetDataItemResponse(GetDataItemResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onGetDataItemResponse");
}
@Override
public void onDataItemChanged(DataHolder dataHolder) throws RemoteException {
Log.d(TAG, "unimplemented Method: onDataItemChanged");
}
@Override
public void onDeleteDataItemsResponse(DeleteDataItemsResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onDeleteDataItemsResponse");
}
@Override
public void onSendMessageResponse(SendMessageResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onSendMessageResponse");
}
@Override
public void onGetFdForAssetResponse(GetFdForAssetResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onGetFdForAssetResponse");
}
@Override
public void onGetLocalNodeResponse(GetLocalNodeResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onGetLocalNodeResponse");
}
@Override
public void onGetConnectedNodesResponse(GetConnectedNodesResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onGetConnectedNodesResponse");
}
@Override
public void onStatus(Status status) throws RemoteException {
Log.d(TAG, "unimplemented Method: onStatus");
}
@Override
public void onStorageInfoResponse(StorageInfoResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onStorageInfoResponse");
}
@Override
public void onGetConfigsResponse(GetConfigsResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onGetConfigsResponse");
}
@Override
public void onGetCloudSyncSettingResponse(GetCloudSyncSettingResponse response) throws RemoteException {
Log.d(TAG, "unimplemented Method: onGetCloudSyncSettingResponse");
}
}

View File

@ -30,46 +30,46 @@ import com.google.android.gms.wearable.internal.PutDataRequest;
public class DataApiImpl implements DataApi {
@Override
public PendingResult<Status> addListener(GoogleApiClient client, DataListener listener) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<DeleteDataItemsResult> deleteDataItems(GoogleApiClient client, Uri uri) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<DataItemResult> getDataItem(GoogleApiClient client, Uri uri) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<DataItemBuffer> getDataItems(GoogleApiClient client) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<DataItemBuffer> getDataItems(GoogleApiClient client, Uri uri) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<GetFdForAssetResult> getFdForAsset(GoogleApiClient client, DataItemAsset asset) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<GetFdForAssetResult> getFdForAsset(GoogleApiClient client, Asset asset) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<DataItemResult> putDataItem(GoogleApiClient client, PutDataRequest request) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<Status> removeListener(GoogleApiClient client, DataListener listener) {
return null;
throw new UnsupportedOperationException();
}
}

View File

@ -16,24 +16,42 @@
package org.microg.gms.wearable;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.internal.SendMessageResponse;
import org.microg.gms.common.GmsConnector;
import org.microg.gms.common.api.ApiConnection;
public class MessageApiImpl implements MessageApi {
@Override
public PendingResult<Status> addListener(GoogleApiClient client, MessageListener listener) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<Status> removeListener(GoogleApiClient client, MessageListener listener) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<SendMessageResult> sendMessage(GoogleApiClient client, String nodeId, String path, byte[] data) {
return null;
public PendingResult<SendMessageResult> sendMessage(GoogleApiClient client, final String nodeId, final String path, final byte[] data) {
return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback<WearableClientImpl, SendMessageResult>() {
@Override
public void onClientAvailable(WearableClientImpl client, final ResultProvider<SendMessageResult> resultProvider) throws RemoteException {
client.getServiceInterface().sendMessage(new BaseWearableCallbacks() {
@Override
public void onSendMessageResponse(SendMessageResponse response) throws RemoteException {
resultProvider.onResultAvailable(response);
}
}, nodeId, path, data);
}
});
}
}

View File

@ -24,21 +24,21 @@ import com.google.android.gms.wearable.NodeApi;
public class NodeApiImpl implements NodeApi {
@Override
public PendingResult<Status> addListener(GoogleApiClient client, NodeListener listener) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<GetConnectedNodesResult> getConnectedNodes(GoogleApiClient client) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<GetLocalNodeResult> getLocalNode(GoogleApiClient client) {
return null;
throw new UnsupportedOperationException();
}
@Override
public PendingResult<Status> removeListener(GoogleApiClient client, NodeListener listener) {
return null;
throw new UnsupportedOperationException();
}
}

View File

@ -18,7 +18,6 @@ package org.microg.gms.wearable;
import android.content.Context;
import android.os.Looper;
import android.util.Log;
import com.google.android.gms.common.api.AccountInfo;
import com.google.android.gms.common.api.GoogleApiClient;
@ -34,7 +33,6 @@ public class WearableApiBuilder implements ApiBuilder<Wearable.WearableOptions>
public ApiConnection build(Context context, Looper looper, Wearable.WearableOptions options,
AccountInfo accountInfo, GoogleApiClient.ConnectionCallbacks callbacks,
GoogleApiClient.OnConnectionFailedListener connectionFailedListener) {
Log.d(TAG, "Wearables not supported");
return null;
return new WearableClientImpl(context, options, callbacks, connectionFailedListener);
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2013-2016 microG Project Team
*
* 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 org.microg.gms.wearable;
import android.content.Context;
import android.os.IBinder;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.internal.IWearableService;
import org.microg.gms.common.GmsClient;
import org.microg.gms.common.GmsService;
import org.microg.gms.common.api.GoogleApiClientImpl;
public class WearableClientImpl extends GmsClient<IWearableService> {
private static final String TAG = "GmsWearClient";
public WearableClientImpl(Context context, Wearable.WearableOptions options, GoogleApiClient.ConnectionCallbacks callbacks, GoogleApiClient.OnConnectionFailedListener connectionFailedListener) {
super(context, callbacks, connectionFailedListener, GmsService.WEARABLE.ACTION);
serviceId = GmsService.WEARABLE.SERVICE_ID;
if (options != null && options.firstPartyMode)
extras.putBoolean("firstPartyMode", true);
Log.d(TAG, "<init>");
}
@Override
protected IWearableService interfaceFromBinder(IBinder binder) {
return IWearableService.Stub.asInterface(binder);
}
public static WearableClientImpl get(GoogleApiClient apiClient) {
if (apiClient instanceof GoogleApiClientImpl) {
return (WearableClientImpl) ((GoogleApiClientImpl) apiClient).getApiConnection(Wearable.API);
}
return null;
}
}

View File

@ -0,0 +1,658 @@
/*
* Copyright 2013-2016 microG Project Team
*
* 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 org.microg.gms.wearable.databundle;
import android.util.SparseArray;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.DataMap;
import com.squareup.wire.Wire;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import okio.ByteString;
public class DataBundleUtil {
public static DataMap readDataMap(byte[] bytes, List<Asset> assets) {
try {
return readDataMap(new Wire().parseFrom(bytes, DataBundle.class), assets);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static DataMap readDataMap(DataBundle dataBundle, List<Asset> assets) {
return readDataMap(dataBundle.entries, assets);
}
public static DataMap readDataMap(List<DataBundleEntry> entries, List<Asset> assets) {
DataMap dataMap = new DataMap();
for (DataBundleEntry entry : entries) {
readAndStore(dataMap, entry.key, entry.typedValue, assets);
}
return dataMap;
}
public static byte[] createBytes(DataMap dataMap) {
AssetAnnotatedDataBundle dataBundle = createDataBundle(dataMap);
if (!dataBundle.getAssets().isEmpty()) {
throw new UnsupportedOperationException();
}
return dataBundle.getData();
}
public static AssetAnnotatedDataBundle createDataBundle(DataMap dataMap) {
AssetAnnotatedDataBundle dataBundle = new AssetAnnotatedDataBundle();
dataBundle.assets = new ArrayList<Asset>();
dataBundle.dataBundle = new DataBundle(createEntryList(dataMap, dataBundle.assets));
return dataBundle;
}
private static List<DataBundleEntry> createEntryList(DataMap dataMap, List<Asset> assets) {
List<DataBundleEntry> entries = new ArrayList<DataBundleEntry>();
for (String key : dataMap.keySet()) {
entries.add(getTypeHelper(dataMap.getType(key)).loadAndCreateEntry(dataMap, key, assets));
}
return entries;
}
private static void readAndStore(DataMap dataMap, String key, DataBundleTypedValue value, List<Asset> assets) {
if (value.type == null) return;
getTypeHelper(value.type).readAndStore(dataMap, key, value, assets);
}
private static SparseArray<TypeHelper> typeHelperByCode;
private static void rememberTypeReader(TypeHelper typeHelper) {
typeHelperByCode.put(typeHelper.type, typeHelper);
}
static TypeHelper getTypeHelper(int type) {
if (typeHelperByCode.get(type) != null) {
return typeHelperByCode.get(type);
} else {
throw new IllegalArgumentException();
}
}
static TypeHelper getTypeHelper(DataMap.StoredType type) {
return getTypeHelper(type.getTypeCode());
}
static TypeHelper getListInnerTypeHelper(DataMap.StoredType type) {
return getTypeHelper(type.getListType());
}
public static final int BYTE_ARRAY_TYPE_CODE = 1;
static TypeHelper<byte[]> BYTEARRAY = new TypeHelper<byte[]>(BYTE_ARRAY_TYPE_CODE) {
@Override
byte[] read(DataBundleValue value, List<Asset> assets) {
return value.byteArray.toByteArray();
}
@Override
DataBundleValue create(byte[] value, List<Asset> assets) {
return new DataBundleValue.Builder().byteArray(ByteString.of(value)).build();
}
@Override
void store(DataMap dataMap, String key, byte[] value) {
dataMap.putByteArray(key, value);
}
@Override
byte[] load(DataMap dataMap, String key) {
return dataMap.getByteArray(key);
}
};
public static final int STRING_TYPE_CODE = 2;
static TypeHelper<String> STRING = new TypeHelper<String>(STRING_TYPE_CODE) {
@Override
String read(DataBundleValue value, List<Asset> assets) {
return value.stringVal;
}
@Override
DataBundleValue create(String value, List<Asset> assets) {
return new DataBundleValue.Builder().stringVal(value).build();
}
@Override
void store(DataMap dataMap, String key, String value) {
dataMap.putString(key, value);
}
@Override
void storeList(DataMap dataMap, String key, ArrayList<String> valueList) {
dataMap.putStringArrayList(key, valueList);
}
@Override
String load(DataMap dataMap, String key) {
return dataMap.getString(key);
}
@Override
AnnotatedArrayList<String> loadList(DataMap dataMap, String key) {
AnnotatedArrayList<String> list = new AnnotatedArrayList<String>(this);
list.addAll(dataMap.getStringArrayList(key));
return list;
}
};
public static final int DOUBLE_TYPE_CODE = 3;
static TypeHelper<Double> DOUBLE = new TypeHelper<Double>(DOUBLE_TYPE_CODE) {
@Override
Double read(DataBundleValue value, List<Asset> assets) {
return value.doubleVal;
}
@Override
DataBundleValue create(Double value, List<Asset> assets) {
return new DataBundleValue.Builder().doubleVal(value).build();
}
@Override
void store(DataMap dataMap, String key, Double value) {
dataMap.putDouble(key, value);
}
@Override
Double load(DataMap dataMap, String key) {
return dataMap.getDouble(key);
}
};
public static final int FLOAT_TYPE_CODE = 4;
static TypeHelper<Float> FLOAT = new TypeHelper<Float>(FLOAT_TYPE_CODE) {
@Override
Float read(DataBundleValue value, List<Asset> assets) {
return value.floatVal;
}
@Override
DataBundleValue create(Float value, List<Asset> assets) {
return new DataBundleValue.Builder().floatVal(value).build();
}
@Override
void store(DataMap dataMap, String key, Float value) {
dataMap.putFloat(key, value);
}
@Override
Float load(DataMap dataMap, String key) {
return dataMap.getFloat(key);
}
};
public static final int LONG_TYPE_CODE = 5;
static TypeHelper<Long> LONG = new TypeHelper<Long>(LONG_TYPE_CODE) {
@Override
Long read(DataBundleValue value, List<Asset> assets) {
return value.longVal;
}
@Override
DataBundleValue create(Long value, List<Asset> assets) {
return new DataBundleValue.Builder().longVal(value).build();
}
@Override
void store(DataMap dataMap, String key, Long value) {
dataMap.putLong(key, value);
}
@Override
Long load(DataMap dataMap, String key) {
return dataMap.getLong(key);
}
};
public static final int INTEGER_TYPE_CODE = 6;
static TypeHelper<Integer> INTEGER = new TypeHelper<Integer>(INTEGER_TYPE_CODE) {
@Override
Integer read(DataBundleValue value, List<Asset> assets) {
return value.intVal;
}
@Override
DataBundleValue create(Integer value, List<Asset> assets) {
return new DataBundleValue.Builder().intVal(value).build();
}
@Override
void store(DataMap dataMap, String key, Integer value) {
dataMap.putInt(key, value);
}
@Override
void storeList(DataMap dataMap, String key, ArrayList<Integer> valueList) {
dataMap.putIntegerArrayList(key, valueList);
}
@Override
Integer load(DataMap dataMap, String key) {
return dataMap.getInt(key);
}
@Override
AnnotatedArrayList<Integer> loadList(DataMap dataMap, String key) {
AnnotatedArrayList<Integer> list = new AnnotatedArrayList<Integer>(this);
list.addAll(dataMap.getIntegerArrayList(key));
return list;
}
};
public static final int BYTE_TYPE_CODE = 7;
static TypeHelper<Byte> BYTE = new TypeHelper<Byte>(BYTE_TYPE_CODE) {
@Override
Byte read(DataBundleValue value, List<Asset> assets) {
return (byte) (int) value.byteVal;
}
@Override
DataBundleValue create(Byte value, List<Asset> assets) {
return new DataBundleValue.Builder().byteVal((int) (byte) value).build();
}
@Override
void store(DataMap dataMap, String key, Byte value) {
dataMap.putByte(key, value);
}
@Override
Byte load(DataMap dataMap, String key) {
return dataMap.getByte(key);
}
};
public static final int BOOLEAN_TYPE_CODE = 8;
static TypeHelper<Boolean> BOOLEAN = new TypeHelper<Boolean>(BOOLEAN_TYPE_CODE) {
@Override
Boolean read(DataBundleValue value, List<Asset> assets) {
return value.booleanVal;
}
@Override
DataBundleValue create(Boolean value, List<Asset> assets) {
return new DataBundleValue.Builder().booleanVal(value).build();
}
@Override
void store(DataMap dataMap, String key, Boolean value) {
dataMap.putBoolean(key, value);
}
@Override
Boolean load(DataMap dataMap, String key) {
return dataMap.getBoolean(key);
}
};
public static final int DATAMAP_TYPE_CODE = 9;
static TypeHelper<DataMap> DATAMAP = new TypeHelper<DataMap>(DATAMAP_TYPE_CODE) {
@Override
DataMap read(DataBundleValue value, List<Asset> assets) {
return readDataMap(value.map, assets);
}
@Override
DataBundleValue create(DataMap value, List<Asset> assets) {
return new DataBundleValue.Builder().map(createEntryList(value, assets)).build();
}
@Override
void store(DataMap dataMap, String key, DataMap value) {
dataMap.putDataMap(key, value);
}
@Override
void storeList(DataMap dataMap, String key, ArrayList<DataMap> valueList) {
dataMap.putDataMapArrayList(key, valueList);
}
@Override
DataMap load(DataMap dataMap, String key) {
return dataMap.getDataMap(key);
}
@Override
AnnotatedArrayList<DataMap> loadList(DataMap dataMap, String key) {
AnnotatedArrayList<DataMap> list = new AnnotatedArrayList<DataMap>(this);
list.addAll(dataMap.getDataMapArrayList(key));
return list;
}
};
public static final int LIST_TYPE_CODE = 10;
static TypeHelper<AnnotatedArrayList<?>> ARRAYLIST = new TypeHelper<AnnotatedArrayList<?>>(LIST_TYPE_CODE) {
@Override
AnnotatedArrayList read(DataBundleValue value, List<Asset> assets) {
TypeHelper innerTypeHelper = NULL;
for (DataBundleTypedValue typedValue : value.list) {
if (innerTypeHelper == NULL) {
innerTypeHelper = getTypeHelper(typedValue.type);
} else if (typedValue.type != innerTypeHelper.type && typedValue.type != NULL.type) {
throw new IllegalArgumentException("List has elements of different types: " + innerTypeHelper.type + " and " + typedValue.type);
}
}
return innerTypeHelper.readList(value.list, assets);
}
@Override
DataBundleValue create(AnnotatedArrayList<?> value, List<Asset> assets) {
return new DataBundleValue.Builder().list(value.createList(assets)).build();
}
@Override
void store(DataMap dataMap, String key, AnnotatedArrayList value) {
value.store(dataMap, key);
}
@Override
AnnotatedArrayList load(DataMap dataMap, String key) {
return getListInnerTypeHelper(dataMap.getType(key)).loadList(dataMap, key);
}
};
public static final int STRING_ARRAY_TYPE_CODE = 11;
static TypeHelper<String[]> STRINGARRAY = new TypeHelper<String[]>(STRING_ARRAY_TYPE_CODE) {
@Override
String[] read(DataBundleValue value, List<Asset> assets) {
return value.stringArray.toArray(new String[value.stringArray.size()]);
}
@Override
DataBundleValue create(String[] value, List<Asset> assets) {
return new DataBundleValue.Builder().stringArray(Arrays.asList(value)).build();
}
@Override
void store(DataMap dataMap, String key, String[] value) {
dataMap.putStringArray(key, value);
}
@Override
String[] load(DataMap dataMap, String key) {
return dataMap.getStringArray(key);
}
};
public static final int LONG_ARRAY_TYPE_CODE = 12;
static TypeHelper<long[]> LONGARRAY = new TypeHelper<long[]>(LONG_ARRAY_TYPE_CODE) {
@Override
long[] read(DataBundleValue value, List<Asset> assets) {
long[] longArr = new long[value.longArray.size()];
for (int i = 0; i < value.longArray.size(); i++) {
longArr[i] = value.longArray.get(i);
}
return longArr;
}
@Override
DataBundleValue create(long[] value, List<Asset> assets) {
List<Long> longList = new ArrayList<Long>(value.length);
for (long l : value) {
longList.add(l);
}
return new DataBundleValue.Builder().longArray(longList).build();
}
@Override
void store(DataMap dataMap, String key, long[] value) {
dataMap.putLongArray(key, value);
}
@Override
long[] load(DataMap dataMap, String key) {
return dataMap.getLongArray(key);
}
};
public static final int ASSET_TYPE_CODE = 13;
static TypeHelper<Asset> ASSET = new TypeHelper<Asset>(ASSET_TYPE_CODE) {
@Override
Asset read(DataBundleValue value, List<Asset> assets) {
return assets.get(value.assetIndex);
}
@Override
DataBundleValue create(Asset value, List<Asset> assets) {
int index;
if (assets.contains(value)) {
index = assets.indexOf(value);
} else {
index = assets.size();
assets.add(value);
}
return new DataBundleValue.Builder().assetIndex(index).build();
}
@Override
void store(DataMap dataMap, String key, Asset value) {
dataMap.putAsset(key, value);
}
@Override
Asset load(DataMap dataMap, String key) {
return dataMap.getAsset(key);
}
};
public static final int NULL_TYPE_CODE = 14;
static TypeHelper<String> NULL = new TypeHelper<String>(NULL_TYPE_CODE) {
@Override
String read(DataBundleValue value, List<Asset> assets) {
return null;
}
@Override
DataBundleValue create(String value, List<Asset> assets) {
return new DataBundleValue.Builder().build();
}
@Override
void store(DataMap dataMap, String key, String value) {
dataMap.putString(key, value);
}
@Override
void storeList(DataMap dataMap, String key, ArrayList<String> valueList) {
dataMap.putStringArrayList(key, valueList);
}
@Override
String load(DataMap dataMap, String key) {
return null;
}
@Override
AnnotatedArrayList<String> loadList(DataMap dataMap, String key) {
AnnotatedArrayList<String> list = new AnnotatedArrayList<String>(this);
list.addAll(dataMap.getStringArrayList(key));
return list;
}
};
public static final int FLOAT_ARRAY_TYPE_CODE = 15;
static TypeHelper<float[]> FLOATARRAY = new TypeHelper<float[]>(FLOAT_ARRAY_TYPE_CODE) {
@Override
float[] read(DataBundleValue value, List<Asset> assets) {
float[] floatArr = new float[value.floatArray.size()];
for (int i = 0; i < value.floatArray.size(); i++) {
floatArr[i] = value.floatArray.get(i);
}
return floatArr;
}
@Override
DataBundleValue create(float[] value, List<Asset> assets) {
List<Float> floatList = new ArrayList<Float>(value.length);
for (float f : value) {
floatList.add(f);
}
return new DataBundleValue.Builder().floatArray(floatList).build();
}
@Override
void store(DataMap dataMap, String key, float[] value) {
dataMap.putFloatArray(key, value);
}
@Override
float[] load(DataMap dataMap, String key) {
return dataMap.getFloatArray(key);
}
};
static {
typeHelperByCode = new SparseArray<TypeHelper>();
rememberTypeReader(BYTEARRAY);
rememberTypeReader(STRING);
rememberTypeReader(DOUBLE);
rememberTypeReader(FLOAT);
rememberTypeReader(LONG);
rememberTypeReader(INTEGER);
rememberTypeReader(BYTE);
rememberTypeReader(BOOLEAN);
rememberTypeReader(DATAMAP);
rememberTypeReader(ARRAYLIST);
rememberTypeReader(STRINGARRAY);
rememberTypeReader(LONGARRAY);
rememberTypeReader(ASSET);
rememberTypeReader(NULL);
rememberTypeReader(FLOATARRAY);
}
static class AssetAnnotatedDataBundle {
private DataBundle dataBundle;
private List<Asset> assets;
public List<Asset> getAssets() {
return assets;
}
public byte[] getData() {
return dataBundle.toByteArray();
}
}
static class AnnotatedArrayList<T> extends ArrayList<T> {
private TypeHelper<T> innerType;
public AnnotatedArrayList(TypeHelper<T> innerType) {
this.innerType = innerType;
}
void store(DataMap dataMap, String key) {
innerType.storeList(dataMap, key, this);
}
public List<DataBundleTypedValue> createList(List<Asset> assets) {
return innerType.createList(this, assets);
}
}
static abstract class TypeHelper<T> {
private int type;
public TypeHelper(int type) {
this.type = type;
}
abstract T read(DataBundleValue value, List<Asset> assets);
abstract DataBundleValue create(T value, List<Asset> assets);
T read(DataBundleTypedValue value, List<Asset> assets) {
if (value.type == NULL_TYPE_CODE) {
return null;
} else if (value.type == type) {
return read(value.value, assets);
} else {
throw new IllegalArgumentException();
}
}
abstract void store(DataMap dataMap, String key, T value);
void storeList(DataMap dataMap, String key, ArrayList<T> valueList) {
throw new UnsupportedOperationException();
}
abstract T load(DataMap dataMap, String key);
AnnotatedArrayList<T> loadList(DataMap dataMap, String key) {
throw new UnsupportedOperationException();
}
void readAndStore(DataMap dataMap, String key, DataBundleValue value, List<Asset> assets) {
store(dataMap, key, read(value, assets));
}
void readAndStore(DataMap dataMap, String key, DataBundleTypedValue value, List<Asset> assets) {
store(dataMap, key, read(value, assets));
}
void readAndStore(DataMap dataMap, DataBundleEntry entry, List<Asset> assets) {
readAndStore(dataMap, entry.key, entry.typedValue, assets);
}
AnnotatedArrayList<T> readList(List<DataBundleTypedValue> values, List<Asset> assets) {
AnnotatedArrayList<T> list = new AnnotatedArrayList<T>(this);
for (DataBundleTypedValue value : values) {
list.add(read(value, assets));
}
return list;
}
List<DataBundleTypedValue> createList(AnnotatedArrayList<T> value, List<Asset> assets) {
List<DataBundleTypedValue> list = new ArrayList<DataBundleTypedValue>();
for (T val : value) {
list.add(createTyped(val, assets));
}
return list;
}
void readAndStore(DataMap dataMap, String key, List<DataBundleTypedValue> values, List<Asset> assets) {
storeList(dataMap, key, readList(values, assets));
}
DataBundleTypedValue createTyped(T value, List<Asset> assets) {
return new DataBundleTypedValue(type, create(value, assets));
}
DataBundleValue loadAndCreate(DataMap dataMap, String key, List<Asset> assets) {
return create(load(dataMap, key), assets);
}
DataBundleTypedValue loadAndCreateTyped(DataMap dataMap, String key, List<Asset> assets) {
return createTyped(load(dataMap, key), assets);
}
DataBundleEntry loadAndCreateEntry(DataMap dataMap, String key, List<Asset> assets) {
return new DataBundleEntry(key, loadAndCreateTyped(dataMap, key, assets));
}
}
}

View File

@ -0,0 +1,64 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: protos-repo/databundle.proto
package org.microg.gms.wearable.databundle;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoField;
import java.util.Collections;
import java.util.List;
import static com.squareup.wire.Message.Label.REPEATED;
public final class DataBundle extends Message {
public static final List<DataBundleEntry> DEFAULT_ENTRIES = Collections.emptyList();
@ProtoField(tag = 1, label = REPEATED, messageType = DataBundleEntry.class)
public final List<DataBundleEntry> entries;
public DataBundle(List<DataBundleEntry> entries) {
this.entries = immutableCopyOf(entries);
}
private DataBundle(Builder builder) {
this(builder.entries);
setBuilder(builder);
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof DataBundle)) return false;
return equals(entries, ((DataBundle) other).entries);
}
@Override
public int hashCode() {
int result = hashCode;
return result != 0 ? result : (hashCode = entries != null ? entries.hashCode() : 1);
}
public static final class Builder extends Message.Builder<DataBundle> {
public List<DataBundleEntry> entries;
public Builder() {
}
public Builder(DataBundle message) {
super(message);
if (message == null) return;
this.entries = copyOf(message.entries);
}
public Builder entries(List<DataBundleEntry> entries) {
this.entries = checkForNulls(entries);
return this;
}
@Override
public DataBundle build() {
return new DataBundle(this);
}
}
}

View File

@ -0,0 +1,80 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: protos-repo/databundle.proto
package org.microg.gms.wearable.databundle;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoField;
import static com.squareup.wire.Message.Datatype.STRING;
public final class DataBundleEntry extends Message {
public static final String DEFAULT_KEY = "";
@ProtoField(tag = 1, type = STRING)
public final String key;
@ProtoField(tag = 2)
public final DataBundleTypedValue typedValue;
public DataBundleEntry(String key, DataBundleTypedValue typedValue) {
this.key = key;
this.typedValue = typedValue;
}
private DataBundleEntry(Builder builder) {
this(builder.key, builder.typedValue);
setBuilder(builder);
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof DataBundleEntry)) return false;
DataBundleEntry o = (DataBundleEntry) other;
return equals(key, o.key)
&& equals(typedValue, o.typedValue);
}
@Override
public int hashCode() {
int result = hashCode;
if (result == 0) {
result = key != null ? key.hashCode() : 0;
result = result * 37 + (typedValue != null ? typedValue.hashCode() : 0);
hashCode = result;
}
return result;
}
public static final class Builder extends Message.Builder<DataBundleEntry> {
public String key;
public DataBundleTypedValue typedValue;
public Builder() {
}
public Builder(DataBundleEntry message) {
super(message);
if (message == null) return;
this.key = message.key;
this.typedValue = message.typedValue;
}
public Builder key(String key) {
this.key = key;
return this;
}
public Builder typedValue(DataBundleTypedValue typedValue) {
this.typedValue = typedValue;
return this;
}
@Override
public DataBundleEntry build() {
return new DataBundleEntry(this);
}
}
}

View File

@ -0,0 +1,80 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: protos-repo/databundle.proto
package org.microg.gms.wearable.databundle;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoField;
import static com.squareup.wire.Message.Datatype.INT32;
public final class DataBundleTypedValue extends Message {
public static final Integer DEFAULT_TYPE = 0;
@ProtoField(tag = 1, type = INT32)
public final Integer type;
@ProtoField(tag = 2)
public final DataBundleValue value;
public DataBundleTypedValue(Integer type, DataBundleValue value) {
this.type = type;
this.value = value;
}
private DataBundleTypedValue(Builder builder) {
this(builder.type, builder.value);
setBuilder(builder);
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof DataBundleTypedValue)) return false;
DataBundleTypedValue o = (DataBundleTypedValue) other;
return equals(type, o.type)
&& equals(value, o.value);
}
@Override
public int hashCode() {
int result = hashCode;
if (result == 0) {
result = type != null ? type.hashCode() : 0;
result = result * 37 + (value != null ? value.hashCode() : 0);
hashCode = result;
}
return result;
}
public static final class Builder extends Message.Builder<DataBundleTypedValue> {
public Integer type;
public DataBundleValue value;
public Builder() {
}
public Builder(DataBundleTypedValue message) {
super(message);
if (message == null) return;
this.type = message.type;
this.value = message.value;
}
public Builder type(Integer type) {
this.type = type;
return this;
}
public Builder value(DataBundleValue value) {
this.value = value;
return this;
}
@Override
public DataBundleTypedValue build() {
return new DataBundleTypedValue(this);
}
}
}

View File

@ -0,0 +1,259 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: protos-repo/databundle.proto
package org.microg.gms.wearable.databundle;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoField;
import java.util.Collections;
import java.util.List;
import okio.ByteString;
import static com.squareup.wire.Message.Datatype.BOOL;
import static com.squareup.wire.Message.Datatype.BYTES;
import static com.squareup.wire.Message.Datatype.DOUBLE;
import static com.squareup.wire.Message.Datatype.FLOAT;
import static com.squareup.wire.Message.Datatype.INT32;
import static com.squareup.wire.Message.Datatype.INT64;
import static com.squareup.wire.Message.Datatype.STRING;
import static com.squareup.wire.Message.Label.REPEATED;
public final class DataBundleValue extends Message {
public static final ByteString DEFAULT_BYTEARRAY = ByteString.EMPTY;
public static final String DEFAULT_STRINGVAL = "";
public static final Double DEFAULT_DOUBLEVAL = 0D;
public static final Float DEFAULT_FLOATVAL = 0F;
public static final Long DEFAULT_LONGVAL = 0L;
public static final Integer DEFAULT_INTVAL = 0;
public static final Integer DEFAULT_BYTEVAL = 0;
public static final Boolean DEFAULT_BOOLEANVAL = false;
public static final List<DataBundleEntry> DEFAULT_MAP = Collections.emptyList();
public static final List<DataBundleTypedValue> DEFAULT_LIST = Collections.emptyList();
public static final List<String> DEFAULT_STRINGARRAY = Collections.emptyList();
public static final List<Long> DEFAULT_LONGARRAY = Collections.emptyList();
public static final Integer DEFAULT_ASSETINDEX = 0;
public static final List<Float> DEFAULT_FLOATARRAY = Collections.emptyList();
@ProtoField(tag = 1, type = BYTES)
public final ByteString byteArray;
@ProtoField(tag = 2, type = STRING)
public final String stringVal;
@ProtoField(tag = 3, type = DOUBLE)
public final Double doubleVal;
@ProtoField(tag = 4, type = FLOAT)
public final Float floatVal;
@ProtoField(tag = 5, type = INT64)
public final Long longVal;
@ProtoField(tag = 6, type = INT32)
public final Integer intVal;
@ProtoField(tag = 7, type = INT32)
public final Integer byteVal;
@ProtoField(tag = 8, type = BOOL)
public final Boolean booleanVal;
@ProtoField(tag = 9, label = REPEATED, messageType = DataBundleEntry.class)
public final List<DataBundleEntry> map;
@ProtoField(tag = 10, label = REPEATED, messageType = DataBundleTypedValue.class)
public final List<DataBundleTypedValue> list;
@ProtoField(tag = 11, type = STRING, label = REPEATED)
public final List<String> stringArray;
@ProtoField(tag = 12, type = INT64, label = REPEATED)
public final List<Long> longArray;
@ProtoField(tag = 13, type = INT32)
public final Integer assetIndex;
@ProtoField(tag = 14, type = FLOAT, label = REPEATED)
public final List<Float> floatArray;
public DataBundleValue(ByteString byteArray, String stringVal, Double doubleVal, Float floatVal, Long longVal, Integer intVal, Integer byteVal, Boolean booleanVal, List<DataBundleEntry> map, List<DataBundleTypedValue> list, List<String> stringArray, List<Long> longArray, Integer assetIndex, List<Float> floatArray) {
this.byteArray = byteArray;
this.stringVal = stringVal;
this.doubleVal = doubleVal;
this.floatVal = floatVal;
this.longVal = longVal;
this.intVal = intVal;
this.byteVal = byteVal;
this.booleanVal = booleanVal;
this.map = immutableCopyOf(map);
this.list = immutableCopyOf(list);
this.stringArray = immutableCopyOf(stringArray);
this.longArray = immutableCopyOf(longArray);
this.assetIndex = assetIndex;
this.floatArray = immutableCopyOf(floatArray);
}
private DataBundleValue(Builder builder) {
this(builder.byteArray, builder.stringVal, builder.doubleVal, builder.floatVal, builder.longVal, builder.intVal, builder.byteVal, builder.booleanVal, builder.map, builder.list, builder.stringArray, builder.longArray, builder.assetIndex, builder.floatArray);
setBuilder(builder);
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof DataBundleValue)) return false;
DataBundleValue o = (DataBundleValue) other;
return equals(byteArray, o.byteArray)
&& equals(stringVal, o.stringVal)
&& equals(doubleVal, o.doubleVal)
&& equals(floatVal, o.floatVal)
&& equals(longVal, o.longVal)
&& equals(intVal, o.intVal)
&& equals(byteVal, o.byteVal)
&& equals(booleanVal, o.booleanVal)
&& equals(map, o.map)
&& equals(list, o.list)
&& equals(stringArray, o.stringArray)
&& equals(longArray, o.longArray)
&& equals(assetIndex, o.assetIndex)
&& equals(floatArray, o.floatArray);
}
@Override
public int hashCode() {
int result = hashCode;
if (result == 0) {
result = byteArray != null ? byteArray.hashCode() : 0;
result = result * 37 + (stringVal != null ? stringVal.hashCode() : 0);
result = result * 37 + (doubleVal != null ? doubleVal.hashCode() : 0);
result = result * 37 + (floatVal != null ? floatVal.hashCode() : 0);
result = result * 37 + (longVal != null ? longVal.hashCode() : 0);
result = result * 37 + (intVal != null ? intVal.hashCode() : 0);
result = result * 37 + (byteVal != null ? byteVal.hashCode() : 0);
result = result * 37 + (booleanVal != null ? booleanVal.hashCode() : 0);
result = result * 37 + (map != null ? map.hashCode() : 1);
result = result * 37 + (list != null ? list.hashCode() : 1);
result = result * 37 + (stringArray != null ? stringArray.hashCode() : 1);
result = result * 37 + (longArray != null ? longArray.hashCode() : 1);
result = result * 37 + (assetIndex != null ? assetIndex.hashCode() : 0);
result = result * 37 + (floatArray != null ? floatArray.hashCode() : 1);
hashCode = result;
}
return result;
}
public static final class Builder extends Message.Builder<DataBundleValue> {
public ByteString byteArray;
public String stringVal;
public Double doubleVal;
public Float floatVal;
public Long longVal;
public Integer intVal;
public Integer byteVal;
public Boolean booleanVal;
public List<DataBundleEntry> map;
public List<DataBundleTypedValue> list;
public List<String> stringArray;
public List<Long> longArray;
public Integer assetIndex;
public List<Float> floatArray;
public Builder() {
}
public Builder(DataBundleValue message) {
super(message);
if (message == null) return;
this.byteArray = message.byteArray;
this.stringVal = message.stringVal;
this.doubleVal = message.doubleVal;
this.floatVal = message.floatVal;
this.longVal = message.longVal;
this.intVal = message.intVal;
this.byteVal = message.byteVal;
this.booleanVal = message.booleanVal;
this.map = copyOf(message.map);
this.list = copyOf(message.list);
this.stringArray = copyOf(message.stringArray);
this.longArray = copyOf(message.longArray);
this.assetIndex = message.assetIndex;
this.floatArray = copyOf(message.floatArray);
}
public Builder byteArray(ByteString byteArray) {
this.byteArray = byteArray;
return this;
}
public Builder stringVal(String stringVal) {
this.stringVal = stringVal;
return this;
}
public Builder doubleVal(Double doubleVal) {
this.doubleVal = doubleVal;
return this;
}
public Builder floatVal(Float floatVal) {
this.floatVal = floatVal;
return this;
}
public Builder longVal(Long longVal) {
this.longVal = longVal;
return this;
}
public Builder intVal(Integer intVal) {
this.intVal = intVal;
return this;
}
public Builder byteVal(Integer byteVal) {
this.byteVal = byteVal;
return this;
}
public Builder booleanVal(Boolean booleanVal) {
this.booleanVal = booleanVal;
return this;
}
public Builder map(List<DataBundleEntry> map) {
this.map = checkForNulls(map);
return this;
}
public Builder list(List<DataBundleTypedValue> list) {
this.list = checkForNulls(list);
return this;
}
public Builder stringArray(List<String> stringArray) {
this.stringArray = checkForNulls(stringArray);
return this;
}
public Builder longArray(List<Long> longArray) {
this.longArray = checkForNulls(longArray);
return this;
}
public Builder assetIndex(Integer assetIndex) {
this.assetIndex = assetIndex;
return this;
}
public Builder floatArray(List<Float> floatArray) {
this.floatArray = checkForNulls(floatArray);
return this;
}
@Override
public DataBundleValue build() {
return new DataBundleValue(this);
}
}
}

View File

@ -0,0 +1,33 @@
option java_package = "org.microg.gms.wearable.databundle";
option java_outer_classname = "DataBundleProto";
message DataBundle {
repeated DataBundleEntry entries = 1;
}
message DataBundleEntry {
optional string key = 1;
optional DataBundleTypedValue typedValue = 2;
}
message DataBundleTypedValue {
optional int32 type = 1;
optional DataBundleValue value = 2;
}
message DataBundleValue {
optional bytes byteArray = 1;
optional string stringVal = 2;
optional double doubleVal = 3;
optional float floatVal = 4;
optional int64 longVal = 5;
optional int32 intVal = 6;
optional int32 byteVal = 7;
optional bool booleanVal = 8;
repeated DataBundleEntry map = 9;
repeated DataBundleTypedValue list = 10;
repeated string stringArray = 11;
repeated int64 longArray = 12;
optional int32 assetIndex = 13;
repeated float floatArray = 14;
}

View File

@ -16,8 +16,8 @@
include ':safe-parcel'
include ':play-services-basement'
include ':play-services-cast-api'
include ':play-services-common-api'
include ':play-services-iid-api'
include ':play-services-location-api'
include ':play-services-wearable-api'