diff --git a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/IMatterAppAgent.aidl b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/IMatterAppAgent.aidl new file mode 100644 index 00000000000000..930a2436fa1a2b --- /dev/null +++ b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/IMatterAppAgent.aidl @@ -0,0 +1,33 @@ +// IMatterAppAgent.aidl +package com.matter.tv.app.api; + +import com.matter.tv.app.api.SetSupportedClustersRequest; +import com.matter.tv.app.api.ReportAttributeChangeRequest; + +/* + * To use this interface, partners should query for and bind to a service that handles the "com.matter.tv.app.api.action.MatterAppAgent" Action. + * They should verify the host process holds the "com.matter.tv.app.api.permission.SEND_DATA" permission + * To bind to this service the client app itself must hold "com.matter.tv.app.api.permission.BIND_SERVICE_PERMISSION". + */ +interface IMatterAppAgent { + /** + * Report dynamic clusters to matter agent. Note that this api is not incremental, every time it is called + * you must report ALL dynamic clusters the app supports. Any dynamic clusters previously reported + * which are not reported in a subsequent call will be removed. This does NOT impact static clusters + * declared in app resources; those cannot be removed. However, a dynamic cluster can be used to override + * and hide a static one based on cluster name. + * + * @param SetClustersRequest request object containing the list of clusters to assert for this app. + * @returns true if successful. + */ + // TODO : replace the boolean with some kind of enumerated status field + boolean setSupportedClusters(in SetSupportedClustersRequest request); + + /** + * Reports the Attribute changes of attributes. + * @param - ReportAttributeChangeRequest, request object containing all attributes which have changed. + * @return - ReportAttributeChangeResult, returns success or error code + */ + // TODO : replace the boolean with some kind of enumerated status field + boolean reportAttributeChange(in ReportAttributeChangeRequest request); +} \ No newline at end of file diff --git a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/ReportAttributeChangeRequest.aidl b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/ReportAttributeChangeRequest.aidl new file mode 100644 index 00000000000000..abc10066340e44 --- /dev/null +++ b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/ReportAttributeChangeRequest.aidl @@ -0,0 +1,12 @@ +// ReportAttributeChangeRequest.aidl +package com.matter.tv.app.api; + +parcelable ReportAttributeChangeRequest{ + + int clusterIdentifier; + + int attributeIdentifier; + + String value; + +} \ No newline at end of file diff --git a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SetSupportedClustersRequest.aidl b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SetSupportedClustersRequest.aidl new file mode 100644 index 00000000000000..d067074648be7e --- /dev/null +++ b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SetSupportedClustersRequest.aidl @@ -0,0 +1,10 @@ +// SetSupportedClustersRequest.aidl +package com.matter.tv.app.api; + +import com.matter.tv.app.api.SupportedCluster; + +parcelable SetSupportedClustersRequest { + + List supportedClusters; + +} \ No newline at end of file diff --git a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SupportedCluster.aidl b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SupportedCluster.aidl new file mode 100644 index 00000000000000..7db26283d00de1 --- /dev/null +++ b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SupportedCluster.aidl @@ -0,0 +1,12 @@ +// SupportedCluster.aidl +package com.matter.tv.app.api; + +parcelable SupportedCluster { + + int clusterIdentifier; + + String[] features; + + int[] optionalCommandIdentifiers; + +} \ No newline at end of file diff --git a/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/MatterIntentConstants.java b/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/MatterIntentConstants.java index 3f78e9de06b1c4..4ee15e4a84c496 100644 --- a/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/MatterIntentConstants.java +++ b/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/MatterIntentConstants.java @@ -4,5 +4,17 @@ public class MatterIntentConstants { public static final String ACTION_MATTER_COMMAND = "com.matter.tv.app.api.action.MATTER_COMMAND"; + public static final String ACTION_MATTER_AGENT = "com.matter.tv.app.api.action.MatterAppAgent"; + + public static final String PERMISSION_MATTER_AGENT_BIND = + "com.matter.tv.app.api.permission.BIND_SERVICE_PERMISSION"; + + public static final String PERMISSION_MATTER_AGENT = "com.matter.tv.app.api.permission.SEND_DATA"; + public static final String EXTRA_COMMAND_PAYLOAD = "EXTRA_COMMAND_PAYLOAD"; + + public static final String EXTRA_RESPONSE_PAYLOAD = "EXTRA_RESPONSE_PAYLOAD"; + + public static final String EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT = + "EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT"; } diff --git a/examples/tv-app/android/App/content-app/build.gradle b/examples/tv-app/android/App/content-app/build.gradle index 21553a014e51a7..046b57b93e3c8f 100644 --- a/examples/tv-app/android/App/content-app/build.gradle +++ b/examples/tv-app/android/App/content-app/build.gradle @@ -33,6 +33,7 @@ android { 'src/main/java', '../common-api/src/main/java', ] + aidl.srcDirs = ['../common-api/src/main/aidl'] } } diff --git a/examples/tv-app/android/App/content-app/src/main/AndroidManifest.xml b/examples/tv-app/android/App/content-app/src/main/AndroidManifest.xml index 107283a2474a44..c67adc438f879a 100644 --- a/examples/tv-app/android/App/content-app/src/main/AndroidManifest.xml +++ b/examples/tv-app/android/App/content-app/src/main/AndroidManifest.xml @@ -1,7 +1,13 @@ + + + + diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/MainActivity.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/MainActivity.java index 6b1474e21b57e1..dc9b8eb26f6641 100644 --- a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/MainActivity.java +++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/MainActivity.java @@ -2,12 +2,20 @@ import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; +import com.example.contentapp.matter.MatterAgentClient; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class MainActivity extends AppCompatActivity { + private static final String TAG = "ContentAppMainActivity"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + MatterAgentClient matterAgentClient = MatterAgentClient.getInstance(getApplicationContext()); + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.execute(matterAgentClient::reportClusters); } } diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/matter/MatterAgentClient.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/matter/MatterAgentClient.java new file mode 100644 index 00000000000000..36bb872c8854e3 --- /dev/null +++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/matter/MatterAgentClient.java @@ -0,0 +1,216 @@ +package com.example.contentapp.matter; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import com.matter.tv.app.api.IMatterAppAgent; +import com.matter.tv.app.api.MatterIntentConstants; +import com.matter.tv.app.api.SetSupportedClustersRequest; +import com.matter.tv.app.api.SupportedCluster; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +public class MatterAgentClient { + + private static final String TAG = "MatterAgentClient"; + private static MatterAgentClient instance; + private IMatterAppAgent service; + private boolean bound = false; + private CountDownLatch latch = new CountDownLatch(1); + + // TODO : Introduce dependency injection + private MatterAgentClient() {}; + + public static synchronized MatterAgentClient getInstance(Context context) { + if (instance == null || (instance.service == null && !instance.bound)) { + instance = new MatterAgentClient(); + if (!instance.bindService(context)) { + Log.e(TAG, "Matter agent binding request unsuccessful."); + instance = null; + } else { + Log.d(TAG, "Matter agent binding request successful."); + } + } + return instance; + } + + public void reportClusters() { + IMatterAppAgent matterAgent = instance.getMatterAgent(); + if (matterAgent == null) { + Log.e(TAG, "Matter agent not retrieved."); + return; + } + SetSupportedClustersRequest supportedClustersRequest = new SetSupportedClustersRequest(); + supportedClustersRequest.supportedClusters = new ArrayList(); + SupportedCluster supportedCluster = new SupportedCluster(); + supportedCluster.clusterIdentifier = 1; + + supportedClustersRequest.supportedClusters.add(supportedCluster); + try { + boolean success = matterAgent.setSupportedClusters(supportedClustersRequest); + Log.d(TAG, "Setting supported clusters returned " + (success ? "True" : "False")); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + private IMatterAppAgent getMatterAgent() { + try { + latch.await(); + return service; + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for service connection.", e); + } + return null; + } + + private synchronized boolean bindService(Context context) { + + ServiceConnection serviceConnection = new MyServiceConnection(); + final Intent intent = new Intent(MatterIntentConstants.ACTION_MATTER_AGENT); + if (intent.getComponent() == null) { + final ResolveInfo resolveInfo = + resolveBindIntent( + context, + intent, + MatterIntentConstants.PERMISSION_MATTER_AGENT_BIND, + MatterIntentConstants.PERMISSION_MATTER_AGENT); + if (resolveInfo == null) { + Log.e(TAG, "No Service available on device to bind for intent " + intent); + return false; + } + final ComponentName component = + new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); + intent.setComponent(component); + } + + try { + Log.e(TAG, "Binding to service"); + bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + return bound; + } catch (final Throwable e) { + Log.e(TAG, "Exception binding to service", e); + } + return false; + } + + /** + * Returns a {@link ResolveInfo} for a service bindable with the provided intent and permission. + * + * @param context Android Context. + * @param bindIntent The Intent used to bind to the Service. Implicit or Explicit. + * @param bindPermission The permission that the resolved Service must enforce. + * @param permissionHeldByService A permission that the resolved Service must hold. + * @return A {@link ResolveInfo} with ServiceInfo for the service, or null. + */ + private ResolveInfo resolveBindIntent( + final Context context, + final Intent bindIntent, + final String bindPermission, + final String permissionHeldByService) { + if (bindPermission == null || permissionHeldByService == null) { + Log.w( + TAG, + "Must specify the permission protecting the service, as well as " + + "a permission held by the service's package."); + return null; + } + final PackageManager pm = context.getPackageManager(); + if (pm == null) { + Log.w(TAG, "Package manager is not available."); + return null; + } + // Check for Services able to handle this intent. + final List infos = pm.queryIntentServices(bindIntent, 0); + if (infos == null || infos.isEmpty()) { + return null; + } + + // For all the services returned, remove those that don't have the specified permissions. + int size = infos.size(); + for (int i = size - 1; i >= 0; --i) { + final ResolveInfo resolveInfo = infos.get(i); + // The service must be protected by the bindPermission + if (!bindPermission.equals(resolveInfo.serviceInfo.permission)) { + Log.w( + TAG, + String.format( + "Service (%s) does not enforce the required permission (%s)", + resolveInfo.serviceInfo.name, bindPermission)); + infos.remove(i); + continue; + } + // And the service's package must hold the permissionHeldByService permission + final String pkgName = resolveInfo.serviceInfo.packageName; + final int state = pm.checkPermission(permissionHeldByService, pkgName); + if (state != PackageManager.PERMISSION_GRANTED) { + Log.w( + TAG, + String.format( + "Package (%s) does not hold the required permission (%s)", + pkgName, bindPermission)); + infos.remove(i); + } + } + size = infos.size(); + + if (size > 1) { + // This is suspicious. This means we've got at least 2 services both claiming to handle + // this intent, and they both have declared this permission. In this case, filter those + // that aren't on the system image. + for (int i = size - 1; i >= 0; --i) { + final ResolveInfo resolveInfo = infos.get(i); + try { + final ApplicationInfo appInfo = + pm.getApplicationInfo(resolveInfo.serviceInfo.packageName, 0); + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 + && (appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { + // Not a system app or an updated system app. Remove this sketchy service. + infos.remove(i); + } + } catch (final PackageManager.NameNotFoundException e) { + infos.remove(i); + } + } + } + + if (infos.size() > 1) { + Log.w( + TAG, + "More than one permission-enforced system" + + " service can handle intent " + + bindIntent + + " and permission " + + bindPermission); + } + + return (infos.isEmpty() ? null : infos.get(0)); + } + + private class MyServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + Log.d( + TAG, + String.format( + "onServiceConnected for API with intent action %s", + MatterIntentConstants.ACTION_MATTER_AGENT)); + service = IMatterAppAgent.Stub.asInterface(binder); + latch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + service = null; + } + } +} diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java index 21f285cac24878..0f01b9ee71f394 100644 --- a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java +++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java @@ -1,5 +1,6 @@ package com.example.contentapp.receiver; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -22,12 +23,27 @@ public void onReceive(Context context, Intent intent) { case MatterIntentConstants.ACTION_MATTER_COMMAND: byte[] commandPayload = intent.getByteArrayExtra(MatterIntentConstants.EXTRA_COMMAND_PAYLOAD); - Log.e( + Log.d( TAG, new StringBuilder() .append("Received matter command: ") .append(intent.getAction()) .toString()); + + PendingIntent pendingIntent = + intent.getParcelableExtra( + MatterIntentConstants.EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT); + if (pendingIntent != null) { + final Intent responseIntent = + new Intent() + .putExtra(MatterIntentConstants.EXTRA_RESPONSE_PAYLOAD, "Success".getBytes()); + try { + pendingIntent.send(context, 0, responseIntent); + } catch (final PendingIntent.CanceledException ex) { + Log.e(TAG, "Error sending pending intent to the Matter agent", ex); + } + } + break; default: Log.e( TAG, diff --git a/examples/tv-app/android/App/platform-app/build.gradle b/examples/tv-app/android/App/platform-app/build.gradle index e41d3556a488ae..5d9b8e32a4a9f1 100644 --- a/examples/tv-app/android/App/platform-app/build.gradle +++ b/examples/tv-app/android/App/platform-app/build.gradle @@ -44,10 +44,12 @@ android { 'src/main/java', '../common-api/src/main/java', ] + aidl.srcDirs = ['../common-api/src/main/aidl'] // uncomment this code to debug // java.srcDirs = [ // 'src/main/java', +// '../common-api/src/main/java', // '../../third_party/connectedhomeip/src/setup_payload/java/src', // '../../third_party/connectedhomeip/src/platform/android/java', // '../../third_party/connectedhomeip/src/app/server/java/src/', diff --git a/examples/tv-app/android/App/platform-app/src/main/AndroidManifest.xml b/examples/tv-app/android/App/platform-app/src/main/AndroidManifest.xml index 02cd1a760b91c6..1d1445480beab7 100644 --- a/examples/tv-app/android/App/platform-app/src/main/AndroidManifest.xml +++ b/examples/tv-app/android/App/platform-app/src/main/AndroidManifest.xml @@ -1,13 +1,18 @@ + + + - + @@ -18,6 +23,8 @@ + - - + android:exported="true" + android:permission="com.matter.tv.app.api.permission.BIND_SERVICE_PERMISSION"> + - - - + - + diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java index 3d37170f7479da..4d51004023f8b9 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java @@ -42,6 +42,14 @@ public class MainActivity extends AppCompatActivity { private LinkedHashMap packages = new LinkedHashMap<>(); + @Override + protected void onRestart() { + super.onRestart(); + packages.clear(); + ContentAppDiscoveryService.getReceiverInstance() + .initializeMatterApps(this.getApplicationContext()); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -140,8 +148,8 @@ public void onReceive(Context context, Intent intent) { registerReceiver(broadcastReceiver, new IntentFilter("com.matter.tv.server.appagent.add")); registerReceiver(broadcastReceiver, new IntentFilter("com.matter.tv.server.appagent.remove")); - ContentAppDiscoveryService.getRecieverInstance().registerSelf(this.getApplicationContext()); - ContentAppDiscoveryService.getRecieverInstance() + ContentAppDiscoveryService.getReceiverInstance().registerSelf(this.getApplicationContext()); + ContentAppDiscoveryService.getReceiverInstance() .initializeMatterApps(this.getApplicationContext()); } diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/receivers/ContentAppDiscoveryService.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/receivers/ContentAppDiscoveryService.java index a108d6118e0c4f..b5b3793d0b414b 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/receivers/ContentAppDiscoveryService.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/receivers/ContentAppDiscoveryService.java @@ -17,7 +17,7 @@ import java.util.Set; public class ContentAppDiscoveryService extends BroadcastReceiver { - private static final String TAG = "MatterContentAppDiscoveryService"; + private static final String TAG = "ContentAppDiscoveryService"; static final String CLUSTERS_RESOURCE_METADATA_KEY = "com.matter.app_agent_api.clusters"; private static final String ANDROID_PACKAGE_REMOVED_ACTION = "android.intent.action.PACKAGE_REMOVED"; @@ -116,10 +116,10 @@ public void registerSelf(Context context) { Log.i(TAG, "Trying to register the matter package update receiver"); IntentFilter pckAdded = new IntentFilter(ANDROID_PACKAGE_ADDED_ACTION); - pckAdded.hasDataScheme("package"); + pckAdded.addDataScheme("package"); context.registerReceiver(this, pckAdded); IntentFilter pckRemoved = new IntentFilter(ANDROID_PACKAGE_REMOVED_ACTION); - pckRemoved.hasDataScheme("package"); + pckRemoved.addDataScheme("package"); context.registerReceiver(this, pckRemoved); Log.i(TAG, "Registered the matter package update receiver"); } @@ -134,7 +134,8 @@ public void initializeMatterApps(Context context) { private Set getMatterApps(Context context) { PackageManager pm = context.getPackageManager(); List receivers = - pm.queryBroadcastReceivers(new Intent(MatterIntentConstants.ACTION_MATTER_COMMAND), 0); + pm.queryBroadcastReceivers( + new Intent(MatterIntentConstants.ACTION_MATTER_COMMAND), PackageManager.MATCH_ALL); Set matterApps = new HashSet<>(); if (receivers.isEmpty()) { @@ -151,7 +152,8 @@ private Set getMatterApps(Context context) { return matterApps; } - public static ContentAppDiscoveryService getRecieverInstance() { + // TODO : Introduce dependency injection + public static ContentAppDiscoveryService getReceiverInstance() { return instance; } } diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ContentAppAgentService.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ContentAppAgentService.java index d9fb849aae26e8..40e997a692e211 100644 --- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ContentAppAgentService.java +++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ContentAppAgentService.java @@ -1,17 +1,53 @@ package com.matter.tv.server.service; +import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; import androidx.annotation.Nullable; +import com.matter.tv.app.api.IMatterAppAgent; import com.matter.tv.app.api.MatterIntentConstants; public class ContentAppAgentService extends Service { + + private static final String TAG = "ContentAppAgentService"; + public static final String ACTION_MATTER_RESPONSE = + "com.matter.tv.app.api.action.MATTER_COMMAND_RESPONSE"; + public static final String EXTRA_RESPONSE_RECEIVING_PACKAGE = "EXTRA_RESPONSE_RECEIVING_PACKAGE"; + public static final String EXTRA_RESPONSE_ID = "EXTRA_RESPONSE_ID"; + + private final IBinder appAgentBinder = + new IMatterAppAgent.Stub() { + @Override + public boolean setSupportedClusters( + com.matter.tv.app.api.SetSupportedClustersRequest request) throws RemoteException { + Log.d( + TAG, + "Received request to add the following supported clusters " + + request.supportedClusters.toString()); + // TODO : need to (re)discover the app with the new clusters. + return true; + } + + @Override + public boolean reportAttributeChange( + com.matter.tv.app.api.ReportAttributeChangeRequest request) throws RemoteException { + return false; + } + }; + @Nullable @Override - public IBinder onBind(Intent intent) { + public IBinder onBind(final Intent intent) { + Log.d(TAG, "Received binding request."); + if (MatterIntentConstants.ACTION_MATTER_AGENT.equals(intent.getAction())) { + Log.d(TAG, "Returning MatterAppAgent"); + return appAgentBinder; + } return null; } @@ -21,6 +57,37 @@ public static void sendCommand(Context context, String packageName) { extras.putByteArray(MatterIntentConstants.EXTRA_COMMAND_PAYLOAD, "test payload".getBytes()); in.putExtras(extras); in.setPackage(packageName); + int flags = Intent.FLAG_INCLUDE_STOPPED_PACKAGES; + flags |= Intent.FLAG_RECEIVER_FOREGROUND; + in.setFlags(flags); + in.putExtra( + MatterIntentConstants.EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT, + getPendingIntentForResponse(context, packageName, "0")); context.sendBroadcast(in); } + + private static PendingIntent getPendingIntentForResponse( + final Context context, final String targetPackage, final String responseId) { + Intent ackBackIntent = new Intent(ACTION_MATTER_RESPONSE); + ackBackIntent.setClass(context, ContentAppAgentService.class); + ackBackIntent.putExtra(EXTRA_RESPONSE_RECEIVING_PACKAGE, targetPackage); + ackBackIntent.putExtra(EXTRA_RESPONSE_ID, responseId); + + return PendingIntent.getService(context, 0, ackBackIntent, PendingIntent.FLAG_ONE_SHOT); + } + + @Override + public int onStartCommand(final Intent intent, final int flags, final int startId) { + Log.d(TAG, "onStartCommand"); + + if (intent != null && ACTION_MATTER_RESPONSE.equals(intent.getAction())) { + Log.d( + TAG, + "Command response " + + intent.getByteArrayExtra(MatterIntentConstants.EXTRA_RESPONSE_PAYLOAD)); + // Send the response back to the client. + } + + return START_NOT_STICKY; + } }