diff options
| author | 2021-03-04 02:26:25 +0000 | |
|---|---|---|
| committer | 2021-03-04 02:26:25 +0000 | |
| commit | d77366e4f2e208ad7faa8f0e43d5af7c6231ad96 (patch) | |
| tree | 4809e09c3da2c3bd3b69afe8a31915fafba8dbff | |
| parent | 880fdaf4cff227695877e14f9b33088d55ad8d93 (diff) | |
| parent | 842dd79ec5b575947f003ccd5bdc63c188115634 (diff) | |
Merge "Propagate ContentCaptureManager allowlist update to activities" into sc-dev
7 files changed, 220 insertions, 2 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 730fce9449d0..e7751b861037 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -176,6 +176,8 @@ import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; +import android.view.contentcapture.IContentCaptureManager; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.view.translation.TranslationSpec; import android.webkit.WebView; import android.window.SplashScreen; @@ -512,6 +514,8 @@ public final class ActivityThread extends ClientTransactionHandler { boolean mHasImeComponent = false; + private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null; + /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */ public static final class ActivityClientRecord { @UnsupportedAppUsage @@ -1939,6 +1943,7 @@ public final class ActivityThread extends ClientTransactionHandler { public static final int PURGE_RESOURCES = 161; public static final int ATTACH_STARTUP_AGENTS = 162; public static final int UPDATE_UI_TRANSLATION_STATE = 163; + public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164; public static final int INSTRUMENT_WITHOUT_RESTART = 170; public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171; @@ -1988,6 +1993,8 @@ public final class ActivityThread extends ClientTransactionHandler { case PURGE_RESOURCES: return "PURGE_RESOURCES"; case ATTACH_STARTUP_AGENTS: return "ATTACH_STARTUP_AGENTS"; case UPDATE_UI_TRANSLATION_STATE: return "UPDATE_UI_TRANSLATION_STATE"; + case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK: + return "SET_CONTENT_CAPTURE_OPTIONS_CALLBACK"; case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART"; case FINISH_INSTRUMENTATION_WITHOUT_RESTART: return "FINISH_INSTRUMENTATION_WITHOUT_RESTART"; @@ -2180,6 +2187,9 @@ public final class ActivityThread extends ClientTransactionHandler { (TranslationSpec) args.arg3, (TranslationSpec) args.arg4, (List<AutofillId>) args.arg5); break; + case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK: + handleSetContentCaptureOptionsCallback((String) msg.obj); + break; case INSTRUMENT_WITHOUT_RESTART: handleInstrumentWithoutRestart((AppBindData) msg.obj); break; @@ -6795,6 +6805,7 @@ public final class ActivityThread extends ClientTransactionHandler { // Propagate Content Capture options app.setContentCaptureOptions(data.contentCaptureOptions); + sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName); mInitialApplication = app; @@ -6856,6 +6867,36 @@ public final class ActivityThread extends ClientTransactionHandler { } } + private void handleSetContentCaptureOptionsCallback(String packageName) { + if (mContentCaptureOptionsCallback != null) { + return; + } + + IBinder b = ServiceManager.getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE); + if (b == null) { + return; + } + + IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(b); + mContentCaptureOptionsCallback = new IContentCaptureOptionsCallback.Stub() { + @Override + public void setContentCaptureOptions(ContentCaptureOptions options) + throws RemoteException { + if (mInitialApplication != null) { + mInitialApplication.setContentCaptureOptions(options); + } + } + }; + try { + service.registerContentCaptureOptionsCallback(packageName, + mContentCaptureOptionsCallback); + } catch (RemoteException e) { + Slog.w(TAG, "registerContentCaptureOptionsCallback() failed: " + + packageName, e); + mContentCaptureOptionsCallback = null; + } + } + private void handleInstrumentWithoutRestart(AppBindData data) { try { data.compatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index b1b443f919d9..a64111069c9b 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -22,6 +22,7 @@ import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IDataShareWriteAdapter; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.os.IBinder; import android.os.ICancellationSignal; @@ -101,4 +102,10 @@ oneway interface IContentCaptureManager { * Sets whether the default service should be used. */ void setDefaultServiceEnabled(int userId, boolean enabled); + + /** + * Registers a listener to handle updates ContentCaptureOptions from server. + */ + void registerContentCaptureOptionsCallback(String packageName, + in IContentCaptureOptionsCallback callback); } diff --git a/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl b/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl new file mode 100644 index 000000000000..b0f062de3bb9 --- /dev/null +++ b/core/java/android/view/contentcapture/IContentCaptureOptionsCallback.aidl @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.contentcapture; + +import android.content.ContentCaptureOptions; + +/** + * Callback for changes to content capture options made by ContentCaptureService. + * Callback interface used by IContentCaptureManager to send asynchronous + * notifications back to its clients. Note that this is a + * one-way interface so the server does not block waiting for the client. + * + * @hide + */ +oneway interface IContentCaptureOptionsCallback { + void setContentCaptureOptions(in ContentCaptureOptions options); +} diff --git a/core/java/com/android/internal/infra/GlobalWhitelistState.java b/core/java/com/android/internal/infra/GlobalWhitelistState.java index 3c081e27305c..7529536de66b 100644 --- a/core/java/com/android/internal/infra/GlobalWhitelistState.java +++ b/core/java/com/android/internal/infra/GlobalWhitelistState.java @@ -99,6 +99,18 @@ public class GlobalWhitelistState { } /** + * Gets packages that are either entirely allowlisted or have components that are allowlisted + * for the given user. + */ + public ArraySet<String> getWhitelistedPackages(@UserIdInt int userId) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) return null; + final WhitelistHelper helper = mWhitelisterHelpers.get(userId); + return helper == null ? null : helper.getWhitelistedPackages(); + } + } + + /** * Resets the allowlist for the given user. */ public void resetWhitelist(@NonNull int userId) { diff --git a/core/java/com/android/internal/infra/WhitelistHelper.java b/core/java/com/android/internal/infra/WhitelistHelper.java index 1d76090f59f3..3e93106822a2 100644 --- a/core/java/com/android/internal/infra/WhitelistHelper.java +++ b/core/java/com/android/internal/infra/WhitelistHelper.java @@ -140,6 +140,15 @@ public final class WhitelistHelper { return mWhitelistedPackages == null ? null : mWhitelistedPackages.get(packageName); } + /** + * Returns a set of all packages that are either entirely allowlisted or have components that + * are allowlisted. + */ + @Nullable + public ArraySet<String> getWhitelistedPackages() { + return mWhitelistedPackages == null ? null : new ArraySet<>(mWhitelistedPackages.keySet()); + } + @Override public String toString() { return "WhitelistHelper[" + mWhitelistedPackages + ']'; diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 02930dc238ba..f4a8ccd184e5 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -69,6 +69,7 @@ import android.provider.Settings; import android.service.contentcapture.ActivityEvent.ActivityEventType; import android.service.contentcapture.IDataShareCallback; import android.service.contentcapture.IDataShareReadAdapter; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.LocalLog; import android.util.Pair; @@ -81,6 +82,7 @@ import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.DataRemovalRequest; import android.view.contentcapture.DataShareRequest; import android.view.contentcapture.IContentCaptureManager; +import android.view.contentcapture.IContentCaptureOptionsCallback; import android.view.contentcapture.IDataShareWriteAdapter; import com.android.internal.annotations.GuardedBy; @@ -134,6 +136,9 @@ public final class ContentCaptureManagerService extends private final LocalService mLocalService = new LocalService(); + private final ContentCaptureManagerServiceStub mContentCaptureManagerServiceStub = + new ContentCaptureManagerServiceStub(); + @Nullable final LocalLog mRequestsHistory; @@ -224,8 +229,7 @@ public final class ContentCaptureManagerService extends @Override // from SystemService public void onStart() { - publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, - new ContentCaptureManagerServiceStub()); + publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, mContentCaptureManagerServiceStub); publishLocalService(ContentCaptureManagerInternal.class, mLocalService); } @@ -492,6 +496,19 @@ public final class ContentCaptureManagerService extends } } + void updateOptions(String packageName, ContentCaptureOptions options) { + ArraySet<CallbackRecord> records; + synchronized (mLock) { + records = mContentCaptureManagerServiceStub.mCallbacks.get(packageName); + if (records != null) { + int N = records.size(); + for (int i = 0; i < N; i++) { + records.valueAt(i).setContentCaptureOptions(options); + } + } + } + } + private ActivityManagerInternal getAmInternal() { synchronized (mLock) { if (mAm == null) { @@ -599,6 +616,8 @@ public final class ContentCaptureManagerService extends } final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { + @GuardedBy("mLock") + private final ArrayMap<String, ArraySet<CallbackRecord>> mCallbacks = new ArrayMap<>(); @Override public void startSession(@NonNull IBinder activityToken, @@ -755,6 +774,46 @@ public final class ContentCaptureManagerService extends } @Override + public void registerContentCaptureOptionsCallback(@NonNull String packageName, + IContentCaptureOptionsCallback callback) { + assertCalledByPackageOwner(packageName); + + CallbackRecord record = new CallbackRecord(callback, packageName); + record.registerObserver(); + + synchronized (mLock) { + ArraySet<CallbackRecord> records = mCallbacks.get(packageName); + if (records == null) { + records = new ArraySet<>(); + } + records.add(record); + mCallbacks.put(packageName, records); + } + + // Set options here in case it was updated before this was registered. + final int userId = UserHandle.getCallingUserId(); + final ContentCaptureOptions options = mGlobalContentCaptureOptions.getOptions(userId, + packageName); + if (options != null) { + record.setContentCaptureOptions(options); + } + } + + private void unregisterContentCaptureOptionsCallback(CallbackRecord record) { + synchronized (mLock) { + ArraySet<CallbackRecord> records = mCallbacks.get(record.mPackageName); + if (records != null) { + records.remove(record); + } + + if (records == null || records.isEmpty()) { + mCallbacks.remove(record.mPackageName); + } + } + record.unregisterObserver(); + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; @@ -1218,4 +1277,39 @@ public final class ContentCaptureManagerService extends mDataShareRequest.getPackageName()); } } + + private final class CallbackRecord implements IBinder.DeathRecipient { + private final String mPackageName; + private final IContentCaptureOptionsCallback mCallback; + + private CallbackRecord(IContentCaptureOptionsCallback callback, String packageName) { + mCallback = callback; + mPackageName = packageName; + } + + private void setContentCaptureOptions(ContentCaptureOptions options) { + try { + mCallback.setContentCaptureOptions(options); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to send setContentCaptureOptions(): " + e); + } + } + + private void registerObserver() { + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to register callback cleanup " + e); + } + } + + private void unregisterObserver() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + mContentCaptureManagerServiceStub.unregisterContentCaptureOptionsCallback(this); + } + } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 53cdc330cf9e..225a8d48114b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -597,9 +597,15 @@ final class ContentCapturePerUserService ? "null_activities" : activities.size() + " activities") + ")" + " for user " + mUserId); } + + ArraySet<String> oldList = + mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId); + mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); writeSetWhitelistEvent(getServiceComponentName(), packages, activities); + updateContentCaptureOptions(oldList); + // Must disable session that are not the allowlist anymore... final int numSessions = mSessions.size(); if (numSessions <= 0) return; @@ -671,5 +677,23 @@ final class ContentCapturePerUserService ContentCaptureMetricsLogger.writeSessionFlush(sessionId, getServiceComponentName(), app, flushMetrics, options, flushReason); } + + /** Updates {@link ContentCaptureOptions} for all newly added packages on allowlist. */ + private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) { + ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions + .getWhitelistedPackages(mUserId); + + if (oldList != null && adding != null) { + adding.removeAll(oldList); + } + + int N = adding != null ? adding.size() : 0; + for (int i = 0; i < N; i++) { + String packageName = adding.valueAt(i); + ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions + .getOptions(mUserId, packageName); + mMaster.updateOptions(packageName, options); + } + } } } |