• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.car.voicecontrol;
17 
18 import android.Manifest;
19 import android.app.Notification;
20 import android.app.NotificationChannel;
21 import android.app.NotificationManager;
22 import android.app.PendingIntent;
23 import android.car.Car;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.os.IBinder;
29 import android.os.RemoteCallbackList;
30 import android.os.RemoteException;
31 import android.service.voice.VoiceInteractionService;
32 import android.util.Log;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.core.app.NotificationCompat;
37 
38 import com.android.car.assist.CarVoiceInteractionSession;
39 import com.android.car.telephony.common.Contact;
40 
41 import java.util.Arrays;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Set;
45 
46 /**
47  * Voice interaction entry point. This service is kept running for as long as this application
48  * is set as the default voice interaction service.
49  */
50 public class InteractionService extends VoiceInteractionService {
51     private static final String TAG = "Mica.InteractionService";
52     private static final String CHANNEL_ID = "Mica";
53     private static final int NOTIFICATION_ID = 1;
54     private NotificationManager mNotificationManager;
55     public static final String LOCAL_SERVICE_ACTION = "com.android.car.voicecontrol.local";
56     public static final String LOCAL_SERVICE_CMD_SETUP_CHANGED = "setup-changed";
57     private static final List<String> SUPPORTED_VOICE_ACTIONS = Arrays.asList(
58             CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION,
59             CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION,
60             CarVoiceInteractionSession.VOICE_ACTION_HANDLE_EXCEPTION,
61             CarVoiceInteractionSession.VOICE_ACTION_SEND_SMS
62     );
63     public static final List<String> REQUIRED_PERMISSIONS = Arrays.asList(
64             Manifest.permission.RECORD_AUDIO,
65             Car.PERMISSION_SPEED,
66             Manifest.permission.CALL_PHONE,
67             Manifest.permission.READ_PHONE_STATE,
68             Manifest.permission.SEND_SMS,
69             Manifest.permission.READ_CONTACTS
70     );
71     private ContactsProvider mContactsProvider;
72 
73     private final ILocalService.Stub mBinder = new ILocalService.Stub() {
74         @Override
75         public boolean isSetupComplete() {
76             return InteractionService.this.isSetupComplete();
77         }
78 
79         @Override
80         public void registerListener(ILocalServiceListener listener) {
81             mListeners.register(listener);
82         }
83 
84         @Override
85         public void unregisterListener(ILocalServiceListener listener) {
86             mListeners.unregister(listener);
87         }
88 
89         @Override
90         public String getVoice() {
91             return PreferencesController.getInstance(InteractionService.this).getVoice();
92         }
93 
94         @Override
95         public void setVoice(String name) {
96             PreferencesController.getInstance(InteractionService.this).setVoice(name);
97         }
98 
99         @Override
100         public String getUsername() {
101             String value = PreferencesController.getInstance(InteractionService.this).getUsername();
102             Log.d(TAG, "getUsername(): " + value);
103             return value;
104         }
105 
106         @Override
107         public void setUsername(String value) {
108             Log.d(TAG, "setUsername: " + value);
109             PreferencesController.getInstance(InteractionService.this).setUsername(value);
110             onSetupChanged();
111         }
112 
113         @Override
114         public boolean hasAllPermissions() {
115             return InteractionService.hasAllPermissions(InteractionService.this);
116         }
117 
118         @Override
119         public boolean isNotificationListener() {
120             return InteractionService.this.isNotificationListener();
121         }
122 
123         @Override
124         public Contact getContact(String query, @Nullable String deviceAddress) {
125             return mContactsProvider.getContact(query, deviceAddress);
126         }
127     };
128     private final RemoteCallbackList<ILocalServiceListener> mListeners = new RemoteCallbackList<>();
129 
130     @Override
onBind(Intent intent)131     public IBinder onBind(Intent intent) {
132         Log.d(TAG, "onBind: " + intent);
133         if (LOCAL_SERVICE_ACTION.equals(intent.getAction())) {
134             return mBinder;
135         }
136         return super.onBind(intent);
137     }
138 
139     @Override
onStartCommand(Intent intent, int flags, int startId)140     public int onStartCommand(Intent intent, int flags, int startId) {
141         switch (intent.getAction()) {
142             case LOCAL_SERVICE_CMD_SETUP_CHANGED:
143                 onSetupChanged();
144                 return START_NOT_STICKY;
145             default:
146                 return super.onStartCommand(intent, flags, startId);
147         }
148     }
149 
150     @Override
onCreate()151     public void onCreate() {
152         super.onCreate();
153         mContactsProvider = new ContactsProvider(this);
154     }
155 
156     @Override
onDestroy()157     public void onDestroy() {
158         mContactsProvider.destroy();
159         super.onDestroy();
160     }
161 
162     @Override
onReady()163     public void onReady() {
164         Log.e(TAG, "onReady()");
165         super.onReady();
166         updateNotification();
167 
168         // Setup always-on hot-word detector here (see: VoiceInteractionService#onReady()) for
169         // details.
170     }
171 
isSetupComplete()172     private boolean isSetupComplete() {
173         boolean setupComplete = PreferencesController.getInstance(this).isUserSignedIn()
174                 && hasAllPermissions(this) && isNotificationListener();
175         Log.d(TAG, "isSetupComplete: " + setupComplete);
176         return setupComplete;
177     }
178 
179     /**
180      * @return true if this application has all required dangerous permissions (permissions the
181      * user is able to grant).
182      */
hasAllPermissions(Context context)183     public static boolean hasAllPermissions(Context context) {
184         for (String permission : REQUIRED_PERMISSIONS) {
185             if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
186                 Log.d(TAG, "Missing permission: " + permission);
187                 return false;
188             }
189         }
190         return true;
191     }
192 
isNotificationListener()193     private boolean isNotificationListener() {
194         ComponentName component = new ComponentName(this,
195                 VoiceNotificationListenerService.class);
196         NotificationManager notificationManager = getSystemService(NotificationManager.class);
197         return notificationManager.isNotificationListenerAccessGranted(component);
198     }
199 
onSetupChanged()200     private void onSetupChanged() {
201         Log.d(TAG, "onSetupChanged");
202         int items = mListeners.beginBroadcast();
203         for (int i = 0; i < items; i++) {
204             try {
205                 mListeners.getBroadcastItem(i).setupChanged();
206             } catch (RemoteException e) {
207                 Log.e(TAG, "Unable to notify broadcast item "
208                         + mListeners.getBroadcastItem(i), e);
209             }
210         }
211         mListeners.finishBroadcast();
212         updateNotification();
213     }
214 
updateNotification()215     private void updateNotification() {
216         if (mNotificationManager == null) {
217             mNotificationManager = getSystemService(NotificationManager.class);
218             NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
219                     getString(R.string.app_name), NotificationManager.IMPORTANCE_DEFAULT);
220             mNotificationManager.createNotificationChannel(channel);
221         }
222         if (isSetupComplete()) {
223             Log.d(TAG, "updateNotification(): is setup complete = true");
224             mNotificationManager.cancel(NOTIFICATION_ID);
225             return;
226         }
227         Log.d(TAG, "updateNotification(): is setup complete = false");
228         Intent intent = new Intent(this, SignInActivity.class);
229         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
230         PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
231                 intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
232 
233         Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
234                 .setSmallIcon(R.drawable.ic_launcher)
235                 .setColor(getColor(R.color.car_ui_color_accent))
236                 .setContentTitle(getString(R.string.notification_setup_title))
237                 .setContentText(getString(R.string.notification_setup_text))
238                 .setContentIntent(pendingIntent)
239                 .build();
240 
241         mNotificationManager.notify(NOTIFICATION_ID, notification);
242     }
243 
244     @NonNull
245     @Override
onGetSupportedVoiceActions(@onNull Set<String> voiceActions)246     public Set<String> onGetSupportedVoiceActions(@NonNull Set<String> voiceActions) {
247         Set<String> result = new HashSet<>(voiceActions);
248         result.retainAll(SUPPORTED_VOICE_ACTIONS);
249         return result;
250     }
251 
252 }
253