How to expose data to watch face manually triggered

435 views Asked by At

I'm new to Android Wear OS. I would like to expose numeric data (0,1,2,3,...,9) to an Android Smartwatch's data field (triggered manually without tapping on the data field).

As described here,

https://developer.android.com/codelabs/data-providers#0

I already implemented the onReceive() / onComplicationUpdate() methods:

public class ComplicationTapBroadcastReceiver extends BroadcastReceiver {

private static final String EXTRA_PROVIDER_COMPONENT =
        "com.example.android.wearable.watchface.provider.action.PROVIDER_COMPONENT";
private static final String EXTRA_COMPLICATION_ID =
        "com.example.android.wearable.watchface.provider.action.COMPLICATION_ID";

static final int MAX_NUMBER = 20;
static final String COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY =
        "com.example.android.wearable.watchface.COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY";

@Override
public void onReceive(Context context, Intent intent) {
    Bundle extras = intent.getExtras();
    ComponentName provider = extras.getParcelable(EXTRA_PROVIDER_COMPONENT);
    int complicationId = extras.getInt(EXTRA_COMPLICATION_ID);

    // Retrieve data via SharedPreferences.
    String preferenceKey = getPreferenceKey(provider, complicationId);
    SharedPreferences sharedPreferences =
            context.getSharedPreferences(COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY, 0);

    int value = sharedPreferences.getInt(preferenceKey, 0);

    // Update data for complication.
    value = (value + 1) % MAX_NUMBER;

    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.putInt(preferenceKey, value);
    editor.apply();

    // Request an update for the complication that has just been tapped.
    ProviderUpdateRequester requester = new ProviderUpdateRequester(context, provider);
    requester.requestUpdate(complicationId);
}

/**
 * Returns a pending intent, suitable for use as a tap intent, that causes a complication to be
 * toggled and updated.
 */
static PendingIntent getToggleIntent(
        Context context, ComponentName provider, int complicationId) {
    Intent intent = new Intent(context, ComplicationTapBroadcastReceiver.class);
    intent.putExtra(EXTRA_PROVIDER_COMPONENT, provider);
    intent.putExtra(EXTRA_COMPLICATION_ID, complicationId);

    // Pass complicationId as the requestCode to ensure that different complications get
    // different intents.
    return PendingIntent.getBroadcast(
            context, complicationId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}

/**
 * Returns the key for the shared preference used to hold the current state of a given
 * complication.
 */
static String getPreferenceKey(ComponentName provider, int complicationId) {
    return provider.getClassName() + complicationId;
}
}

public class CustomComplicationProviderService extends ComplicationProviderService {

private static final String TAG = "ComplicationProvider";

/*
 * Called when a complication has been activated. The method is for any one-time
 * (per complication) set-up.
 *
 * You can continue sending data for the active complicationId until onComplicationDeactivated()
 * is called.
 */
@Override
public void onComplicationActivated(
        int complicationId, int dataType, ComplicationManager complicationManager) {
    Log.d(TAG, "onComplicationActivated(): " + complicationId);
}

/*
 * Called when the complication needs updated data from your provider. There are four scenarios
 * when this will happen:
 *
 *   1. An active watch face complication is changed to use this provider
 *   2. A complication using this provider becomes active
 *   3. The period of time you specified in the manifest has elapsed (UPDATE_PERIOD_SECONDS)
 *   4. You triggered an update from your own class via the
 *       ProviderUpdateRequester.requestUpdate() method.
 */
@Override
public void onComplicationUpdate(
        int complicationId, int dataType, ComplicationManager complicationManager) {
    Log.d(TAG, "onComplicationUpdate() id: " + complicationId);

    // Create Tap Action so that the user can trigger an update by tapping the complication.
    ComponentName thisProvider = new ComponentName(this, getClass());
    // We pass the complication id, so we can only update the specific complication tapped.
    PendingIntent complicationPendingIntent =
            ComplicationTapBroadcastReceiver.getToggleIntent(
                    this, thisProvider, complicationId);

    // Retrieves your data, in this case, we grab an incrementing number from SharedPrefs.
    SharedPreferences preferences =
            getSharedPreferences(
                    ComplicationTapBroadcastReceiver.COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY,
                    0);
    int number =
            preferences.getInt(
                    ComplicationTapBroadcastReceiver.getPreferenceKey(
                            thisProvider, complicationId),
                    0);
    String numberText = String.format(Locale.getDefault(), "%d!", number);

    ComplicationData complicationData = null;

    switch (dataType) {
        case ComplicationData.TYPE_SHORT_TEXT:
            complicationData =
                    new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
                            .setShortText(ComplicationText.plainText(numberText))
                            .setTapAction(complicationPendingIntent)
                            .build();
            break;
        case ComplicationData.TYPE_LONG_TEXT:
            complicationData =
                    new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
                            .setLongText(ComplicationText.plainText("Number: " + numberText))
                            .setTapAction(complicationPendingIntent)
                            .build();
            break;
        case ComplicationData.TYPE_RANGED_VALUE:
            complicationData =
                    new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
                            .setValue(number)
                            .setMinValue(0)
                            .setMaxValue(ComplicationTapBroadcastReceiver.MAX_NUMBER)
                            .setShortText(ComplicationText.plainText(numberText))
                            .setTapAction(complicationPendingIntent)
                            .build();
            break;
        default:
            if (Log.isLoggable(TAG, Log.WARN)) {
                Log.w(TAG, "Unexpected complication type " + dataType);
            }
    }

    if (complicationData != null) {
        complicationManager.updateComplicationData(complicationId, complicationData);

    } else {
        // If no data is sent, we still need to inform the ComplicationManager, so the update
        // job can finish and the wake lock isn't held any longer than necessary.
        complicationManager.noUpdateRequired(complicationId);
    }
}

/*
 * Called when the complication has been deactivated.
 */
@Override
public void onComplicationDeactivated(int complicationId) {
    Log.d(TAG, "onComplicationDeactivated(): " + complicationId);
}
}

The code is running, but...

a) How is it possible to update the display of my Smartwatch's data field with my sensor's information, without tapping on the data field of my Smartwatch? Is it even possible?

