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.view.translation.TranslationManager.EXTRA_CAPABILITIES; 20 import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL; 21 import static android.view.translation.UiTranslationManager.EXTRA_PACKAGE_NAME; 22 import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE; 23 import static android.view.translation.UiTranslationManager.EXTRA_STATE; 24 import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE; 25 import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_FINISHED; 26 import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_PAUSED; 27 import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_RESUMED; 28 import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_STARTED; 29 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.annotation.SuppressLint; 33 import android.app.Activity; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.pm.PackageManager; 38 import android.content.pm.ResolveInfo; 39 import android.content.pm.ServiceInfo; 40 import android.os.Binder; 41 import android.os.Bundle; 42 import android.os.IBinder; 43 import android.os.IRemoteCallback; 44 import android.os.RemoteCallbackList; 45 import android.os.RemoteException; 46 import android.os.ResultReceiver; 47 import android.service.translation.TranslationService; 48 import android.service.translation.TranslationServiceInfo; 49 import android.util.ArrayMap; 50 import android.util.ArraySet; 51 import android.util.Log; 52 import android.util.Slog; 53 import android.view.autofill.AutofillId; 54 import android.view.inputmethod.InputMethodInfo; 55 import android.view.translation.ITranslationServiceCallback; 56 import android.view.translation.TranslationCapability; 57 import android.view.translation.TranslationContext; 58 import android.view.translation.TranslationSpec; 59 import android.view.translation.UiTranslationController; 60 import android.view.translation.UiTranslationManager.UiTranslationState; 61 import android.view.translation.UiTranslationSpec; 62 63 import com.android.internal.annotations.GuardedBy; 64 import com.android.internal.os.IResultReceiver; 65 import com.android.internal.os.TransferPipe; 66 import com.android.server.LocalServices; 67 import com.android.server.infra.AbstractPerUserSystemService; 68 import com.android.server.inputmethod.InputMethodManagerInternal; 69 import com.android.server.wm.ActivityTaskManagerInternal; 70 import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens; 71 72 import java.io.FileDescriptor; 73 import java.io.IOException; 74 import java.io.PrintWriter; 75 import java.lang.ref.WeakReference; 76 import java.util.List; 77 78 final class TranslationManagerServiceImpl extends 79 AbstractPerUserSystemService<TranslationManagerServiceImpl, TranslationManagerService> 80 implements IBinder.DeathRecipient { 81 82 private static final String TAG = "TranslationManagerServiceImpl"; 83 @SuppressLint("IsLoggableTagLength") 84 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 85 86 @GuardedBy("mLock") 87 @Nullable 88 private RemoteTranslationService mRemoteTranslationService; 89 90 @GuardedBy("mLock") 91 @Nullable 92 private ServiceInfo mRemoteTranslationServiceInfo; 93 94 @GuardedBy("mLock") 95 private TranslationServiceInfo mTranslationServiceInfo; 96 97 @GuardedBy("mLock") 98 private WeakReference<ActivityTokens> mLastActivityTokens; 99 100 private final ActivityTaskManagerInternal mActivityTaskManagerInternal; 101 102 private final TranslationServiceRemoteCallback mRemoteServiceCallback = 103 new TranslationServiceRemoteCallback(); 104 private final RemoteCallbackList<IRemoteCallback> mTranslationCapabilityCallbacks = 105 new RemoteCallbackList<>(); 106 private final ArraySet<IBinder> mWaitingFinishedCallbackActivities = new ArraySet<>(); 107 108 /** 109 * Key is translated activity token, value is the specification and state for the translation. 110 */ 111 @GuardedBy("mLock") 112 private final ArrayMap<IBinder, ActiveTranslation> mActiveTranslations = new ArrayMap<>(); 113 TranslationManagerServiceImpl( @onNull TranslationManagerService master, @NonNull Object lock, int userId, boolean disabled)114 protected TranslationManagerServiceImpl( 115 @NonNull TranslationManagerService master, 116 @NonNull Object lock, int userId, boolean disabled) { 117 super(master, lock, userId); 118 updateRemoteServiceLocked(); 119 mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); 120 } 121 122 @GuardedBy("mLock") 123 @Override // from PerUserSystemService newServiceInfoLocked(@onNull ComponentName serviceComponent)124 protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) 125 throws PackageManager.NameNotFoundException { 126 mTranslationServiceInfo = new TranslationServiceInfo(getContext(), 127 serviceComponent, isTemporaryServiceSetLocked(), mUserId); 128 mRemoteTranslationServiceInfo = mTranslationServiceInfo.getServiceInfo(); 129 return mTranslationServiceInfo.getServiceInfo(); 130 } 131 132 @GuardedBy("mLock") 133 @Override // from PerUserSystemService updateLocked(boolean disabled)134 protected boolean updateLocked(boolean disabled) { 135 final boolean enabledChanged = super.updateLocked(disabled); 136 updateRemoteServiceLocked(); 137 return enabledChanged; 138 } 139 140 /** 141 * Updates the reference to the remote service. 142 */ 143 @GuardedBy("mLock") updateRemoteServiceLocked()144 private void updateRemoteServiceLocked() { 145 if (mRemoteTranslationService != null) { 146 if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): destroying old remote service"); 147 mRemoteTranslationService.unbind(); 148 mRemoteTranslationService = null; 149 } 150 } 151 152 @GuardedBy("mLock") 153 @Nullable ensureRemoteServiceLocked()154 private RemoteTranslationService ensureRemoteServiceLocked() { 155 if (mRemoteTranslationService == null) { 156 final String serviceName = getComponentNameLocked(); 157 if (serviceName == null) { 158 if (mMaster.verbose) { 159 Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name."); 160 } 161 return null; 162 } 163 final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); 164 boolean isServiceAvailableForUser; 165 final long identity = Binder.clearCallingIdentity(); 166 try { 167 isServiceAvailableForUser = isServiceAvailableForUser(serviceComponent); 168 if (mMaster.verbose) { 169 Slog.v(TAG, "ensureRemoteServiceLocked(): isServiceAvailableForUser=" 170 + isServiceAvailableForUser); 171 } 172 } finally { 173 Binder.restoreCallingIdentity(identity); 174 } 175 if (!isServiceAvailableForUser) { 176 Slog.w(TAG, "ensureRemoteServiceLocked(): " + serviceComponent 177 + " is not available,"); 178 return null; 179 } 180 mRemoteTranslationService = new RemoteTranslationService(getContext(), serviceComponent, 181 mUserId, /* isInstantAllowed= */ false, mRemoteServiceCallback); 182 } 183 return mRemoteTranslationService; 184 } 185 isServiceAvailableForUser(ComponentName serviceComponent)186 private boolean isServiceAvailableForUser(ComponentName serviceComponent) { 187 Intent intent = new Intent(TranslationService.SERVICE_INTERFACE) 188 .setComponent(serviceComponent); 189 final ResolveInfo resolveInfo = getContext().getPackageManager().resolveServiceAsUser( 190 intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, mUserId); 191 return resolveInfo != null && resolveInfo.serviceInfo != null; 192 } 193 194 @GuardedBy("mLock") onTranslationCapabilitiesRequestLocked(@ranslationSpec.DataFormat int sourceFormat, @TranslationSpec.DataFormat int destFormat, @NonNull ResultReceiver resultReceiver)195 void onTranslationCapabilitiesRequestLocked(@TranslationSpec.DataFormat int sourceFormat, 196 @TranslationSpec.DataFormat int destFormat, 197 @NonNull ResultReceiver resultReceiver) { 198 final RemoteTranslationService remoteService = ensureRemoteServiceLocked(); 199 if (remoteService != null) { 200 remoteService.onTranslationCapabilitiesRequest(sourceFormat, destFormat, 201 resultReceiver); 202 } else { 203 Slog.v(TAG, "onTranslationCapabilitiesRequestLocked(): no remote service."); 204 resultReceiver.send(STATUS_SYNC_CALL_FAIL, null); 205 } 206 } 207 registerTranslationCapabilityCallback(IRemoteCallback callback, int sourceUid)208 public void registerTranslationCapabilityCallback(IRemoteCallback callback, int sourceUid) { 209 mTranslationCapabilityCallbacks.register(callback, sourceUid); 210 ensureRemoteServiceLocked(); 211 } 212 unregisterTranslationCapabilityCallback(IRemoteCallback callback)213 public void unregisterTranslationCapabilityCallback(IRemoteCallback callback) { 214 mTranslationCapabilityCallbacks.unregister(callback); 215 } 216 217 @GuardedBy("mLock") onSessionCreatedLocked(@onNull TranslationContext translationContext, int sessionId, IResultReceiver resultReceiver)218 void onSessionCreatedLocked(@NonNull TranslationContext translationContext, int sessionId, 219 IResultReceiver resultReceiver) throws RemoteException { 220 final RemoteTranslationService remoteService = ensureRemoteServiceLocked(); 221 if (remoteService != null) { 222 remoteService.onSessionCreated(translationContext, sessionId, resultReceiver); 223 } else { 224 Slog.v(TAG, "onSessionCreatedLocked(): no remote service."); 225 resultReceiver.send(STATUS_SYNC_CALL_FAIL, null); 226 } 227 } 228 getAppUidByComponentName(Context context, ComponentName componentName, int userId)229 private int getAppUidByComponentName(Context context, ComponentName componentName, int userId) { 230 int translatedAppUid = -1; 231 try { 232 if (componentName != null) { 233 translatedAppUid = context.getPackageManager().getApplicationInfoAsUser( 234 componentName.getPackageName(), 0, userId).uid; 235 } 236 } catch (PackageManager.NameNotFoundException e) { 237 Slog.d(TAG, "Cannot find packageManager for" + componentName); 238 } 239 return translatedAppUid; 240 } 241 242 @GuardedBy("mLock") onTranslationFinishedLocked(boolean activityDestroyed, IBinder token, ComponentName componentName)243 public void onTranslationFinishedLocked(boolean activityDestroyed, IBinder token, 244 ComponentName componentName) { 245 final int translatedAppUid = 246 getAppUidByComponentName(getContext(), componentName, getUserId()); 247 final String packageName = componentName.getPackageName(); 248 // In the Activity destroyed case, we only call onTranslationFinished() in 249 // non-finishTranslation() state. If there is a finishTranslation() call by apps, we 250 // should remove the waiting callback to avoid invoking callbacks twice. 251 if (activityDestroyed || mWaitingFinishedCallbackActivities.contains(token)) { 252 invokeCallbacks(STATE_UI_TRANSLATION_FINISHED, 253 /* sourceSpec= */ null, /* targetSpec= */ null, 254 packageName, translatedAppUid); 255 mWaitingFinishedCallbackActivities.remove(token); 256 mActiveTranslations.remove(token); 257 } 258 } 259 260 @GuardedBy("mLock") updateUiTranslationStateLocked(@iTranslationState int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds, IBinder token, int taskId, UiTranslationSpec uiTranslationSpec)261 public void updateUiTranslationStateLocked(@UiTranslationState int state, 262 TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds, 263 IBinder token, int taskId, UiTranslationSpec uiTranslationSpec) { 264 // If the app starts a new Activity in the same task then the finish or pause API 265 // is called, the operation doesn't work if we only check task top Activity. The top 266 // Activity is the new Activity, the original Activity is paused in the same task. 267 // To make sure the operation still work, we use the token to find the target Activity in 268 // this task, not the top Activity only. 269 // 270 // Note: getAttachedNonFinishingActivityForTask() takes the shareable activity token. We 271 // call this method so that we can get the regular activity token below. 272 ActivityTokens candidateActivityTokens = 273 mActivityTaskManagerInternal.getAttachedNonFinishingActivityForTask(taskId, token); 274 if (candidateActivityTokens == null) { 275 Slog.w(TAG, "Unknown activity or it was finished to query for update " 276 + "translation state for token=" + token + " taskId=" + taskId + " for " 277 + "state= " + state); 278 return; 279 } 280 mLastActivityTokens = new WeakReference<>(candidateActivityTokens); 281 if (state == STATE_UI_TRANSLATION_FINISHED) { 282 mWaitingFinishedCallbackActivities.add(token); 283 } 284 IBinder activityToken = candidateActivityTokens.getActivityToken(); 285 try { 286 candidateActivityTokens.getApplicationThread().updateUiTranslationState( 287 activityToken, state, sourceSpec, targetSpec, 288 viewIds, uiTranslationSpec); 289 } catch (RemoteException e) { 290 Slog.w(TAG, "Update UiTranslationState fail: " + e); 291 } 292 293 ComponentName componentName = mActivityTaskManagerInternal.getActivityName(activityToken); 294 int translatedAppUid = 295 getAppUidByComponentName(getContext(), componentName, getUserId()); 296 String packageName = componentName.getPackageName(); 297 298 invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, token, 299 translatedAppUid); 300 updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, token, 301 translatedAppUid); 302 } 303 304 @GuardedBy("mLock") updateActiveTranslationsLocked(int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken, int translatedAppUid)305 private void updateActiveTranslationsLocked(int state, TranslationSpec sourceSpec, 306 TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken, 307 int translatedAppUid) { 308 // We keep track of active translations and their state so that we can: 309 // 1. Trigger callbacks that are registered after translation has started. 310 // See registerUiTranslationStateCallbackLocked(). 311 // 2. NOT trigger callbacks when the state didn't change. 312 // See invokeCallbacksIfNecessaryLocked(). 313 ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken); 314 switch (state) { 315 case STATE_UI_TRANSLATION_STARTED: { 316 if (activeTranslation == null) { 317 try { 318 shareableActivityToken.linkToDeath(this, /* flags= */ 0); 319 } catch (RemoteException e) { 320 Slog.w(TAG, "Failed to call linkToDeath for translated app with uid=" 321 + translatedAppUid + "; activity is already dead", e); 322 323 // Apps with registered callbacks were just notified that translation 324 // started. We should let them know translation is finished too. 325 invokeCallbacks(STATE_UI_TRANSLATION_FINISHED, sourceSpec, targetSpec, 326 packageName, translatedAppUid); 327 return; 328 } 329 mActiveTranslations.put(shareableActivityToken, 330 new ActiveTranslation(sourceSpec, targetSpec, translatedAppUid, 331 packageName)); 332 } 333 break; 334 } 335 336 case STATE_UI_TRANSLATION_PAUSED: { 337 if (activeTranslation != null) { 338 activeTranslation.isPaused = true; 339 } 340 break; 341 } 342 343 case STATE_UI_TRANSLATION_RESUMED: { 344 if (activeTranslation != null) { 345 activeTranslation.isPaused = false; 346 } 347 break; 348 } 349 350 case STATE_UI_TRANSLATION_FINISHED: { 351 if (activeTranslation != null) { 352 mActiveTranslations.remove(shareableActivityToken); 353 } 354 break; 355 } 356 } 357 358 if (DEBUG) { 359 Slog.d(TAG, 360 "Updating to translation state=" + state + " for app with uid=" 361 + translatedAppUid + " packageName=" + packageName); 362 } 363 } 364 365 @GuardedBy("mLock") invokeCallbacksIfNecessaryLocked(int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken, int translatedAppUid)366 private void invokeCallbacksIfNecessaryLocked(int state, TranslationSpec sourceSpec, 367 TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken, 368 int translatedAppUid) { 369 boolean shouldInvokeCallbacks = true; 370 int stateForCallbackInvocation = state; 371 372 ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken); 373 if (activeTranslation == null) { 374 if (state != STATE_UI_TRANSLATION_STARTED) { 375 shouldInvokeCallbacks = false; 376 Slog.w(TAG, 377 "Updating to translation state=" + state + " for app with uid=" 378 + translatedAppUid + " packageName=" + packageName 379 + " but no active translation was found for it"); 380 } 381 } else { 382 switch (state) { 383 case STATE_UI_TRANSLATION_STARTED: { 384 boolean specsAreIdentical = activeTranslation.sourceSpec.getLocale().equals( 385 sourceSpec.getLocale()) 386 && activeTranslation.targetSpec.getLocale().equals( 387 targetSpec.getLocale()); 388 if (specsAreIdentical) { 389 if (activeTranslation.isPaused) { 390 // Ideally UiTranslationManager.resumeTranslation() should be first 391 // used to resume translation, but for the purposes of invoking the 392 // callback, we want to call onResumed() instead of onStarted(). This 393 // way there can only be one call to onStarted() for the lifetime of 394 // a translated activity and this will simplify the number of states 395 // apps have to handle. 396 stateForCallbackInvocation = STATE_UI_TRANSLATION_RESUMED; 397 } else { 398 // Don't invoke callbacks if the state or specs didn't change. For a 399 // given activity, startTranslation() will be called every time there 400 // are new views to be translated, but we don't need to repeatedly 401 // notify apps about it. 402 shouldInvokeCallbacks = false; 403 } 404 } 405 break; 406 } 407 408 case STATE_UI_TRANSLATION_PAUSED: { 409 if (activeTranslation.isPaused) { 410 // Don't invoke callbacks if the state didn't change. 411 shouldInvokeCallbacks = false; 412 } 413 break; 414 } 415 416 case STATE_UI_TRANSLATION_RESUMED: { 417 if (!activeTranslation.isPaused) { 418 // Don't invoke callbacks if the state didn't change. Either 419 // resumeTranslation() was called consecutive times, or right after 420 // startTranslation(). The latter case shouldn't happen normally, so we 421 // don't want apps to have to handle that particular transition. 422 shouldInvokeCallbacks = false; 423 } 424 break; 425 } 426 427 case STATE_UI_TRANSLATION_FINISHED: { 428 // Note: Here finishTranslation() was called but we don't want to invoke 429 // onFinished() on the callbacks. They will be invoked when 430 // UiTranslationManager.onTranslationFinished() is called (see 431 // onTranslationFinishedLocked()). 432 shouldInvokeCallbacks = false; 433 break; 434 } 435 } 436 } 437 438 if (shouldInvokeCallbacks) { 439 invokeCallbacks(stateForCallbackInvocation, sourceSpec, targetSpec, packageName, 440 translatedAppUid); 441 } 442 } 443 444 @GuardedBy("mLock") dumpLocked(String prefix, FileDescriptor fd, PrintWriter pw)445 public void dumpLocked(String prefix, FileDescriptor fd, PrintWriter pw) { 446 if (mLastActivityTokens != null) { 447 ActivityTokens activityTokens = mLastActivityTokens.get(); 448 if (activityTokens == null) { 449 return; 450 } 451 try (TransferPipe tp = new TransferPipe()) { 452 activityTokens.getApplicationThread().dumpActivity(tp.getWriteFd(), 453 activityTokens.getActivityToken(), prefix, 454 new String[]{ 455 Activity.DUMP_ARG_DUMP_DUMPABLE, 456 UiTranslationController.DUMPABLE_NAME 457 }); 458 tp.go(fd); 459 } catch (IOException e) { 460 pw.println(prefix + "Failure while dumping the activity: " + e); 461 } catch (RemoteException e) { 462 pw.println(prefix + "Got a RemoteException while dumping the activity"); 463 } 464 } else { 465 pw.print(prefix); 466 pw.println("No requested UiTranslation Activity."); 467 } 468 final int waitingFinishCallbackSize = mWaitingFinishedCallbackActivities.size(); 469 if (waitingFinishCallbackSize > 0) { 470 pw.print(prefix); 471 pw.print("number waiting finish callback activities: "); 472 pw.println(waitingFinishCallbackSize); 473 for (IBinder activityToken : mWaitingFinishedCallbackActivities) { 474 pw.print(prefix); 475 pw.print("shareableActivityToken: "); 476 pw.println(activityToken); 477 } 478 } 479 } 480 invokeCallbacks( int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName, int translatedAppUid)481 private void invokeCallbacks( 482 int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName, 483 int translatedAppUid) { 484 Bundle result = createResultForCallback(state, sourceSpec, targetSpec, packageName); 485 int registeredCallbackCount = mCallbacks.getRegisteredCallbackCount(); 486 if (DEBUG) { 487 Slog.d(TAG, "Invoking " + registeredCallbackCount + " callbacks for translation state=" 488 + state + " for app with uid=" + translatedAppUid 489 + " packageName=" + packageName); 490 } 491 492 if (registeredCallbackCount == 0) { 493 return; 494 } 495 List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods(); 496 mCallbacks.broadcast((callback, uid) -> { 497 invokeCallback((int) uid, translatedAppUid, callback, result, enabledInputMethods); 498 }); 499 } 500 getEnabledInputMethods()501 private List<InputMethodInfo> getEnabledInputMethods() { 502 return LocalServices.getService(InputMethodManagerInternal.class) 503 .getEnabledInputMethodListAsUser(mUserId); 504 } 505 createResultForCallback( int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName)506 private Bundle createResultForCallback( 507 int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName) { 508 Bundle result = new Bundle(); 509 result.putInt(EXTRA_STATE, state); 510 // TODO(177500482): Store the locale pair so it can be sent for RESUME events. 511 if (sourceSpec != null) { 512 result.putSerializable(EXTRA_SOURCE_LOCALE, sourceSpec.getLocale()); 513 result.putSerializable(EXTRA_TARGET_LOCALE, targetSpec.getLocale()); 514 } 515 result.putString(EXTRA_PACKAGE_NAME, packageName); 516 return result; 517 } 518 invokeCallback( int callbackSourceUid, int translatedAppUid, IRemoteCallback callback, Bundle result, List<InputMethodInfo> enabledInputMethods)519 private void invokeCallback( 520 int callbackSourceUid, int translatedAppUid, IRemoteCallback callback, 521 Bundle result, List<InputMethodInfo> enabledInputMethods) { 522 if (callbackSourceUid == translatedAppUid) { 523 // Invoke callback for the application being translated. 524 try { 525 callback.sendResult(result); 526 } catch (RemoteException e) { 527 Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e); 528 } 529 return; 530 } 531 532 // TODO(177500482): Only support the *current* Input Method. 533 // Code here is non-optimal since it's temporary.. 534 boolean isIme = false; 535 for (InputMethodInfo inputMethod : enabledInputMethods) { 536 if (callbackSourceUid == inputMethod.getServiceInfo().applicationInfo.uid) { 537 isIme = true; 538 break; 539 } 540 } 541 542 if (!isIme) { 543 return; 544 } 545 try { 546 callback.sendResult(result); 547 } catch (RemoteException e) { 548 Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e); 549 } 550 } 551 552 @GuardedBy("mLock") registerUiTranslationStateCallbackLocked(IRemoteCallback callback, int sourceUid)553 public void registerUiTranslationStateCallbackLocked(IRemoteCallback callback, int sourceUid) { 554 mCallbacks.register(callback, sourceUid); 555 int numActiveTranslations = mActiveTranslations.size(); 556 Slog.i(TAG, "New registered callback for sourceUid=" + sourceUid + " with currently " 557 + numActiveTranslations + " active translations"); 558 if (numActiveTranslations == 0) { 559 return; 560 } 561 562 // Trigger the callback for already active translations. 563 List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods(); 564 for (int i = 0; i < mActiveTranslations.size(); i++) { 565 ActiveTranslation activeTranslation = mActiveTranslations.valueAt(i); 566 int translatedAppUid = activeTranslation.translatedAppUid; 567 String packageName = activeTranslation.packageName; 568 if (DEBUG) { 569 Slog.d(TAG, "Triggering callback for sourceUid=" + sourceUid 570 + " for translated app with uid=" + translatedAppUid 571 + "packageName=" + packageName + " isPaused=" + activeTranslation.isPaused); 572 } 573 574 Bundle startedResult = createResultForCallback(STATE_UI_TRANSLATION_STARTED, 575 activeTranslation.sourceSpec, activeTranslation.targetSpec, 576 packageName); 577 invokeCallback(sourceUid, translatedAppUid, callback, startedResult, 578 enabledInputMethods); 579 if (activeTranslation.isPaused) { 580 // Also send event so callback owners know that translation was started then paused. 581 Bundle pausedResult = createResultForCallback(STATE_UI_TRANSLATION_PAUSED, 582 activeTranslation.sourceSpec, activeTranslation.targetSpec, 583 packageName); 584 invokeCallback(sourceUid, translatedAppUid, callback, pausedResult, 585 enabledInputMethods); 586 } 587 } 588 } 589 unregisterUiTranslationStateCallback(IRemoteCallback callback)590 public void unregisterUiTranslationStateCallback(IRemoteCallback callback) { 591 mCallbacks.unregister(callback); 592 } 593 594 private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>(); 595 getServiceSettingsActivityLocked()596 public ComponentName getServiceSettingsActivityLocked() { 597 if (mTranslationServiceInfo == null) { 598 return null; 599 } 600 final String activityName = mTranslationServiceInfo.getSettingsActivity(); 601 if (activityName == null) { 602 return null; 603 } 604 final String packageName = mTranslationServiceInfo.getServiceInfo().packageName; 605 return new ComponentName(packageName, activityName); 606 } 607 notifyClientsTranslationCapability(TranslationCapability capability)608 private void notifyClientsTranslationCapability(TranslationCapability capability) { 609 final Bundle res = new Bundle(); 610 res.putParcelable(EXTRA_CAPABILITIES, capability); 611 mTranslationCapabilityCallbacks.broadcast((callback, uid) -> { 612 try { 613 callback.sendResult(res); 614 } catch (RemoteException e) { 615 Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e); 616 } 617 }); 618 } 619 620 private final class TranslationServiceRemoteCallback extends 621 ITranslationServiceCallback.Stub { 622 623 @Override updateTranslationCapability(TranslationCapability capability)624 public void updateTranslationCapability(TranslationCapability capability) { 625 if (capability == null) { 626 Slog.wtf(TAG, "received a null TranslationCapability from TranslationService."); 627 return; 628 } 629 notifyClientsTranslationCapability(capability); 630 } 631 } 632 633 private static final class ActiveTranslation { 634 public final TranslationSpec sourceSpec; 635 public final TranslationSpec targetSpec; 636 public final String packageName; 637 public final int translatedAppUid; 638 public boolean isPaused = false; 639 ActiveTranslation(TranslationSpec sourceSpec, TranslationSpec targetSpec, int translatedAppUid, String packageName)640 private ActiveTranslation(TranslationSpec sourceSpec, TranslationSpec targetSpec, 641 int translatedAppUid, String packageName) { 642 this.sourceSpec = sourceSpec; 643 this.targetSpec = targetSpec; 644 this.translatedAppUid = translatedAppUid; 645 this.packageName = packageName; 646 } 647 } 648 649 @Override binderDied()650 public void binderDied() { 651 // Don't need to implement this with binderDied(IBinder) implemented. 652 } 653 654 @Override binderDied(IBinder who)655 public void binderDied(IBinder who) { 656 synchronized (mLock) { 657 mWaitingFinishedCallbackActivities.remove(who); 658 ActiveTranslation activeTranslation = mActiveTranslations.remove(who); 659 if (activeTranslation != null) { 660 // Let apps with registered callbacks know about the activity's death. 661 invokeCallbacks(STATE_UI_TRANSLATION_FINISHED, activeTranslation.sourceSpec, 662 activeTranslation.targetSpec, activeTranslation.packageName, 663 activeTranslation.translatedAppUid); 664 } 665 } 666 } 667 } 668