javaandroidcrashwear-osandroid-pendingintent

Crash because of PendingIntent for Complication in Wear OS


I've been trying to find the error myself for two days now, but so far I haven't been able to find one. The following in advance. There are two classes: the BroadcastReceiver and the ProviderService. When I click the complication I get the following error:

FATAL EXCEPTION: main
                                                                                                    Process: com.danddstudios.counter, PID: 12971
                                                                                                    java.lang.RuntimeException: Unable to start receiver com.danddstudios.counter.Complications.BroadcastReceiver.IndicatorComplicationTapBroadcastReceiver: java.lang.IllegalArgumentException: com.danddstudios.counter: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
                                                                                                    Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
                                                                                                        at android.app.ActivityThread.handleReceiver(ActivityThread.java:4345)
                                                                                                        at android.app.ActivityThread.-$$Nest$mhandleReceiver(Unknown Source:0)
                                                                                                        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2154)
                                                                                                        at android.os.Handler.dispatchMessage(Handler.java:106)
                                                                                                        at android.os.Looper.loopOnce(Looper.java:201)
                                                                                                        at android.os.Looper.loop(Looper.java:288)
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:7898)
                                                                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                                                                        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
                                                                                                    Caused by: java.lang.IllegalArgumentException: com.danddstudios.counter: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
                                                                                                    Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
                                                                                                        at android.app.PendingIntent.checkFlags(PendingIntent.java:401)
                                                                                                        at android.app.PendingIntent.getActivityAsUser(PendingIntent.java:484)
                                                                                                        at android.app.PendingIntent.getActivity(PendingIntent.java:470)
                                                                                                        at android.app.PendingIntent.getActivity(PendingIntent.java:434)
                                                                                                        at android.support.wearable.complications.ProviderUpdateRequester.requestUpdate(ProviderUpdateRequester.java:103)
                                                                                                        at com.danddstudios.counter.Complications.BroadcastReceiver.IndicatorComplicationTapBroadcastReceiver.initComplications(IndicatorComplicationTapBroadcastReceiver.java:81)
                                                                                                        at com.danddstudios.counter.Complications.BroadcastReceiver.IndicatorComplicationTapBroadcastReceiver.onReceive(IndicatorComplicationTapBroadcastReceiver.java:53)
                                                                                                        at android.app.ActivityThread.handleReceiver(ActivityThread.java:4336)
 9 more

Apparently "FLAG_IMMUTABLE" is missing when creating the PendingIntent. However, this is included in my code. The error message still comes. Attached are the classes “IndicatorComplicationTapBroadcastReceiver” and “IndicatorCounterComplicationProviderService”. At lower API levels (30 and below) the code works perfectly. The error occurs as soon as the BroadcastReceiver tries to call the "initComplications()" method. The error message in question then appears with “requestUpdateAll()”.

public class IndicatorComplicationTapBroadcastReceiver extends BroadcastReceiver {

    private static final String EXTRA_PROVIDER_COMPONENT = "com.danddstudios.android.wearable.watchface.provider.action.PROVIDER_COMPONENT";
    private static final String EXTRA_COMPLICATION_ID = "com.danddstudios.android.wearable.watchface.provider.action.COMPLICATION_ID";
    private static final String TAG3 = "SyncLog/IndicatorComplicationTapBroadcastReceiver"; // firestore log;
    private static final String TAG5 = "TLog/IndicatorComplicationTapBroadcastReceiver"; // tiles log

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i(TAG5, "started onReceive()");
        // method to load item list from sharedPreferences //

        Log.i(TAG3, "started loadArrayList()");
        ArrayList<CounterItem> counterList;
        SharedPreferences prefs = context.getSharedPreferences("saveCounter", Context.MODE_PRIVATE);
        Gson gson = new Gson();
        String json = prefs.getString("saveCounter", null);
        Type type = new TypeToken<ArrayList<CounterItem>>() {
        }.getType();
        counterList = gson.fromJson(json, type);
        if (counterList == null) {
            Log.i(TAG3, "counterList = null");
            counterList = new ArrayList<>();
            counterList.add(new CounterItem("0", context.getString(R.string.app_name) + " " + 1, 0, 1));
        } else {
            Log.i(TAG3, "counterList != null");
        }
        Log.i(TAG3, "counter = " + counterList.get(0).getInt());



