From ce9903f1ed54d167d66751ea0bb60a30ff3be71d Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Fri, 21 May 2021 21:42:02 +0800 Subject: [PATCH] Do not bind TextClassifierService if the packge doesn't install. Currently, TCMS will try to bind TCS even the package doesn't install for certain user. If there are a lot of API calls come, we may call bindService() many times, this may cause system stability problem. We received the system reboot problem before. We already have preload TextClassifierService in ExtServices on primary user but it's possible the TextClassifierService doesn't install on the profile user or users unintall package. It's possible the package may not be installed on non-primary user. Users also can disable TextClassifierService from ui interface easily, it's possible occur on both primary and non-primary users, system should also not bind TextClassifierService in this case. In this change, system will avoid trying to call bindService() if the TCS deosn't install or disabled to minimize binder calls. Bug: 188825683 Bug: 170014504 Test: atest CtsTextClassifierTestCases Test: manual. Create work profile, turn on/off it and reboot to check the status. Disable app from Settings and check the status. Change-Id: Ifa2bccc0d2435712461067d650b52cb02a903c59 --- .../TextClassificationManagerService.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 753b42b245565..0f37450c24c97 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -25,6 +25,8 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; @@ -59,6 +61,7 @@ import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageMonitor; import com.android.internal.util.DumpUtils; import com.android.internal.util.FunctionalUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; @@ -110,6 +113,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi try { publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mManagerService); mManagerService.startListenSettings(); + mManagerService.startTrackingPackageChanges(); } catch (Throwable t) { // Starting this service is not critical to the running of this device and should // therefore not crash the device. If it fails, log the error and continue. @@ -119,11 +123,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi @Override public void onUserStarting(@NonNull TargetUser user) { + updatePackageStateForUser(user.getUserIdentifier()); processAnyPendingWork(user.getUserIdentifier()); } @Override public void onUserUnlocking(@NonNull TargetUser user) { + // refresh if we failed earlier due to locked encrypted user + updatePackageStateForUser(user.getUserIdentifier()); // Rebind if we failed earlier due to locked encrypted user processAnyPendingWork(user.getUserIdentifier()); } @@ -134,6 +141,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + private void updatePackageStateForUser(int userId) { + synchronized (mManagerService.mLock) { + // Update the cached disable status, the TextClassfier may not be direct boot aware, + // we should update the disable status after user unlock + mManagerService.getUserStateLocked(userId).updatePackageStateLocked(); + } + } + @Override public void onUserStopping(@NonNull TargetUser user) { int userId = user.getUserIdentifier(); @@ -160,6 +175,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final String mDefaultTextClassifierPackage; @Nullable private final String mSystemTextClassifierPackage; + // TODO: consider using device config to control it. + private boolean DEBUG = false; private TextClassificationManagerService(Context context) { mContext = Objects.requireNonNull(context); @@ -176,6 +193,46 @@ public final class TextClassificationManagerService extends ITextClassifierServi mSettingsListener.registerObserver(); } + void startTrackingPackageChanges() { + final PackageMonitor monitor = new PackageMonitor() { + + @Override + public void onPackageAdded(String packageName, int uid) { + notifyPackageInstallStatusChange(packageName, /* installed*/ true); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + notifyPackageInstallStatusChange(packageName, /* installed= */ false); + } + + @Override + public void onPackageModified(String packageName) { + final int userId = getChangingUserId(); + synchronized (mLock) { + final UserState userState = getUserStateLocked(userId); + final ServiceState serviceState = userState.getServiceStateLocked(packageName); + if (serviceState != null) { + serviceState.onPackageModifiedLocked(); + } + } + } + + private void notifyPackageInstallStatusChange(String packageName, boolean installed) { + final int userId = getChangingUserId(); + synchronized (mLock) { + final UserState userState = getUserStateLocked(userId); + final ServiceState serviceState = userState.getServiceStateLocked(packageName); + if (serviceState != null) { + serviceState.onPackageInstallStatusChangeLocked(installed); + } + } + } + }; + + monitor.register(mContext, null, UserHandle.ALL, true); + } + @Override public void onConnectedStateChanged(@ConnectionState int connected) { } @@ -452,6 +509,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi if (serviceState == null) { Slog.d(LOG_TAG, "No configured system TextClassifierService"); callback.onFailure(); + } else if (!serviceState.isInstalledLocked() || !serviceState.isEnabledLocked()) { + if (DEBUG) { + Slog.d(LOG_TAG, + serviceState.mPackageName + " is not available in user " + userId + + ". Installed: " + serviceState.isInstalledLocked() + + ", enabled:" + serviceState.isEnabledLocked()); + } + callback.onFailure(); } else if (attemptToBind && !serviceState.bindLocked()) { Slog.d(LOG_TAG, "Unable to bind TextClassifierService at " + methodName); callback.onFailure(); @@ -761,6 +826,24 @@ public final class TextClassificationManagerService extends ITextClassifierServi return serviceStates; } + @GuardedBy("mLock") + @Nullable + private ServiceState getServiceStateLocked(String packageName) { + for (ServiceState serviceState : getAllServiceStatesLocked()) { + if (serviceState.mPackageName.equals(packageName)) { + return serviceState; + } + } + return null; + } + + @GuardedBy("mLock") + private void updatePackageStateLocked() { + for (ServiceState serviceState : getAllServiceStatesLocked()) { + serviceState.updatePackageStateLocked(); + } + } + void dump(IndentingPrintWriter pw) { synchronized (mLock) { pw.increaseIndent(); @@ -814,6 +897,10 @@ public final class TextClassificationManagerService extends ITextClassifierServi ComponentName mBoundComponentName = null; @GuardedBy("mLock") int mBoundServiceUid = Process.INVALID_UID; + @GuardedBy("mLock") + boolean mInstalled; + @GuardedBy("mLock") + boolean mEnabled; private ServiceState( @UserIdInt int userId, @NonNull String packageName, boolean isTrusted) { @@ -822,6 +909,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi mConnection = new TextClassifierServiceConnection(mUserId); mIsTrusted = isTrusted; mBindServiceFlags = createBindServiceFlags(packageName); + mInstalled = isPackageInstalledForUser(); + mEnabled = isServiceEnabledForUser(); } @Context.BindServiceFlags @@ -833,6 +922,54 @@ public final class TextClassificationManagerService extends ITextClassifierServi return flags; } + private boolean isPackageInstalledForUser() { + try { + PackageManager packageManager = mContext.getPackageManager(); + return packageManager.getPackageInfoAsUser(mPackageName, 0, mUserId) != null; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + private boolean isServiceEnabledForUser() { + PackageManager packageManager = mContext.getPackageManager(); + Intent intent = new Intent(TextClassifierService.SERVICE_INTERFACE); + intent.setPackage(mPackageName); + ResolveInfo resolveInfo = packageManager.resolveServiceAsUser(intent, + PackageManager.GET_SERVICES, mUserId); + ServiceInfo serviceInfo = resolveInfo == null ? null : resolveInfo.serviceInfo; + return serviceInfo != null; + } + + @GuardedBy("mLock") + @NonNull + private void onPackageInstallStatusChangeLocked(boolean installed) { + mInstalled = installed; + } + + @GuardedBy("mLock") + @NonNull + private void onPackageModifiedLocked() { + mEnabled = isServiceEnabledForUser(); + } + + @GuardedBy("mLock") + @NonNull + private void updatePackageStateLocked() { + mInstalled = isPackageInstalledForUser(); + mEnabled = isServiceEnabledForUser(); + } + + @GuardedBy("mLock") + boolean isInstalledLocked() { + return mInstalled; + } + + @GuardedBy("mLock") + boolean isEnabledLocked() { + return mEnabled; + } + @GuardedBy("mLock") boolean isBoundLocked() { return mService != null; @@ -923,6 +1060,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.printPair("userId", mUserId); synchronized (mLock) { pw.printPair("packageName", mPackageName); + pw.printPair("installed", mInstalled); + pw.printPair("enabled", mEnabled); pw.printPair("boundComponentName", mBoundComponentName); pw.printPair("isTrusted", mIsTrusted); pw.printPair("bindServiceFlags", mBindServiceFlags);