public final class

ComplicationHelperActivity

extends Activity

implements ActivityCompat.OnRequestPermissionsResultCallback

 java.lang.Object

↳Activity

↳androidx.wear.watchface.ComplicationHelperActivity

Gradle dependencies

compile group: 'androidx.wear.watchface', name: 'watchface', version: '1.1.0-rc01'

  • groupId: androidx.wear.watchface
  • artifactId: watchface
  • version: 1.1.0-rc01

Artifact androidx.wear.watchface:watchface:1.1.0-rc01 it located at Google repository (https://maven.google.com/)

Overview

Activity to handle permission requests for complications.

This can be used to start the complication data source chooser, making a permission request if necessary, or to just make a permission request, and update all active complications if the permission is granted.

To use, add this activity to your app, and also add the com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA permission.

Then, to start the complication data source chooser chooser, use ComplicationHelperActivity.createComplicationDataSourceChooserHelperIntent(Context, ComponentName, int, Collection, String) to obtain an intent. If the permission has not yet been granted, the permission will be requested and the complication data source chooser chooser will only be started if the request is accepted by the user.

Or, to request the permission, for instance if android.support.wearable.complications.ComplicationData of TYPE_NO_PERMISSION has been received and tapped on, use ComplicationHelperActivity.createPermissionRequestHelperIntent(Context, ComponentName).

Summary

Fields
public static final java.lang.StringACTION_PERMISSION_REQUEST_ONLY

public static final java.lang.StringACTION_REQUEST_UPDATE_ALL_ACTIVE

public static final java.lang.StringACTION_START_PROVIDER_CHOOSER

public static final java.lang.StringEXTRA_WATCH_FACE_COMPONENT

public static booleanskipPermissionCheck

Whether to skip th permission check and directly attempt to invoke the complication data source chooser.

public static booleanuseTestComplicationDataSourceChooserActivity

Whether to invoke a specified activity instead of the system's complication data source chooser.

Constructors
publicComplicationHelperActivity()

Methods
public static IntentcreateComplicationDataSourceChooserHelperIntent(Context context, ComponentName watchFace, int watchFaceComplicationId, java.util.Collection<ComplicationType> supportedTypes, java.lang.String watchFaceInstanceId)

Returns an intent that may be used to start the complication data source chooser activity via the ComplicationHelperActivity.

public static IntentcreatePermissionRequestHelperIntent(Context context, ComponentName watchFace)

Returns an intent that may be used to start this activity in order to request the permission required to receive complication data.

protected voidonActivityResult(int requestCode, int resultCode, Intent data)

protected voidonCreate(Bundle savedInstanceState)

public voidonRequestPermissionsResult(int requestCode, java.lang.String permissions[], int[] grantResults[])

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

Fields

public static boolean useTestComplicationDataSourceChooserActivity

Whether to invoke a specified activity instead of the system's complication data source chooser. To be used in tests.

public static boolean skipPermissionCheck

Whether to skip th permission check and directly attempt to invoke the complication data source chooser. To be used in tests.

public static final java.lang.String ACTION_REQUEST_UPDATE_ALL_ACTIVE

public static final java.lang.String EXTRA_WATCH_FACE_COMPONENT

public static final java.lang.String ACTION_START_PROVIDER_CHOOSER

public static final java.lang.String ACTION_PERMISSION_REQUEST_ONLY

Constructors

public ComplicationHelperActivity()

Methods

protected void onCreate(Bundle savedInstanceState)

public void onRequestPermissionsResult(int requestCode, java.lang.String permissions[], int[] grantResults[])

protected void onActivityResult(int requestCode, int resultCode, Intent data)

public static Intent createComplicationDataSourceChooserHelperIntent(Context context, ComponentName watchFace, int watchFaceComplicationId, java.util.Collection<ComplicationType> supportedTypes, java.lang.String watchFaceInstanceId)

Returns an intent that may be used to start the complication data source chooser activity via the ComplicationHelperActivity. This allows the required permission to be checked before the complication data source chooser is displayed.

To use this, the ComplicationHelperActivity must be added to your app, and your app must include the com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA permission in its manifest.

The complication data source chooser activity will show a list of all complication data sources that can supply data of at least one of the supportedTypes.

When the user chooses a complication data source, the configuration will be set up in the complications system - the watch face does not need to do anything else.

The activity may be started using . The result delivered back to your activity will have a result code of if a complication data source was successfully set, or a result code of if no complication data source was set. In the case where a complication data source was set, android.support.wearable.complications.ComplicationProviderInfo for the chosen complication data source will be included in the data intent of the result, as an extra with the key android.support.wearable.complications.EXTRA_PROVIDER_INFO.

The package of the calling app must match the package of the watch face, or this will not work.

From android R onwards this API can only be called during an editing session.

Parameters:

context: context for the current app, that must contain a ComplicationHelperActivity
watchFace: the ComponentName of the WatchFaceService being configured.
watchFaceComplicationId: the watch face's id for the complication being configured. This must match the id passed in when the watch face calls WatchFaceService.Engine#setActiveComplications.
supportedTypes: the types supported by the complication, in decreasing order of preference. If a complication data source can supply data for more than one of these types, the type chosen will be whichever was specified first.
watchFaceInstanceId: The ID of the watchface being edited.

public static Intent createPermissionRequestHelperIntent(Context context, ComponentName watchFace)

Returns an intent that may be used to start this activity in order to request the permission required to receive complication data.

To use this, the ComplicationHelperActivity must be added to your app, and your app must include the com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA permission in its manifest.

If the current app has already been granted this permission, the activity will finish immediately.

If the current app has not been granted this permission, a permission request will be made. If the permission is granted by the user, an update of all complications on the current watch face will be triggered. The provided watchFace must match the current watch face for this to occur.

Parameters:

context: context for the current app, that must contain a ComplicationHelperActivity
watchFace: the ComponentName of the WatchFaceService for the current watch face

Source

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

package androidx.wear.watchface;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.wearable.complications.ComplicationData;
import android.support.wearable.complications.ComplicationProviderInfo;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.app.ActivityCompat;
import androidx.wear.complications.ComplicationDataSourceUpdateRequesterConstants;
import androidx.wear.complications.data.ComplicationType;

import java.util.Collection;
import java.util.Objects;

/**
 * Activity to handle permission requests for complications.
 *
 * <p>This can be used to start the complication data source chooser, making a permission request
 * if necessary, or to just make a permission request, and update all active complications if the
 * permission is granted.
 *
 * <p>To use, add this activity to your app, and also add the {@code
 * com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA} permission.
 *
 * <p>Then, to start the complication data source chooser chooser, use
 * {@link #createComplicationDataSourceChooserHelperIntent} to obtain an intent. If the
 * permission has not yet been granted, the permission will be requested and the complication
 * data source chooser chooser will only be started if the request is accepted by the user.
 *
 * <p>Or, to request the permission, for instance if {@link ComplicationData} of {@link
 * ComplicationData#TYPE_NO_PERMISSION TYPE_NO_PERMISSION} has been received and tapped on, use
 * {@link #createPermissionRequestHelperIntent}.
 *
 * @hide
 */
@RequiresApi(Build.VERSION_CODES.N)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@SuppressWarnings("ForbiddenSuperClass")
public final class ComplicationHelperActivity extends Activity
        implements ActivityCompat.OnRequestPermissionsResultCallback {

    /**
     * Whether to invoke a specified activity instead of the system's complication data source
     * chooser.
     *
     * To be used in tests.
     */
    public static boolean useTestComplicationDataSourceChooserActivity = false;

    /**
     * Whether to skip th permission check and directly attempt to invoke the complication data
     * source chooser.
     *
     * To be used in tests.
     */
    public static boolean skipPermissionCheck = false;

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final String ACTION_REQUEST_UPDATE_ALL_ACTIVE =
            "android.support.wearable.complications.ACTION_REQUEST_UPDATE_ALL_ACTIVE";

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final String EXTRA_WATCH_FACE_COMPONENT =
            "android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT";

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final String ACTION_START_PROVIDER_CHOOSER =
            "android.support.wearable.complications.ACTION_START_PROVIDER_CHOOSER";

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final String ACTION_PERMISSION_REQUEST_ONLY =
            "android.support.wearable.complications.ACTION_PERMISSION_REQUEST_ONLY";

    /** The package of the service that accepts complication data source requests. */
    private static final String UPDATE_REQUEST_RECEIVER_PACKAGE = "com.google.android.wearable.app";

    private static final int START_REQUEST_CODE_PROVIDER_CHOOSER = 1;
    private static final int PERMISSION_REQUEST_CODE_PROVIDER_CHOOSER = 1;
    private static final int PERMISSION_REQUEST_CODE_REQUEST_ONLY = 2;

    private static final String COMPLICATIONS_PERMISSION =
            "com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA";

    private static final String COMPLICATIONS_PERMISSION_PRIVILEGED =
            "com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA_PRIVILEGED";

    @Nullable
    private ComponentName mWatchFace;
    private int mWfComplicationId;
    @Nullable
    private Bundle mAdditionalExtras;
    @Nullable
    @ComplicationData.ComplicationType
    private int[] mTypes;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();

        switch (Objects.requireNonNull(intent.getAction())) {
            case ACTION_START_PROVIDER_CHOOSER:
                mWatchFace = intent.getParcelableExtra(
                        ComplicationDataSourceChooserIntent.EXTRA_WATCH_FACE_COMPONENT_NAME);
                mWfComplicationId =
                        intent.getIntExtra(
                                ComplicationDataSourceChooserIntent.EXTRA_COMPLICATION_ID, 0);
                mTypes = intent.getIntArrayExtra(
                        ComplicationDataSourceChooserIntent.EXTRA_SUPPORTED_TYPES);
                mAdditionalExtras = getAdditionalExtras(intent);
                if (checkPermission()) {
                    startComplicationDataSourceChooser();
                } else {
                    ActivityCompat.requestPermissions(
                            this,
                            new String[]{COMPLICATIONS_PERMISSION},
                            PERMISSION_REQUEST_CODE_PROVIDER_CHOOSER);
                }
                break;
            case ACTION_PERMISSION_REQUEST_ONLY:
                mWatchFace = intent.getParcelableExtra(
                    ComplicationDataSourceChooserIntent.EXTRA_WATCH_FACE_COMPONENT_NAME);
                if (checkPermission()) {
                    finish();
                } else {
                    ActivityCompat.requestPermissions(
                            this,
                            new String[]{COMPLICATIONS_PERMISSION},
                            PERMISSION_REQUEST_CODE_REQUEST_ONLY);
                }
                break;
            default:
                throw new IllegalStateException("Unrecognised intent action.");
        }
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (grantResults.length == 0) {
            // Request was cancelled.
            finish();
            return;
        }
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            if (requestCode == PERMISSION_REQUEST_CODE_PROVIDER_CHOOSER) {
                startComplicationDataSourceChooser();
            } else {
                finish();
            }
            requestUpdateAll(mWatchFace);
        } else {
            finish();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (requestCode == START_REQUEST_CODE_PROVIDER_CHOOSER) {
            setResult(resultCode, data);
            finish();
        }
    }

    private boolean checkPermission() {
        return ActivityCompat.checkSelfPermission(this, COMPLICATIONS_PERMISSION_PRIVILEGED)
                == PackageManager.PERMISSION_GRANTED
                || ActivityCompat.checkSelfPermission(this, COMPLICATIONS_PERMISSION)
                == PackageManager.PERMISSION_GRANTED
                || skipPermissionCheck;
    }

    /**
     * Returns an intent that may be used to start the complication data source chooser activity via
     * the ComplicationHelperActivity. This allows the required permission to be checked before the
     * complication data source chooser is displayed.
     *
     * <p>To use this, the ComplicationHelperActivity must be added to your app, and your app must
     * include the {@code com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA}
     * permission in its manifest.
     *
     * <p>The complication data source chooser activity will show a list of all complication data
     * sources that can supply data of at least one of the {@code supportedTypes}.
     *
     * <p>When the user chooses a complication data source, the configuration will be set up in the
     * complications system - the watch face does not need to do anything else.
     *
     * <p>The activity may be started using {@link Activity#startActivityForResult}. The result
     * delivered back to your activity will have a result code of {@link Activity#RESULT_OK
     * RESULT_OK} if a complication data source was successfully set, or a result code of {@link
     * Activity#RESULT_CANCELED RESULT_CANCELED} if no complication data source was set. In the case
     * where a complication data source was set, {@link ComplicationProviderInfo} for the chosen
     * complication data source will be included in the data intent of the result, as an extra
     * with the key android.support.wearable.complications.EXTRA_PROVIDER_INFO.
     *
     * <p>The package of the calling app must match the package of the watch face, or this will not
     * work.
     *
     * <p>From android R onwards this API can only be called during an editing session.
     *
     * @param context                 context for the current app, that must contain a
     *                                ComplicationHelperActivity
     * @param watchFace               the ComponentName of the WatchFaceService being configured.
     * @param watchFaceComplicationId the watch face's id for the complication being configured.
     *                                This must match the id passed in when the watch face calls
     *                                WatchFaceService.Engine#setActiveComplications.
     * @param supportedTypes          the types supported by the complication, in decreasing
     *                                order of
     *                                preference. If a complication data source can supply data for
     *                                more than one of these types, the type chosen will be
     *                                whichever was specified first.
     * @param watchFaceInstanceId     The ID of the watchface being edited.
     */
    @NonNull
    public static Intent createComplicationDataSourceChooserHelperIntent(
            @NonNull Context context,
            @NonNull ComponentName watchFace,
            int watchFaceComplicationId,
            @NonNull Collection<ComplicationType> supportedTypes,
            @Nullable String watchFaceInstanceId) {
        Intent intent = new Intent(context, ComplicationHelperActivity.class);
        intent.setAction(ACTION_START_PROVIDER_CHOOSER);
        intent.putExtra(
                ComplicationDataSourceChooserIntent.EXTRA_WATCH_FACE_COMPONENT_NAME, watchFace);
        intent.putExtra(
                ComplicationDataSourceChooserIntent.EXTRA_COMPLICATION_ID, watchFaceComplicationId);
        if (watchFaceInstanceId != null) {
            intent.putExtra(ComplicationDataSourceChooserIntent.EXTRA_WATCHFACE_INSTANCE_ID,
                    watchFaceInstanceId);
        }
        int[] wireSupportedTypes = new int[supportedTypes.size()];
        int i = 0;
        for (ComplicationType supportedType : supportedTypes) {
            wireSupportedTypes[i++] = supportedType.toWireComplicationType();
        }
        intent.putExtra(ComplicationDataSourceChooserIntent.EXTRA_SUPPORTED_TYPES,
                wireSupportedTypes);
        return intent;
    }

    /**
     * Returns an intent that may be used to start this activity in order to request the permission
     * required to receive complication data.
     *
     * <p>To use this, the ComplicationHelperActivity must be added to your app, and your app must
     * include the {@code com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA}
     * permission in its manifest.
     *
     * <p>If the current app has already been granted this permission, the activity will finish
     * immediately.
     *
     * <p>If the current app has not been granted this permission, a permission request will be
     * made. If the permission is granted by the user, an update of all complications on the current
     * watch face will be triggered. The provided {@code watchFace} must match the current watch
     * face for this to occur.
     *
     * @param context   context for the current app, that must contain a ComplicationHelperActivity
     * @param watchFace the ComponentName of the WatchFaceService for the current watch face
     */
    @NonNull
    public static Intent createPermissionRequestHelperIntent(
            @NonNull Context context, @NonNull ComponentName watchFace) {
        Intent intent = new Intent(context, ComplicationHelperActivity.class);
        intent.setAction(ACTION_PERMISSION_REQUEST_ONLY);
        intent.putExtra(ComplicationDataSourceChooserIntent.EXTRA_WATCH_FACE_COMPONENT_NAME,
                watchFace);
        return intent;
    }

    private void startComplicationDataSourceChooser() {
        Intent intent =
                ComplicationDataSourceChooserIntent.createComplicationDataSourceChooserIntent(
                        mWatchFace, mWfComplicationId, mTypes);
        // Add the extras that were provided to the ComplicationHelperActivity. This is done by
        // first taking the additional extras and adding to that anything that was set in the
        // chooser intent, and setting them back on the intent itself to avoid the additional
        // extras being able to override anything that was set by the chooser intent.
        Bundle extras = new Bundle(mAdditionalExtras);
        extras.putAll(intent.getExtras());
        intent.replaceExtras(extras);
        if (useTestComplicationDataSourceChooserActivity) {
            intent.setComponent(new ComponentName(
                    "androidx.wear.watchface.editor.test",
                    "androidx.wear.watchface.editor.TestComplicationDataSourceChooserActivity"));
        }
        startActivityForResult(intent, START_REQUEST_CODE_PROVIDER_CHOOSER);
    }

    /** Requests that the system update all active complications on the watch face. */
    private void requestUpdateAll(ComponentName watchFaceComponent) {
        Intent intent = new Intent(ACTION_REQUEST_UPDATE_ALL_ACTIVE);
        intent.setPackage(UPDATE_REQUEST_RECEIVER_PACKAGE);
        intent.putExtra(EXTRA_WATCH_FACE_COMPONENT, watchFaceComponent);
        // Add a placeholder PendingIntent to allow the UID to be checked.
        intent.putExtra(
                ComplicationDataSourceUpdateRequesterConstants.EXTRA_PENDING_INTENT,
                PendingIntent.getActivity(this, 0, new Intent(""), 0));
        sendBroadcast(intent);
    }

    /**
     * Returns any extras that were not handled by the activity itself.
     *
     * <p>These will be forwarded to the chooser activity.
     */
    private Bundle getAdditionalExtras(Intent intent) {
        Bundle extras = intent.getExtras();
        extras.remove(ComplicationDataSourceChooserIntent.EXTRA_WATCH_FACE_COMPONENT_NAME);
        extras.remove(ComplicationDataSourceChooserIntent.EXTRA_COMPLICATION_ID);
        extras.remove(ComplicationDataSourceChooserIntent.EXTRA_SUPPORTED_TYPES);
        return extras;
    }
}