Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee57f49c authored by Fabian Kozynski's avatar Fabian Kozynski
Browse files

Merge ControlState into Control

All the information that was sent with a ControlState is now sent with a
new updated Control object. That way, overriding Type and changes to
name can be done inside subscribe.

Modified ControlsProviderService to encapsulate the callback service and
provide public methods to send information to SystemUI. In particular,
onLoad verifies that all Control passed do not have status information
and erases that information if it has any.

Add token for reusing the same Callback binder.

Test: build
Change-Id: I6ce5e9ad53899ea588b23e0f067b5d1f86a0bb18
parent 6b7926d1
Loading
Loading
Loading
Loading
+210 −55
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.service.controls;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -28,28 +29,26 @@ import android.util.Log;

import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Represents a physical object that can be represented by a {@link ControlTemplate} and whose
 * properties may be modified through a {@link ControlAction}.
 *
 * The information is provided by a {@link ControlProviderService} and represents static
 * The information is provided by a {@link ControlsProviderService} and represents static
 * information (not current status) about the device.
 * <p>
 * Each control needs a unique (per provider) identifier that is persistent across reboots of the
 * system.
 * <p>
 * Each {@link Control} will have a name, a subtitle and will optionally belong to a structure
 * and zone. Some of these values are defined by the user and/or the {@link ControlProviderService}
 * and zone. Some of these values are defined by the user and/or the {@link ControlsProviderService}
 * and will be used to display the control as well as group them for management.
 * <p>
 * Each object will have an associated {@link DeviceTypes.DeviceType}. This will determine the icons and colors
 * used to display it.
 * <p>
 * The {@link ControlTemplate.TemplateType} provided will be used as a hint when displaying this in
 * non-interactive situations (for example when there's no state to display). This template is not
 * the one that will be shown with the current state and provide interactions. That template is set
 * using {@link ControlState}.
 * <p>
 * An {@link Intent} linking to the provider Activity that expands on this {@link Control} and
 * allows for further actions should be provided.
 * @hide
@@ -57,17 +56,52 @@ import com.android.internal.util.Preconditions;
public class Control implements Parcelable {
    private static final String TAG = "Control";

    ;
    private static final int NUM_STATUS = 5;
    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            STATUS_UNKNOWN,
            STATUS_OK,
            STATUS_NOT_FOUND,
            STATUS_ERROR,
            STATUS_DISABLED,
    })
    public @interface Status {};

    public static final int STATUS_UNKNOWN = 0;

    /**
     * The device corresponding to the {@link Control} is responding correctly.
     */
    public static final int STATUS_OK = 1;

    /**
     * The device corresponding to the {@link Control} cannot be found or was removed.
     */
    public static final int STATUS_NOT_FOUND = 2;

    /**
     * The device corresponding to the {@link Control} is in an error state.
     */
    public static final int STATUS_ERROR = 3;

    /**
     * The {@link Control} is currently disabled.
     */
    public static final int STATUS_DISABLED = 4;

    private final @NonNull String mControlId;
    private final @DeviceTypes.DeviceType
    int mDeviceType;
    private final @DeviceTypes.DeviceType int mDeviceType;
    private final @NonNull CharSequence mTitle;
    private final @NonNull CharSequence mSubtitle;
    private final @Nullable CharSequence mStructure;
    private final @Nullable CharSequence mZone;
    private final @NonNull PendingIntent mAppIntent;
    private final @ControlTemplate.TemplateType int mPrimaryType;
    private final @Status int mStatus;
    private final @NonNull ControlTemplate mControlTemplate;
    private final @NonNull CharSequence mStatusText;

    /**
     * @param controlId the unique persistent identifier for this object.
@@ -79,7 +113,6 @@ public class Control implements Parcelable {
     * @param zone
     * @param appIntent a {@link PendingIntent} linking to a page to interact with the
     *                  corresponding device.
     * @param primaryType the primary template for this type.
     */
    public Control(@NonNull String controlId,
            @DeviceTypes.DeviceType int deviceType,
@@ -88,11 +121,15 @@ public class Control implements Parcelable {
            @Nullable CharSequence structure,
            @Nullable CharSequence zone,
            @NonNull PendingIntent appIntent,
            int primaryType) {
            @Status int status,
            @NonNull ControlTemplate controlTemplate,
            @NonNull CharSequence statusText) {
        Preconditions.checkNotNull(controlId);
        Preconditions.checkNotNull(title);
        Preconditions.checkNotNull(subtitle);
        Preconditions.checkNotNull(appIntent);
        Preconditions.checkNotNull(controlTemplate);
        Preconditions.checkNotNull(statusText);
        mControlId = controlId;
        if (!DeviceTypes.validDeviceType(deviceType)) {
            Log.e(TAG, "Invalid device type:" + deviceType);
@@ -105,7 +142,14 @@ public class Control implements Parcelable {
        mStructure = structure;
        mZone = zone;
        mAppIntent = appIntent;
        mPrimaryType = primaryType;
        if (status < 0 || status >= NUM_STATUS) {
            mStatus = STATUS_UNKNOWN;
            Log.e(TAG, "Status unknown:" + status);
        } else {
            mStatus = status;
        }
        mControlTemplate = controlTemplate;
        mStatusText = statusText;
    }

    public Control(Parcel in) {
@@ -124,7 +168,9 @@ public class Control implements Parcelable {
            mZone = null;
        }
        mAppIntent = PendingIntent.CREATOR.createFromParcel(in);
        mPrimaryType = in.readInt();
        mStatus = in.readInt();
        mControlTemplate = ControlTemplate.CREATOR.createFromParcel(in);
        mStatusText = in.readCharSequence();
    }

    @NonNull
@@ -162,9 +208,19 @@ public class Control implements Parcelable {
        return mAppIntent;
    }

    @android.service.controls.templates.ControlTemplate.TemplateType
    public int getPrimaryType() {
        return mPrimaryType;
    @Status
    public int getStatus() {
        return mStatus;
    }

    @NonNull
    public ControlTemplate getControlTemplate() {
        return mControlTemplate;
    }

    @NonNull
    public CharSequence getStatusText() {
        return mStatusText;
    }

    @Override
@@ -191,7 +247,9 @@ public class Control implements Parcelable {
            dest.writeByte((byte) 0);
        }
        mAppIntent.writeToParcel(dest, flags);
        dest.writeInt(mPrimaryType);
        dest.writeInt(mStatus);
        mControlTemplate.writeToParcel(dest, flags);
        dest.writeCharSequence(mStatusText);
    }

    public static final Creator<Control> CREATOR = new Creator<Control>() {
@@ -209,32 +267,39 @@ public class Control implements Parcelable {
    /**
     * Builder class for {@link Control}.
     *
     * This class facilitates the creation of {@link Control}. It provides the following
     * defaults for non-optional parameters:
     * This class facilitates the creation of {@link Control} with no state.
     * It provides the following defaults for non-optional parameters:
     * <ul>
     *     <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN}
     *     <li> Title: {@code ""}
     *     <li> Subtitle: {@code ""}
     *     <li> Primary template: {@link ControlTemplate#TYPE_NONE}
     * </ul>
     * This fixes the values relating to state of the {@link Control} as required by
     * {@link ControlsProviderService#onLoad}:
     * <ul>
     *     <li> Status: {@link Status#STATUS_UNKNOWN}
     *     <li> Control template: {@link ControlTemplate#NO_TEMPLATE}
     *     <li> Status text: {@code ""}
     * </ul>
     */
    public static class Builder {
        private static final String TAG = "Control.Builder";
        private @NonNull String mControlId;
        private @DeviceTypes.DeviceType
        int mDeviceType = DeviceTypes.TYPE_UNKNOWN;
        private @NonNull CharSequence mTitle = "";
        private @NonNull CharSequence mSubtitle = "";
        private @Nullable CharSequence mStructure;
        private @Nullable CharSequence mZone;
        private @NonNull PendingIntent mAppIntent;
        private @ControlTemplate.TemplateType int mPrimaryType = ControlTemplate.TYPE_NONE;
    public static class StatelessBuilder {
        private static final String TAG = "StatelessBuilder";
        protected @NonNull String mControlId;
        protected @DeviceTypes.DeviceType int mDeviceType = DeviceTypes.TYPE_UNKNOWN;
        protected @NonNull CharSequence mTitle = "";
        protected @NonNull CharSequence mSubtitle = "";
        protected @Nullable CharSequence mStructure;
        protected @Nullable CharSequence mZone;
        protected @NonNull PendingIntent mAppIntent;
        protected @Status int mStatus = STATUS_UNKNOWN;
        protected @NonNull ControlTemplate mControlTemplate = ControlTemplate.NO_TEMPLATE;
        protected @NonNull CharSequence mStatusText = "";

        /**
         * @param controlId the identifier for the {@link Control}.
         * @param appIntent the pending intent linking to the device Activity.
         */
        public Builder(@NonNull String controlId,
        public StatelessBuilder(@NonNull String controlId,
                @NonNull PendingIntent appIntent) {
            Preconditions.checkNotNull(controlId);
            Preconditions.checkNotNull(appIntent);
@@ -243,10 +308,10 @@ public class Control implements Parcelable {
        }

        /**
         * Creates a {@link Builder} using an existing {@link Control} as a base.
         * Creates a {@link StatelessBuilder} using an existing {@link Control} as a base.
         * @param control base for the builder.
         */
        public Builder(@NonNull Control control) {
        public StatelessBuilder(@NonNull Control control) {
            Preconditions.checkNotNull(control);
            mControlId = control.mControlId;
            mDeviceType = control.mDeviceType;
@@ -255,7 +320,6 @@ public class Control implements Parcelable {
            mStructure = control.mStructure;
            mZone = control.mZone;
            mAppIntent = control.mAppIntent;
            mPrimaryType = control.mPrimaryType;
        }

        /**
@@ -263,14 +327,14 @@ public class Control implements Parcelable {
         * @return {@code this}
         */
        @NonNull
        public Builder setControlId(@NonNull String controlId) {
        public StatelessBuilder setControlId(@NonNull String controlId) {
            Preconditions.checkNotNull(controlId);
            mControlId = controlId;
            return this;
        }

        @NonNull
        public Builder setDeviceType(@DeviceTypes.DeviceType int deviceType) {
        public StatelessBuilder setDeviceType(@DeviceTypes.DeviceType int deviceType) {
            if (!DeviceTypes.validDeviceType(deviceType)) {
                Log.e(TAG, "Invalid device type:" + deviceType);
                mDeviceType = DeviceTypes.TYPE_UNKNOWN;
@@ -285,27 +349,27 @@ public class Control implements Parcelable {
         * @return {@code this}
         */
        @NonNull
        public Builder setTitle(@NonNull CharSequence title) {
        public StatelessBuilder setTitle(@NonNull CharSequence title) {
            Preconditions.checkNotNull(title);
            mTitle = title;
            return this;
        }

        @NonNull
        public Builder setSubtitle(@NonNull CharSequence subtitle) {
        public StatelessBuilder setSubtitle(@NonNull CharSequence subtitle) {
            Preconditions.checkNotNull(subtitle);
            mSubtitle = subtitle;
            return this;
        }

        @NonNull
        public Builder setStructure(@Nullable CharSequence structure) {
        public StatelessBuilder setStructure(@Nullable CharSequence structure) {
            mStructure = structure;
            return this;
        }

        @NonNull
        public Builder setZone(@Nullable CharSequence zone) {
        public StatelessBuilder setZone(@Nullable CharSequence zone) {
            mZone = zone;
            return this;
        }
@@ -315,22 +379,12 @@ public class Control implements Parcelable {
         * @return {@code this}
         */
        @NonNull
        public Builder setAppIntent(@NonNull PendingIntent appIntent) {
        public StatelessBuilder setAppIntent(@NonNull PendingIntent appIntent) {
            Preconditions.checkNotNull(appIntent);
            mAppIntent = appIntent;
            return this;
        }

        /**
         * @param type type to use as default in the {@link Control}
         * @return {@code this}
         */
        @NonNull
        public Builder setPrimaryType(@ControlTemplate.TemplateType int type) {
            mPrimaryType = type;
            return this;
        }

        /**
         * Build a {@link Control}
         * @return a valid {@link Control}
@@ -344,7 +398,108 @@ public class Control implements Parcelable {
                    mStructure,
                    mZone,
                    mAppIntent,
                    mPrimaryType);
                    mStatus,
                    mControlTemplate,
                    mStatusText);
        }
    }

    public static class StatefulBuilder extends StatelessBuilder {
        private static final String TAG = "StatefulBuilder";

        /**
         * @param controlId the identifier for the {@link Control}.
         * @param appIntent the pending intent linking to the device Activity.
         */
        public StatefulBuilder(@NonNull String controlId,
                @NonNull PendingIntent appIntent) {
            super(controlId, appIntent);
        }

        public StatefulBuilder(@NonNull Control control) {
            super(control);
            mStatus = control.mStatus;
            mControlTemplate = control.mControlTemplate;
            mStatusText = control.mStatusText;
        }

        /**
         * @param controlId the identifier for the {@link Control}.
         * @return {@code this}
         */
        @NonNull
        public StatefulBuilder setControlId(@NonNull String controlId) {
            super.setControlId(controlId);
            return this;
        }

        @NonNull
        public StatefulBuilder setDeviceType(@DeviceTypes.DeviceType int deviceType) {
            super.setDeviceType(deviceType);
            return this;
        }

        /**
         * @param title the user facing name of the {@link Control}
         * @return {@code this}
         */
        @NonNull
        public StatefulBuilder setTitle(@NonNull CharSequence title) {
            super.setTitle(title);
            return this;
        }

        @NonNull
        public StatefulBuilder setSubtitle(@NonNull CharSequence subtitle) {
            super.setSubtitle(subtitle);
            return this;
        }

        @NonNull
        public StatefulBuilder setStructure(@Nullable CharSequence structure) {
            super.setStructure(structure);
            return this;
        }

        @NonNull
        public StatefulBuilder setZone(@Nullable CharSequence zone) {
            super.setZone(zone);
            return this;
        }

        /**
         * @param appIntent an {@link Intent} linking to an Activity for the {@link Control}
         * @return {@code this}
         */
        @NonNull
        public StatefulBuilder setAppIntent(@NonNull PendingIntent appIntent) {
            super.setAppIntent(appIntent);
            return this;
        }

        @NonNull
        public StatefulBuilder setStatus(@Status int status) {
            if (status < 0 || status >= NUM_STATUS) {
                mStatus = STATUS_UNKNOWN;
                Log.e(TAG, "Status unknown:" + status);
            } else {
                mStatus = status;
            }
            return this;
        }

        @NonNull
        public StatefulBuilder setControlTemplate(@NonNull ControlTemplate controlTemplate) {
            Preconditions.checkNotNull(controlTemplate);
            mControlTemplate = controlTemplate;
            return this;
        }

        @NonNull
        public StatefulBuilder setStatusText(@NonNull CharSequence statusText) {
            Preconditions.checkNotNull(statusText);
            mStatusText = statusText;
            return this;
        }
    }
}
+0 −19
Original line number Diff line number Diff line
/*
 * Copyright (c) 2019, 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 android.service.controls;

parcelable ControlState;
 No newline at end of file
+0 −335

File deleted.

Preview size limit exceeded, changes collapsed.

+82 −4
Original line number Diff line number Diff line
@@ -20,13 +20,21 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.service.controls.actions.ControlAction;
import android.service.controls.templates.ControlTemplate;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.List;

/**
@@ -38,8 +46,14 @@ public abstract class ControlsProviderService extends Service {

    @SdkConstant(SdkConstantType.SERVICE_ACTION)
    public static final String CONTROLS_ACTION = "android.service.controls.ControlsProviderService";
    public static final String CALLBACK_BUNDLE = "CALLBACK_BUNDLE";
    public static final String CALLBACK_BINDER = "CALLBACK_BINDER";
    public static final String CALLBACK_TOKEN = "CALLBACK_TOKEN";

    public final String TAG = getClass().getSimpleName();

    private IControlsProviderCallback mCallback;
    private IBinder mToken;
    private RequestHandler mHandler;

    /**
@@ -67,16 +81,80 @@ public abstract class ControlsProviderService extends Service {
     */
    public abstract void onAction(@NonNull String controlId, @NonNull ControlAction action);

    protected IControlsProviderCallback getControlsProviderCallback() {
        return mCallback;
    /**
     * Sends a list of the controls available from this service.
     *
     * The items in the list must not have state information (as created by
     * {@link Control.StatelessBuilder}).
     * @param controls
     */
    public void onLoad(@NonNull List<Control> controls) {
        Preconditions.checkNotNull(controls);
        List<Control> list = new ArrayList<>();
        for (Control control: controls) {
            if (control == null) {
                Log.e(TAG, "onLoad: null control.");
            }
            if (isStateless(control)) {
                list.add(control);
            } else {
                Log.w(TAG, "onLoad: control is not stateless.");
                list.add(new Control.StatelessBuilder(control).build());
            }
        }
        try {
            mCallback.onLoad(mToken, list);
        } catch (RemoteException ex) {
            ex.rethrowAsRuntimeException();
        }
    }

    /**
     * Sends a list of the controls requested by {@link ControlsProviderService#subscribe} with
     * their state.
     * @param statefulControls
     */
    public void onRefreshState(@NonNull List<Control> statefulControls) {
        Preconditions.checkNotNull(statefulControls);
        try {
            mCallback.onRefreshState(mToken, statefulControls);
        } catch (RemoteException ex) {
            ex.rethrowAsRuntimeException();
        }
    }

    /**
     * Sends the response of a command in the specified {@link Control}.
     * @param controlId
     * @param response
     */
    public void onControlActionResponse(
            @NonNull String controlId, @ControlAction.ResponseResult int response) {
        Preconditions.checkNotNull(controlId);
        if (!ControlAction.isValidResponse(response)) {
            Log.e(TAG, "Not valid response result: " + response);
            response = ControlAction.RESPONSE_UNKNOWN;
        }
        try {
            mCallback.onControlActionResponse(mToken, controlId, response);
        } catch (RemoteException ex) {
            ex.rethrowAsRuntimeException();
        }
    }

    private boolean isStateless(Control control) {
        return (control.getStatus() == Control.STATUS_UNKNOWN
                    && control.getControlTemplate().getTemplateType() == ControlTemplate.TYPE_NONE
                    && TextUtils.isEmpty(control.getStatusText()));
    }

    @Override
    public IBinder onBind(Intent intent) {
        mHandler = new RequestHandler(Looper.getMainLooper());

        Bundle bundle = intent.getBundleExtra("CALLBACK_BUNDLE");
        IBinder callbackBinder = bundle.getBinder("CALLBACK_BINDER");
        Bundle bundle = intent.getBundleExtra(CALLBACK_BUNDLE);
        IBinder callbackBinder = bundle.getBinder(CALLBACK_BINDER);
        mToken = bundle.getBinder(CALLBACK_TOKEN);
        mCallback = IControlsProviderCallback.Stub.asInterface(callbackBinder);

        return new IControlsProvider.Stub() {
+3 −4
Original line number Diff line number Diff line
@@ -17,13 +17,12 @@
package android.service.controls;

import android.service.controls.Control;
import android.service.controls.ControlState;

/** @hide */
oneway interface IControlsProviderCallback {
    void onLoad(in List<Control> controls);
    void onLoad(in IBinder token, in List<Control> controls);

    void onRefreshState(in List<ControlState> controlStates);
    void onRefreshState(in IBinder token, in List<Control> statefulControls);

    void onControlActionResponse(in String controlId, int response);
    void onControlActionResponse(in IBinder token, in String controlId, int response);
}
 No newline at end of file
Loading