blob: d7f1b9f2c3e136c16dfbe5a7ccf003e8fb27d212 [file] [log] [blame]
Felipe Leme1dfa9a02018-10-17 17:24:37 -07001/*
2 * Copyright (C) 2018 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 */
Felipe Leme749b8892018-12-03 16:30:30 -080016package android.view.contentcapture;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070017
Felipe Lemebe002d82019-01-23 10:22:32 -080018import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
19
Perumaal Saddabba2019-01-04 16:43:35 -080020import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
21
Felipe Leme1dfa9a02018-10-17 17:24:37 -070022import android.annotation.NonNull;
23import android.annotation.Nullable;
Felipe Lemee348dc32018-11-05 12:35:29 -080024import android.annotation.SystemService;
Felipe Leme87a9dc92018-12-18 14:28:07 -080025import android.annotation.UiThread;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070026import android.content.ComponentName;
27import android.content.Context;
Felipe Leme88eae3b2018-11-07 15:11:56 -080028import android.os.Handler;
Felipe Lemee348dc32018-11-05 12:35:29 -080029import android.os.IBinder;
Felipe Leme34ccedf2019-01-17 13:42:35 -080030import android.os.Looper;
Perumaal Saddabba2019-01-04 16:43:35 -080031import android.os.RemoteException;
Felipe Lemee348dc32018-11-05 12:35:29 -080032import android.util.Log;
Felipe Leme1af85ea2019-01-16 13:23:40 -080033import android.view.contentcapture.ContentCaptureSession.FlushReason;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070034
Adam He6079d152019-01-10 11:37:17 -080035import com.android.internal.annotations.GuardedBy;
Perumaal Saddabba2019-01-04 16:43:35 -080036import com.android.internal.os.IResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070037import com.android.internal.util.Preconditions;
Perumaal Saddabba2019-01-04 16:43:35 -080038import com.android.internal.util.SyncResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070039
Felipe Lemee348dc32018-11-05 12:35:29 -080040import java.io.PrintWriter;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070041
Felipe Lemeb18e3172018-11-27 10:33:41 -080042/*
43 * NOTE: all methods in this class should return right away, or do the real work in a handler
44 * thread.
45 *
46 * Hence, the only field that must be thread-safe is mEnabled, which is called at the beginning
47 * of every method.
48 */
Felipe Lemeecb08be2018-11-27 15:48:47 -080049/**
50 * TODO(b/111276913): add javadocs / implement
51 */
52@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
53public final class ContentCaptureManager {
Felipe Leme1dfa9a02018-10-17 17:24:37 -070054
Felipe Leme749b8892018-12-03 16:30:30 -080055 private static final String TAG = ContentCaptureManager.class.getSimpleName();
Felipe Lemee348dc32018-11-05 12:35:29 -080056
Perumaal Saddabba2019-01-04 16:43:35 -080057 /**
58 * Timeout for calls to system_server.
59 */
60 private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
61
Adam He6079d152019-01-10 11:37:17 -080062 private final Object mLock = new Object();
63
64 @GuardedBy("mLock")
65 private boolean mDisabled;
Felipe Lemeb18e3172018-11-27 10:33:41 -080066
67 @NonNull
Felipe Lemee348dc32018-11-05 12:35:29 -080068 private final Context mContext;
69
70 @Nullable
Felipe Leme749b8892018-12-03 16:30:30 -080071 private final IContentCaptureManager mService;
Felipe Lemee348dc32018-11-05 12:35:29 -080072
Adam He6079d152019-01-10 11:37:17 -080073 // Flags used for starting session.
74 @GuardedBy("mLock")
75 private int mFlags;
76
Felipe Lemeaa5088e2018-12-10 14:53:58 -080077 // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
Felipe Lemeb18e3172018-11-27 10:33:41 -080078 // held at the Application level
Felipe Lemeaa5088e2018-12-10 14:53:58 -080079 @NonNull
Felipe Leme88eae3b2018-11-07 15:11:56 -080080 private final Handler mHandler;
81
Adam He6079d152019-01-10 11:37:17 -080082 @GuardedBy("mLock")
Felipe Leme87a9dc92018-12-18 14:28:07 -080083 private MainContentCaptureSession mMainSession;
Felipe Leme4017b202018-12-10 12:13:31 -080084
Felipe Lemee348dc32018-11-05 12:35:29 -080085 /** @hide */
Felipe Leme749b8892018-12-03 16:30:30 -080086 public ContentCaptureManager(@NonNull Context context,
87 @Nullable IContentCaptureManager service) {
Felipe Leme1dfa9a02018-10-17 17:24:37 -070088 mContext = Preconditions.checkNotNull(context, "context cannot be null");
Felipe Leme34ccedf2019-01-17 13:42:35 -080089 if (VERBOSE) Log.v(TAG, "Constructor for " + context.getPackageName());
Felipe Lemee348dc32018-11-05 12:35:29 -080090
Felipe Leme34ccedf2019-01-17 13:42:35 -080091 mService = service;
92 // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we
93 // do, then we should optimize it to run the tests after the Choreographer finishes the most
94 // important steps of the frame.
95 mHandler = Handler.createAsync(Looper.getMainLooper());
Felipe Leme88eae3b2018-11-07 15:11:56 -080096 }
97
Felipe Leme7a534082018-11-05 15:03:04 -080098 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -080099 * Gets the main session associated with the context.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800100 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800101 * <p>By default there's just one (associated with the activity lifecycle), but apps could
Felipe Leme87a9dc92018-12-18 14:28:07 -0800102 * explicitly add more using
103 * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800104 *
105 * @hide
106 */
107 @NonNull
Felipe Leme87a9dc92018-12-18 14:28:07 -0800108 @UiThread
109 public MainContentCaptureSession getMainContentCaptureSession() {
Adam He6079d152019-01-10 11:37:17 -0800110 synchronized (mLock) {
111 if (mMainSession == null) {
112 mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
Felipe Leme01b87492019-01-15 13:26:52 -0800113 mDisabled);
Adam He6079d152019-01-10 11:37:17 -0800114 if (VERBOSE) {
115 Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
116 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800117 }
Adam He6079d152019-01-10 11:37:17 -0800118 return mMainSession;
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800119 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800120 }
121
122 /** @hide */
123 public void onActivityStarted(@NonNull IBinder applicationToken,
Adam He328c0e32019-01-03 15:19:22 -0800124 @NonNull ComponentName activityComponent, int flags) {
Adam He6079d152019-01-10 11:37:17 -0800125 synchronized (mLock) {
126 mFlags |= flags;
127 getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags);
128 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800129 }
130
131 /** @hide */
132 public void onActivityStopped() {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800133 getMainContentCaptureSession().destroy();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800134 }
135
136 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800137 * Flushes the content of all sessions.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800138 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800139 * <p>Typically called by {@code Activity} when it's paused / resumed.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800140 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800141 * @hide
Felipe Leme88eae3b2018-11-07 15:11:56 -0800142 */
Felipe Leme1af85ea2019-01-16 13:23:40 -0800143 public void flush(@FlushReason int reason) {
144 getMainContentCaptureSession().flush(reason);
Felipe Leme88eae3b2018-11-07 15:11:56 -0800145 }
146
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700147 /**
Felipe Lemeecb08be2018-11-27 15:48:47 -0800148 * Returns the component name of the system service that is consuming the captured events for
149 * the current user.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700150 */
151 @Nullable
Felipe Lemeecb08be2018-11-27 15:48:47 -0800152 public ComponentName getServiceComponentName() {
Perumaal Saddabba2019-01-04 16:43:35 -0800153 if (!isContentCaptureEnabled()) {
154 return null;
155 }
156 // Wait for system server to return the component name.
157 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
158 mHandler.sendMessage(obtainMessage(
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800159 ContentCaptureManager::handleGetComponentName, this, resultReceiver));
Perumaal Saddabba2019-01-04 16:43:35 -0800160
161 try {
162 return resultReceiver.getParcelableResult();
163 } catch (RemoteException e) {
164 // Unable to retrieve component name in a reasonable amount of time.
165 throw e.rethrowFromSystemServer();
166 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700167 }
168
169 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800170 * Checks whether content capture is enabled for this activity.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700171 */
172 public boolean isContentCaptureEnabled() {
Adam He6079d152019-01-10 11:37:17 -0800173 synchronized (mLock) {
174 return mService != null && !mDisabled;
175 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700176 }
177
178 /**
Felipe Leme284ad1c2018-11-15 18:16:12 -0800179 * Called by apps to explicitly enable or disable content capture.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700180 *
181 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
182 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
183 */
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800184 public void setContentCaptureEnabled(boolean enabled) {
Adam He6079d152019-01-10 11:37:17 -0800185 synchronized (mLock) {
186 mFlags |= enabled ? 0 : ContentCaptureContext.FLAG_DISABLED_BY_APP;
187 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800188 }
189
190 /**
Adam He3d0409b2019-01-15 14:22:04 -0800191 * Called by the app to request the Content Capture service to remove user-data associated with
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800192 * some context.
193 *
194 * @param request object specifying what user data should be removed.
195 */
196 public void removeUserData(@NonNull UserDataRemovalRequest request) {
Adam He3d0409b2019-01-15 14:22:04 -0800197 Preconditions.checkNotNull(request);
198
199 try {
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800200 mService.removeUserData(request);
Adam He3d0409b2019-01-15 14:22:04 -0800201 } catch (RemoteException e) {
202 e.rethrowFromSystemServer();
203 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700204 }
205
Felipe Lemee348dc32018-11-05 12:35:29 -0800206 /** @hide */
207 public void dump(String prefix, PrintWriter pw) {
Adam He6079d152019-01-10 11:37:17 -0800208 synchronized (mLock) {
209 pw.print(prefix); pw.println("ContentCaptureManager");
210 pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
211 pw.print(prefix); pw.print("Context: "); pw.println(mContext);
212 pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
213 if (mService != null) {
214 pw.print(prefix); pw.print("Service: "); pw.println(mService);
215 }
216 pw.print(prefix); pw.print("Flags: "); pw.println(mFlags);
217 if (mMainSession != null) {
218 final String prefix2 = prefix + " ";
219 pw.print(prefix); pw.println("Main session:");
220 mMainSession.dump(prefix2, pw);
221 } else {
222 pw.print(prefix); pw.println("No sessions");
223 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800224 }
225 }
Perumaal Saddabba2019-01-04 16:43:35 -0800226
227
228 /** Retrieves the component name of the target content capture service through system_server. */
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800229 private void handleGetComponentName(@NonNull IResultReceiver resultReceiver) {
Perumaal Saddabba2019-01-04 16:43:35 -0800230 try {
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800231 mService.getServiceComponentName(resultReceiver);
Perumaal Saddabba2019-01-04 16:43:35 -0800232 } catch (RemoteException e) {
233 Log.w(TAG, "Unable to retrieve service component name: " + e);
234 }
235 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700236}