        initComplications(context);
        refreshTile(context);
    }

    /**
     * Returns a pending intent, suitable for use as a tap intent, that causes a complication to be
     * toggled and updated.
     */
    public static PendingIntent getToggleIntent(Context context, ComponentName provider, int complicationId) { // accessed in ProviderService
        Log.i(TAG5, "started getToggleIntent");
        Intent intent = new Intent(context, IndicatorComplicationTapBroadcastReceiver.class);
        intent.putExtra(EXTRA_PROVIDER_COMPONENT, provider);
        intent.putExtra(EXTRA_COMPLICATION_ID, complicationId);
        intent.setPackage(context.getPackageName()); // for security

        // Pass complicationId as the requestCode to ensure that different complications get
        // different intents.

        // Create a PendingIntent using FLAG_IMMUTABLE
        Log.i(TAG5, "return pendingIntent");
        return PendingIntent.getBroadcast(context, complicationId, intent, FLAG_IMMUTABLE);
    }

    public void initComplications(Context c) {
        Log.i(TAG5, "started initComplications()");
        ComponentName componentName1 = new ComponentName(c, IndicatorCounterComplicationProviderService.class);
        // Request an update for the complication
        ProviderUpdateRequester requester1 = new ProviderUpdateRequester(c, componentName1);
        requester1.requestUpdate();
    }

    public void refreshTile(Context context) {
        Log.i(TAG5, "started refreshTile()");
        TileService.getUpdater(context.getApplicationContext())
                .requestUpdate(TileService.class);
    }

}

and the service:

public class IndicatorCounterComplicationProviderService extends ComplicationProviderService {

    int counter;
    String counterString;
    private static final String TAG3 = "SyncLog/IndicatorCounterComplicationProviderService"; // complication log
    private static final String TAG5 = "TLog/IndicatorComplicationTapBroadcastReceiver"; // tiles log

    @Override
    public void onComplicationActivated(int complicationId, int dataType, ComplicationManager complicationManager) {
        Log.i(TAG3, "onComplicationActivated(): " + complicationId);
        Log.i(TAG5, "onComplicationActivated() id: " + complicationId);
        ArrayList<CounterItem> counterList;
        SharedPreferences prefs = getSharedPreferences("saveCounter", Context.MODE_PRIVATE);
        Gson gson = new Gson();
        String json = prefs.getString("saveCounter", null);
        Type type = new TypeToken<ArrayList<CounterItem>>() {
        }.getType();
        counterList = gson.fromJson(json, type);
        if (counterList == null) {
            Log.i(TAG3, "counterList = null");
            counterList = new ArrayList<>();
            counterList.add(new CounterItem("0", getString(R.string.app_name) + " " + 1, 0, 1));
        } else {
            Log.i(TAG3, "counterArray != null");
        }
        counterString = "" + counterList.get(0).getInt();
    }

    /*
     * 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.i(TAG3, "onComplicationUpdate() id: " + complicationId);
        Log.i(TAG5, "onComplicationUpdate() id: " + complicationId);
        ArrayList<CounterItem> counterList;
        SharedPreferences prefs = getSharedPreferences("saveCounter", Context.MODE_PRIVATE);
        Gson gson = new Gson();
        String json = prefs.getString("saveCounter", null);
        Type type = new TypeToken<ArrayList<CounterItem>>() {
        }.getType();
        counterList = gson.fromJson(json, type);
        if (counterList == null) {
            Log.i(TAG3, "counterList = null");
            counterList = new ArrayList<>();
            counterList.add(new CounterItem("0", getString(R.string.app_name) + " " + 1, 0, 1));
        } else {
            Log.i(TAG3, "counterList != null");
        }
        Log.i(TAG3, "counter = " + counterList.get(0).getInt());
        counterString = "" + counterList.get(0).getInt();

        // 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 = IndicatorComplicationTapBroadcastReceiver.getToggleIntent(this, thisProvider, complicationId); // which broadcast receiver should be loaded
        ComplicationData complicationData = null;

        switch (dataType) {
            case ComplicationData.TYPE_SHORT_TEXT:
                complicationData = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
                        .setShortText(ComplicationText.plainText(counterString))
                        .setTapAction(complicationPendingIntent)
                        .build();
                break;

            case ComplicationData.TYPE_LONG_TEXT:
                complicationData = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
                                .setLongText(ComplicationText.plainText(getString(R.string.app_name) + ": " + counterString))
                                .setTapAction(complicationPendingIntent)
                                .build();
                break;
            default:
                Log.i(TAG3, "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.i(TAG3, "onComplicationDeactivated(): " + complicationId);
    }
}

The error occurs as soon as you increase the API level to 31. I tried to solve the problem myself by deleting the "initComplications()" method. After that, the complication no longer updates. It should only display a number (counter).


Solution

  • I had the same problem for days haha... in short there is a new version of the library...

    1. add these dependecies instead of the old ones (for java / I dont remember witch ones exactly, but the old ones are the ones with -alpha):
    implementation "androidx.wear.watchface:watchface:1.1.1"
    implementation "androidx.wear.watchface:watchface-complications-data-source:1.1.1"´
    
    1. change the complication classes, it will now extend ComplicationDataSourceService... like this:
    public class YourComplicationClassName extends ComplicationDataSourceService
    
    1. Update all the imports of the YourComplicationClassName
    2. The new code to update a complication:
    ComponentName cn = new ComponentName(this, YourComplicationClassName.class);
    
    ComplicationDataSourceUpdateRequester cdsur = ComplicationDataSourceUpdateRequester.create(this, cn);
    
    cdsur.requestUpdateAll();
    

    That's all... I think haha

    Some references for you:

    Ps. These new libray works on OS 4 and 3, I tested...