1 /**
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.voicecontrol;
18 
19 import android.annotation.UiThread;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.ServiceConnection;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.car.telephony.common.Contact;
32 
33 /**
34  * A client that is able to talk to {@link InteractionService} over IPC (as InteractionService
35  * might be running on a different process than the UI).
36  */
37 public class InteractionServiceClient {
38     private static final String TAG = "Mica.InteractionServiceClient";
39 
40     private final Context mContext;
41     private ILocalService mInteractionService;
42     private Handler mHandler = new Handler();
43     private ILocalServiceListener mListener = new ILocalServiceListener.Stub() {
44         @Override
45         public void setupChanged() {
46             mHandler.post(() -> onSetupChanged());
47         }
48     };
49     private final ServiceConnection mConnection = new ServiceConnection() {
50         @Override
51         public void onServiceConnected(ComponentName className, IBinder service) {
52             mInteractionService = ILocalService.Stub.asInterface(service);
53             try {
54                 mInteractionService.registerListener(mListener);
55             } catch (RemoteException e) {
56                 Log.e(TAG, "Unable to register listener", e);
57             }
58             mHandler.post(() -> onConnected());
59         }
60 
61         @Override
62         public void onServiceDisconnected(ComponentName name) {
63             mHandler.post(() -> onDisconnected());
64             try {
65                 mInteractionService.unregisterListener(mListener);
66             } catch (RemoteException e) {
67                 Log.e(TAG, "Unable to unregister listener", e);
68             }
69             mInteractionService = null;
70         }
71     };
72 
InteractionServiceClient(Context context)73     public InteractionServiceClient(Context context) {
74         mContext = context;
75     }
76 
77     /**
78      * Connects this client to its service
79      */
connect()80     public void connect() {
81         Intent intent = new Intent(mContext, InteractionService.class);
82         intent.setAction(InteractionService.LOCAL_SERVICE_ACTION);
83         mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
84     }
85 
86     /**
87      * Disconnects from the service
88      */
disconnect()89     public void disconnect() {
90         mContext.unbindService(mConnection);
91         mInteractionService = null;
92     }
93 
isConnected()94     boolean isConnected() {
95         return mInteractionService != null;
96     }
97 
98     @UiThread
onConnected()99     void onConnected() {}
100 
101     @UiThread
onDisconnected()102     void onDisconnected() {}
103 
104     @UiThread
onSetupChanged()105     void onSetupChanged() {}
106 
isSetupComplete()107     boolean isSetupComplete() {
108         return getRemote(() -> mInteractionService.isSetupComplete());
109     }
110 
getUsername()111     String getUsername() {
112         return getRemote(() -> mInteractionService.getUsername());
113     }
114 
hasUsername()115     boolean hasUsername() {
116         return getUsername() != null;
117     }
118 
setUsername(String username)119     void setUsername(String username) {
120         getRemote(() -> {
121             mInteractionService.setUsername(username);
122             return null;
123         });
124     }
125 
hasAllPermissions()126     boolean hasAllPermissions() {
127         return getRemote(() -> mInteractionService.hasAllPermissions());
128     }
129 
isNotificationListener()130     boolean isNotificationListener() {
131         return getRemote(() -> mInteractionService.isNotificationListener());
132     }
133 
134     /**
135      * @return the currently selected TTS voice
136      */
getVoice()137     public String getVoice() {
138         return getRemote(() -> mInteractionService.getVoice());
139     }
140 
141     /**
142      * Sets the TTS voice to be used
143      */
setVoice(String name)144     public void setVoice(String name) {
145         getRemote(() -> {
146             mInteractionService.setVoice(name);
147             return null;
148         });
149     }
150 
151     /**
152      * @return a contact matching the provided query. See {@link ContactsProvider} for more details.
153      */
getContact(String query, @Nullable String deviceAddress)154     public Contact getContact(String query, @Nullable String deviceAddress) {
155         return getRemote(() -> mInteractionService.getContact(query, deviceAddress));
156     }
157 
158     private interface RemoteSupplier<T> {
get()159         T get() throws RemoteException;
160     }
161 
getRemote(RemoteSupplier<T> supplier)162     private <T> T getRemote(RemoteSupplier<T> supplier) {
163         try {
164             if (mInteractionService == null) {
165                 throw new IllegalStateException("Service is not connected");
166             }
167             return supplier.get();
168         } catch (RemoteException e) {
169             throw new IllegalStateException("Unable to connect to service", e);
170         }
171     }
172 
notifySetupChanged()173     final void notifySetupChanged() {
174         Intent intent = new Intent(mContext, InteractionService.class);
175         intent.setAction(InteractionService.LOCAL_SERVICE_CMD_SETUP_CHANGED);
176         mContext.startService(intent);
177     }
178 }
179