• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.voicemail.impl.transcribe;
17 
18 import android.app.job.JobInfo;
19 import android.app.job.JobParameters;
20 import android.app.job.JobScheduler;
21 import android.app.job.JobService;
22 import android.app.job.JobWorkItem;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.os.StrictMode;
28 import android.support.annotation.MainThread;
29 import android.support.annotation.VisibleForTesting;
30 import android.support.v4.os.BuildCompat;
31 import android.text.TextUtils;
32 import com.android.dialer.common.Assert;
33 import com.android.dialer.common.LogUtil;
34 import com.android.dialer.constants.ScheduledJobIds;
35 import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
36 import java.util.concurrent.ExecutorService;
37 import java.util.concurrent.Executors;
38 
39 /**
40  * Job scheduler callback for launching voicemail transcription tasks. The transcription tasks will
41  * run in the background and will typically last for approximately the length of the voicemail audio
42  * (since thats how long the backend transcription service takes to do the transcription).
43  */
44 public class TranscriptionService extends JobService {
45   @VisibleForTesting static final String EXTRA_VOICEMAIL_URI = "extra_voicemail_uri";
46 
47   private ExecutorService executorService;
48   private JobParameters jobParameters;
49   private TranscriptionClientFactory clientFactory;
50   private TranscriptionConfigProvider configProvider;
51   private StrictMode.VmPolicy originalPolicy;
52 
53   /** Callback used by a task to indicate it has finished processing its work item */
54   interface JobCallback {
onWorkCompleted(JobWorkItem completedWorkItem)55     void onWorkCompleted(JobWorkItem completedWorkItem);
56   }
57 
58   // Schedule a task to transcribe the indicated voicemail, return true if transcription task was
59   // scheduled.
transcribeVoicemail(Context context, Uri voicemailUri)60   public static boolean transcribeVoicemail(Context context, Uri voicemailUri) {
61     Assert.isMainThread();
62     if (BuildCompat.isAtLeastO()) {
63       LogUtil.i("TranscriptionService.transcribeVoicemail", "scheduling transcription");
64       ComponentName componentName = new ComponentName(context, TranscriptionService.class);
65       JobInfo.Builder builder =
66           new JobInfo.Builder(ScheduledJobIds.VVM_TRANSCRIPTION_JOB, componentName)
67               .setMinimumLatency(0)
68               .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
69       JobScheduler scheduler = context.getSystemService(JobScheduler.class);
70       JobWorkItem workItem = makeWorkItem(voicemailUri);
71       return scheduler.enqueue(builder.build(), workItem) == JobScheduler.RESULT_SUCCESS;
72     } else {
73       LogUtil.i("TranscriptionService.transcribeVoicemail", "not supported");
74       return false;
75     }
76   }
77 
78   // Cancel all transcription tasks
cancelTranscriptions(Context context)79   public static void cancelTranscriptions(Context context) {
80     Assert.isMainThread();
81     LogUtil.enterBlock("TranscriptionService.cancelTranscriptions");
82     JobScheduler scheduler = context.getSystemService(JobScheduler.class);
83     scheduler.cancel(ScheduledJobIds.VVM_TRANSCRIPTION_JOB);
84   }
85 
TranscriptionService()86   public TranscriptionService() {
87     Assert.isMainThread();
88   }
89 
90   @VisibleForTesting
TranscriptionService( ExecutorService executorService, TranscriptionClientFactory clientFactory, TranscriptionConfigProvider configProvider)91   TranscriptionService(
92       ExecutorService executorService,
93       TranscriptionClientFactory clientFactory,
94       TranscriptionConfigProvider configProvider) {
95     this.executorService = executorService;
96     this.clientFactory = clientFactory;
97     this.configProvider = configProvider;
98   }
99 
100   @Override
onStartJob(JobParameters params)101   public boolean onStartJob(JobParameters params) {
102     Assert.isMainThread();
103     LogUtil.enterBlock("TranscriptionService.onStartJob");
104     if (!getConfigProvider().isVoicemailTranscriptionEnabled()) {
105       LogUtil.i("TranscriptionService.onStartJob", "transcription not enabled, exiting.");
106       return false;
107     } else if (TextUtils.isEmpty(getConfigProvider().getServerAddress())) {
108       LogUtil.i("TranscriptionService.onStartJob", "transcription server not configured, exiting.");
109       return false;
110     } else {
111       LogUtil.i(
112           "TranscriptionService.onStartJob",
113           "transcription server address: " + configProvider.getServerAddress());
114       originalPolicy = StrictMode.getVmPolicy();
115       StrictMode.enableDefaults();
116       jobParameters = params;
117       return checkForWork();
118     }
119   }
120 
121   @Override
onStopJob(JobParameters params)122   public boolean onStopJob(JobParameters params) {
123     Assert.isMainThread();
124     LogUtil.enterBlock("TranscriptionService.onStopJob");
125     cleanup();
126     return true;
127   }
128 
129   @Override
onDestroy()130   public void onDestroy() {
131     Assert.isMainThread();
132     LogUtil.enterBlock("TranscriptionService.onDestroy");
133     cleanup();
134   }
135 
cleanup()136   private void cleanup() {
137     if (clientFactory != null) {
138       clientFactory.shutdown();
139       clientFactory = null;
140     }
141     if (executorService != null) {
142       executorService.shutdownNow();
143       executorService = null;
144     }
145     if (originalPolicy != null) {
146       StrictMode.setVmPolicy(originalPolicy);
147       originalPolicy = null;
148     }
149   }
150 
151   @MainThread
checkForWork()152   private boolean checkForWork() {
153     Assert.isMainThread();
154     JobWorkItem workItem = jobParameters.dequeueWork();
155     if (workItem != null) {
156       getExecutorService()
157           .execute(new TranscriptionTask(this, new Callback(), workItem, getClientFactory()));
158       return true;
159     } else {
160       return false;
161     }
162   }
163 
getExecutorService()164   private ExecutorService getExecutorService() {
165     if (executorService == null) {
166       // The common use case is transcribing a single voicemail so just use a single thread executor
167       // The reason we're not using DialerExecutor here is because the transcription task can be
168       // very long running (ie. multiple minutes).
169       executorService = Executors.newSingleThreadExecutor();
170     }
171     return executorService;
172   }
173 
174   private class Callback implements JobCallback {
175     @Override
onWorkCompleted(JobWorkItem completedWorkItem)176     public void onWorkCompleted(JobWorkItem completedWorkItem) {
177       Assert.isMainThread();
178       LogUtil.i("TranscriptionService.Callback.onWorkCompleted", completedWorkItem.toString());
179       jobParameters.completeWork(completedWorkItem);
180       checkForWork();
181     }
182   }
183 
makeWorkItem(Uri voicemailUri)184   private static JobWorkItem makeWorkItem(Uri voicemailUri) {
185     Intent intent = new Intent();
186     intent.putExtra(EXTRA_VOICEMAIL_URI, voicemailUri);
187     return new JobWorkItem(intent);
188   }
189 
getConfigProvider()190   private TranscriptionConfigProvider getConfigProvider() {
191     if (configProvider == null) {
192       configProvider = new TranscriptionConfigProvider(this);
193     }
194     return configProvider;
195   }
196 
getClientFactory()197   private TranscriptionClientFactory getClientFactory() {
198     if (clientFactory == null) {
199       clientFactory = new TranscriptionClientFactory(this, getConfigProvider());
200     }
201     return clientFactory;
202   }
203 }
204