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