b) How do I have to configure AndroidManifest.xml, and how do I need to implement the ProviderUpdateRequester method requestUpdateAll() in my Java application to trigger the display update manually?

new ProviderUpdateRequester(context, new ComponentName(context, "myComplicationClass"))
        .requestUpdateAll();

What is "context" in my case, and what is "myComplicationClass"?

Unfortunately, I haven't found any good code example yet...

1

There are 1 answers

0
Pezcraft On

To update your complication with your sensor value, you have to store it using shared preferences. Do this inside onSensorChanged of your SensorEventListener.

    @Override
    public void onSensorChanged(SensorEvent event) {
        String heartrate = Integer.toString(Math.round(event.values[0]));

        getSharedPreferences("callItWhatEverYouWant", 0).edit().putString("heartrate", heartrate).apply();

        ComponentName providerComponentName = new ComponentName(this, CustomComplicationProviderService.class);
        ProviderUpdateRequester providerUpdateRequester = new ProviderUpdateRequester(this, providerComponentName);
        providerUpdateRequester.requestUpdateAll();
    }

Currently you are getting a number from shared preferences and convert it into numberText. Replace this with your sensor value shared preference defined earlier.

@Override
public void onComplicationUpdate(
        int complicationId, int dataType, ComplicationManager complicationManager) {
    Log.d(TAG, "onComplicationUpdate() id: " + complicationId);

    // Create Tap Action so that the user can trigger an update by tapping the complication.
    ComponentName thisProvider = new ComponentName(this, getClass());
    // We pass the complication id, so we can only update the specific complication tapped.
    PendingIntent complicationPendingIntent =
            ComplicationTapBroadcastReceiver.getToggleIntent(
                    this, thisProvider, complicationId);

    // Retrieve your sensor value
    String heartrate = getSharedPreferences("callItWhatEverYouWant", 0).getString("heartrate", "--");

    ComplicationData complicationData = null;

    switch (dataType) {
        case ComplicationData.TYPE_SHORT_TEXT:
            complicationData =
                    new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
                            .setShortText(ComplicationText.plainText(heartrate))
                            .setTapAction(complicationPendingIntent)
                            .build();
            break;
        case ComplicationData.TYPE_LONG_TEXT:
            complicationData =
                    new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
                            .setLongText(ComplicationText.plainText("Heart Rate: " + heartrate))
                            .setTapAction(complicationPendingIntent)
                            .build();
            break;
        case ComplicationData.TYPE_RANGED_VALUE:
            complicationData =
                    new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
                            .setValue(Integer.parseInt(heartrate))
                            .setMinValue(0)
                            .setMaxValue(ComplicationTapBroadcastReceiver.MAX_NUMBER)
                            .setShortText(ComplicationText.plainText(heartrate))
                            .setTapAction(complicationPendingIntent)
                            .build();
            break;
        default:
            if (Log.isLoggable(TAG, Log.WARN)) {
                Log.w(TAG, "Unexpected complication type " + dataType);
            }
    }

    if (complicationData != null) {
        complicationManager.updateComplicationData(complicationId, complicationData);

    } else {
        // If no data is sent, we still need to inform the ComplicationManager, so the update
        // job can finish and the wake lock isn't held any longer than necessary.
        complicationManager.noUpdateRequired(complicationId);
    }
}

Manifest...

        <service
            android:name=".CustomComplicationProviderService"
            android:exported="true"
            android:icon="@drawable/ic_heart"
            android:label="Current Heartrate"
            android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
            <intent-filter>
                <action
                    android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST"/>
            </intent-filter>

            <meta-data
                android:name="android.support.wearable.complications.SUPPORTED_TYPES"
                android:value="RANGED_VALUE,SHORT_TEXT,LONG_TEXT"/>

            <meta-data
                android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
                android:value="0" />
        </service>