• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.server.translation;
18 
19 import static android.Manifest.permission.MANAGE_UI_TRANSLATION;
20 import static android.app.PendingIntent.FLAG_IMMUTABLE;
21 import static android.content.Context.TRANSLATION_MANAGER_SERVICE;
22 import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
23 import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
24 
25 import static com.android.internal.util.SyncResultReceiver.bundleFor;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.app.PendingIntent;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.PackageManager;
34 import android.os.Binder;
35 import android.os.IBinder;
36 import android.os.IRemoteCallback;
37 import android.os.RemoteException;
38 import android.os.ResultReceiver;
39 import android.os.ShellCallback;
40 import android.os.UserHandle;
41 import android.util.Slog;
42 import android.view.autofill.AutofillId;
43 import android.view.translation.ITranslationManager;
44 import android.view.translation.TranslationContext;
45 import android.view.translation.TranslationSpec;
46 import android.view.translation.UiTranslationManager.UiTranslationState;
47 import android.view.translation.UiTranslationSpec;
48 
49 import com.android.internal.annotations.GuardedBy;
50 import com.android.internal.os.IResultReceiver;
51 import com.android.internal.util.DumpUtils;
52 import com.android.server.infra.AbstractMasterSystemService;
53 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
54 
55 import java.io.FileDescriptor;
56 import java.io.PrintWriter;
57 import java.util.List;
58 
59 /**
60  * Entry point service for translation management.
61  *
62  * <p>This service provides the {@link ITranslationManager} implementation and keeps a list of
63  * {@link TranslationManagerServiceImpl} per user; the real work is done by
64  * {@link TranslationManagerServiceImpl} itself.
65  */
66 public final class TranslationManagerService
67         extends AbstractMasterSystemService<TranslationManagerService,
68         TranslationManagerServiceImpl> {
69 
70     private static final String TAG = "TranslationManagerService";
71 
72     private static final int MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS = 2 * 60_000; // 2 minutes
73 
TranslationManagerService(Context context)74     public TranslationManagerService(Context context) {
75         // TODO: Discuss the disallow policy
76         super(context, new FrameworkResourcesServiceNameResolver(context,
77                         com.android.internal.R.string.config_defaultTranslationService),
78                 /* disallowProperty */ null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
79     }
80 
81     @Override
newServiceLocked(int resolvedUserId, boolean disabled)82     protected TranslationManagerServiceImpl newServiceLocked(int resolvedUserId, boolean disabled) {
83         return new TranslationManagerServiceImpl(this, mLock, resolvedUserId, disabled);
84     }
85 
86     @Override
enforceCallingPermissionForManagement()87     protected void enforceCallingPermissionForManagement() {
88         getContext().enforceCallingPermission(MANAGE_UI_TRANSLATION, TAG);
89     }
90 
91     @Override
getMaximumTemporaryServiceDurationMs()92     protected int getMaximumTemporaryServiceDurationMs() {
93         return MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS;
94     }
95 
96     @Override
dumpLocked(String prefix, PrintWriter pw)97     protected void dumpLocked(String prefix, PrintWriter pw) {
98         super.dumpLocked(prefix, pw);
99     }
100 
enforceCallerHasPermission(String permission)101     private void enforceCallerHasPermission(String permission) {
102         final String msg = "Permission Denial from pid =" + Binder.getCallingPid() + ", uid="
103                 + Binder.getCallingUid() + " doesn't hold " + permission;
104         getContext().enforceCallingPermission(permission, msg);
105     }
106 
107     /** True if the currently set handler service is not overridden by the shell. */
108     @GuardedBy("mLock")
isDefaultServiceLocked(int userId)109     private boolean isDefaultServiceLocked(int userId) {
110         final String defaultServiceName = mServiceNameResolver.getDefaultServiceName(userId);
111         if (defaultServiceName == null) {
112             return false;
113         }
114 
115         final String currentServiceName = mServiceNameResolver.getServiceName(userId);
116         return defaultServiceName.equals(currentServiceName);
117     }
118 
119     /** True if the caller of the api is the same app which hosts the TranslationService. */
120     @GuardedBy("mLock")
isCalledByServiceAppLocked(int userId, @NonNull String methodName)121     private boolean isCalledByServiceAppLocked(int userId, @NonNull String methodName) {
122         final int callingUid = Binder.getCallingUid();
123 
124         final String serviceName = mServiceNameResolver.getServiceName(userId);
125         if (serviceName == null) {
126             Slog.e(TAG, methodName + ": called by UID " + callingUid
127                     + ", but there's no service set for user " + userId);
128             return false;
129         }
130 
131         final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
132         if (serviceComponent == null) {
133             Slog.w(TAG, methodName + ": invalid service name: " + serviceName);
134             return false;
135         }
136 
137         final String servicePackageName = serviceComponent.getPackageName();
138         final PackageManager pm = getContext().getPackageManager();
139         final int serviceUid;
140         try {
141             serviceUid = pm.getPackageUidAsUser(servicePackageName, userId);
142         } catch (PackageManager.NameNotFoundException e) {
143             Slog.w(TAG, methodName + ": could not verify UID for " + serviceName);
144             return false;
145         }
146         if (callingUid != serviceUid) {
147             Slog.e(TAG, methodName + ": called by UID " + callingUid + ", but service UID is "
148                     + serviceUid);
149             return false;
150         }
151         return true;
152     }
153 
154     final class TranslationManagerServiceStub extends ITranslationManager.Stub {
155 
156         @Override
onTranslationCapabilitiesRequest(@ranslationSpec.DataFormat int sourceFormat, @TranslationSpec.DataFormat int targetFormat, ResultReceiver receiver, int userId)157         public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
158                 @TranslationSpec.DataFormat int targetFormat,
159                 ResultReceiver receiver, int userId)
160                 throws RemoteException {
161             synchronized (mLock) {
162                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
163                 if (service != null && (isDefaultServiceLocked(userId)
164                         || isCalledByServiceAppLocked(userId, "getTranslationCapabilities"))) {
165                     service.onTranslationCapabilitiesRequestLocked(sourceFormat, targetFormat,
166                             receiver);
167                 } else {
168                     Slog.v(TAG, "onGetTranslationCapabilitiesLocked(): no service for " + userId);
169                     receiver.send(STATUS_SYNC_CALL_FAIL, null);
170                 }
171             }
172         }
173 
174         @Override
registerTranslationCapabilityCallback(IRemoteCallback callback, int userId)175         public void registerTranslationCapabilityCallback(IRemoteCallback callback, int userId) {
176             TranslationManagerServiceImpl service;
177             synchronized (mLock) {
178                 service = getServiceForUserLocked(userId);
179             }
180             if (service != null) {
181                 service.registerTranslationCapabilityCallback(callback, Binder.getCallingUid());
182             }
183         }
184 
185         @Override
unregisterTranslationCapabilityCallback(IRemoteCallback callback, int userId)186         public void unregisterTranslationCapabilityCallback(IRemoteCallback callback, int userId) {
187             TranslationManagerServiceImpl service;
188             synchronized (mLock) {
189                 service = getServiceForUserLocked(userId);
190             }
191             if (service != null) {
192                 service.unregisterTranslationCapabilityCallback(callback);
193             }
194         }
195 
196         @Override
onSessionCreated(TranslationContext translationContext, int sessionId, IResultReceiver receiver, int userId)197         public void onSessionCreated(TranslationContext translationContext,
198                 int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
199             synchronized (mLock) {
200                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
201                 if (service != null && (isDefaultServiceLocked(userId)
202                         || isCalledByServiceAppLocked(userId, "onSessionCreated"))) {
203                     service.onSessionCreatedLocked(translationContext, sessionId, receiver);
204                 } else {
205                     Slog.v(TAG, "onSessionCreated(): no service for " + userId);
206                     receiver.send(STATUS_SYNC_CALL_FAIL, null);
207                 }
208             }
209         }
210 
211         @Override
updateUiTranslationState(@iTranslationState int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds, IBinder token, int taskId, UiTranslationSpec uiTranslationSpec, int userId)212         public void updateUiTranslationState(@UiTranslationState int state,
213                 TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
214                 IBinder token, int taskId, UiTranslationSpec uiTranslationSpec, int userId) {
215             enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
216             synchronized (mLock) {
217                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
218                 if (service != null && (isDefaultServiceLocked(userId)
219                         || isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) {
220                     service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
221                             token, taskId, uiTranslationSpec);
222                 }
223             }
224         }
225 
226         @Override
registerUiTranslationStateCallback(IRemoteCallback callback, int userId)227         public void registerUiTranslationStateCallback(IRemoteCallback callback, int userId) {
228             synchronized (mLock) {
229                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
230                 if (service != null) {
231                     service.registerUiTranslationStateCallbackLocked(callback,
232                             Binder.getCallingUid());
233                 }
234             }
235         }
236 
237         @Override
unregisterUiTranslationStateCallback(IRemoteCallback callback, int userId)238         public void unregisterUiTranslationStateCallback(IRemoteCallback callback, int userId) {
239             TranslationManagerServiceImpl service;
240             synchronized (mLock) {
241                 service = getServiceForUserLocked(userId);
242             }
243             if (service != null) {
244                 service.unregisterUiTranslationStateCallback(callback);
245             }
246         }
247 
248         @Override
onTranslationFinished(boolean activityDestroyed, IBinder token, ComponentName componentName, int userId)249         public void onTranslationFinished(boolean activityDestroyed, IBinder token,
250                 ComponentName componentName, int userId) {
251             TranslationManagerServiceImpl service;
252             synchronized (mLock) {
253                 service = getServiceForUserLocked(userId);
254                 service.onTranslationFinishedLocked(activityDestroyed, token, componentName);
255             }
256         }
257 
258         @Override
getServiceSettingsActivity(IResultReceiver result, int userId)259         public void getServiceSettingsActivity(IResultReceiver result, int userId) {
260             final TranslationManagerServiceImpl service;
261             synchronized (mLock) {
262                 service = getServiceForUserLocked(userId);
263             }
264             if (service != null) {
265                 final ComponentName componentName = service.getServiceSettingsActivityLocked();
266                 if (componentName == null) {
267                     try {
268                         result.send(STATUS_SYNC_CALL_SUCCESS, null);
269                     } catch (RemoteException e) {
270                         Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
271                     }
272                 }
273                 final Intent intent = new Intent();
274                 intent.setComponent(componentName);
275                 final long identity = Binder.clearCallingIdentity();
276                 try {
277                     final PendingIntent pendingIntent =
278                             PendingIntent.getActivityAsUser(getContext(), 0, intent, FLAG_IMMUTABLE,
279                                     null, new UserHandle(userId));
280                     try {
281 
282                         result.send(STATUS_SYNC_CALL_SUCCESS, bundleFor(pendingIntent));
283                     } catch (RemoteException e) {
284                         Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
285                     }
286                 } finally {
287                     Binder.restoreCallingIdentity(identity);
288                 }
289             } else {
290                 try {
291                     result.send(STATUS_SYNC_CALL_FAIL, null);
292                 } catch (RemoteException e) {
293                     Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e);
294                 }
295             }
296         }
297 
298         /**
299          * Dump the service state into the given stream. You run "adb shell dumpsys translation".
300          */
301         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)302         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
303             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
304 
305             synchronized (mLock) {
306                 dumpLocked("", pw);
307                 final int userId = UserHandle.getCallingUserId();
308                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
309                 if (service != null) {
310                     service.dumpLocked("  ", fd, pw);
311                 }
312             }
313         }
314 
315         @Override
onShellCommand(@ullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver)316         public void onShellCommand(@Nullable FileDescriptor in,
317                 @Nullable FileDescriptor out,
318                 @Nullable FileDescriptor err,
319                 @NonNull String[] args,
320                 @Nullable ShellCallback callback,
321                 @NonNull ResultReceiver resultReceiver) throws RemoteException {
322             new TranslationManagerServiceShellCommand(
323                     TranslationManagerService.this).exec(this, in, out, err, args, callback,
324                     resultReceiver);
325         }
326     }
327 
328     @Override // from SystemService
onStart()329     public void onStart() {
330         publishBinderService(TRANSLATION_MANAGER_SERVICE,
331                 new TranslationManagerService.TranslationManagerServiceStub());
332     }
333 }
334