| 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 void initialize(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."); |
| } |
| } |
| } |
| |
| public static MatterAgentClient getInstance() { |
| 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(); |
| } |
| } |
| |
| public void reportAttributeChange(int clusterId, int attributeId) { |
| IMatterAppAgent matterAgent = instance.getMatterAgent(); |
| if (matterAgent == null) { |
| Log.e(TAG, "Matter agent not retrieved."); |
| return; |
| } |
| try { |
| matterAgent.reportAttributeChange(clusterId, attributeId); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Error invoking remote method to report attribute change to Matter agent"); |
| } |
| } |
| |
| 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; |
| } |
| } |
| } |