-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tv-app:content-app to tv-app:platform-app service binding and command…
… response support (#18353) * Changes to add dynamic discovery of clusters and response for commands * IMaaterAppAgent interface
- Loading branch information
Showing
15 changed files
with
431 additions
and
20 deletions.
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
...es/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/IMatterAppAgent.aidl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
12 changes: 12 additions & 0 deletions
12
...roid/App/common-api/src/main/aidl/com/matter/tv/app/api/ReportAttributeChangeRequest.aidl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// ReportAttributeChangeRequest.aidl | ||
package com.matter.tv.app.api; | ||
|
||
parcelable ReportAttributeChangeRequest{ | ||
|
||
int clusterIdentifier; | ||
|
||
int attributeIdentifier; | ||
|
||
String value; | ||
|
||
} |
10 changes: 10 additions & 0 deletions
10
...droid/App/common-api/src/main/aidl/com/matter/tv/app/api/SetSupportedClustersRequest.aidl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// SetSupportedClustersRequest.aidl | ||
package com.matter.tv.app.api; | ||
|
||
import com.matter.tv.app.api.SupportedCluster; | ||
|
||
parcelable SetSupportedClustersRequest { | ||
|
||
List<SupportedCluster> supportedClusters; | ||
|
||
} |
12 changes: 12 additions & 0 deletions
12
...s/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SupportedCluster.aidl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// SupportedCluster.aidl | ||
package com.matter.tv.app.api; | ||
|
||
parcelable SupportedCluster { | ||
|
||
int clusterIdentifier; | ||
|
||
String[] features; | ||
|
||
int[] optionalCommandIdentifiers; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
...ndroid/App/content-app/src/main/java/com/example/contentapp/matter/MatterAgentClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 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<ResolveInfo> 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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.