blob: 55e66cab10262489ddc0cef362fc9d87d19413c3 [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
Perumaal Saddabba2019-01-04 16:43:35 -080018import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
19
Felipe Leme1dfa9a02018-10-17 17:24:37 -070020import android.annotation.NonNull;
21import android.annotation.Nullable;
Felipe Lemee348dc32018-11-05 12:35:29 -080022import android.annotation.SystemService;
Felipe Leme87a9dc92018-12-18 14:28:07 -080023import android.annotation.UiThread;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070024import android.content.ComponentName;
25import android.content.Context;
Felipe Leme88eae3b2018-11-07 15:11:56 -080026import android.os.Handler;
27import android.os.HandlerThread;
Felipe Lemee348dc32018-11-05 12:35:29 -080028import android.os.IBinder;
Perumaal Saddabba2019-01-04 16:43:35 -080029import android.os.RemoteException;
Felipe Lemee348dc32018-11-05 12:35:29 -080030import android.util.Log;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070031
Adam He6079d152019-01-10 11:37:17 -080032import com.android.internal.annotations.GuardedBy;
Perumaal Saddabba2019-01-04 16:43:35 -080033import com.android.internal.os.IResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070034import com.android.internal.util.Preconditions;
Perumaal Saddabba2019-01-04 16:43:35 -080035import com.android.internal.util.SyncResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070036
Felipe Lemee348dc32018-11-05 12:35:29 -080037import java.io.PrintWriter;
Felipe Lemeb18e3172018-11-27 10:33:41 -080038import java.util.concurrent.atomic.AtomicBoolean;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070039
Felipe Lemeb18e3172018-11-27 10:33:41 -080040/*
41 * NOTE: all methods in this class should return right away, or do the real work in a handler
42 * thread.
43 *
44 * Hence, the only field that must be thread-safe is mEnabled, which is called at the beginning
45 * of every method.
46 */
Felipe Lemeecb08be2018-11-27 15:48:47 -080047/**
48 * TODO(b/111276913): add javadocs / implement
49 */
50@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
51public final class ContentCaptureManager {
Felipe Leme1dfa9a02018-10-17 17:24:37 -070052
Felipe Leme749b8892018-12-03 16:30:30 -080053 private static final String TAG = ContentCaptureManager.class.getSimpleName();
Felipe Lemee348dc32018-11-05 12:35:29 -080054
Felipe Leme88eae3b2018-11-07 15:11:56 -080055 private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
56
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
Felipe Lemeaa5088e2018-12-10 14:53:58 -080062 // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
63 static final boolean VERBOSE = false;
64 static final boolean DEBUG = true; // STOPSHIP if not set to false
Felipe Leme4017b202018-12-10 12:13:31 -080065
Adam He6079d152019-01-10 11:37:17 -080066 private final Object mLock = new Object();
67
68 @GuardedBy("mLock")
69 private boolean mDisabled;
Felipe Lemeb18e3172018-11-27 10:33:41 -080070
71 @NonNull
Felipe Lemee348dc32018-11-05 12:35:29 -080072 private final Context mContext;
73
74 @Nullable
Felipe Leme749b8892018-12-03 16:30:30 -080075 private final IContentCaptureManager mService;
Felipe Lemee348dc32018-11-05 12:35:29 -080076
Adam He6079d152019-01-10 11:37:17 -080077 // Flags used for starting session.
78 @GuardedBy("mLock")
79 private int mFlags;
80
Felipe Lemeaa5088e2018-12-10 14:53:58 -080081 // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
Felipe Lemeb18e3172018-11-27 10:33:41 -080082 // held at the Application level
Felipe Lemeaa5088e2018-12-10 14:53:58 -080083 @NonNull
Felipe Leme88eae3b2018-11-07 15:11:56 -080084 private final Handler mHandler;
85
Adam He6079d152019-01-10 11:37:17 -080086 @GuardedBy("mLock")
Felipe Leme87a9dc92018-12-18 14:28:07 -080087 private MainContentCaptureSession mMainSession;
Felipe Leme4017b202018-12-10 12:13:31 -080088
Felipe Lemee348dc32018-11-05 12:35:29 -080089 /** @hide */
Felipe Leme749b8892018-12-03 16:30:30 -080090 public ContentCaptureManager(@NonNull Context context,
91 @Nullable IContentCaptureManager service) {
Felipe Leme1dfa9a02018-10-17 17:24:37 -070092 mContext = Preconditions.checkNotNull(context, "context cannot be null");
Felipe Lemeb18e3172018-11-27 10:33:41 -080093 if (VERBOSE) {
94 Log.v(TAG, "Constructor for " + context.getPackageName());
95 }
Felipe Lemee348dc32018-11-05 12:35:29 -080096 mService = service;
Felipe Lemeaa5088e2018-12-10 14:53:58 -080097 // TODO(b/119220549): use an existing bg thread instead...
Felipe Leme88eae3b2018-11-07 15:11:56 -080098 final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
99 bgThread.start();
100 mHandler = Handler.createAsync(bgThread.getLooper());
Felipe Lemee348dc32018-11-05 12:35:29 -0800101 }
102
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800103 @NonNull
104 private static Handler newHandler() {
105 // TODO(b/119220549): use an existing bg thread instead...
106 // TODO(b/119220549): use UI Thread directly (as calls are one-way) or an existing bgThread
107 // or a shared thread / handler held at the Application level
108 final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
109 bgThread.start();
110 return Handler.createAsync(bgThread.getLooper());
Felipe Leme88eae3b2018-11-07 15:11:56 -0800111 }
112
Felipe Leme7a534082018-11-05 15:03:04 -0800113 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800114 * Gets the main session associated with the context.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800115 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800116 * <p>By default there's just one (associated with the activity lifecycle), but apps could
Felipe Leme87a9dc92018-12-18 14:28:07 -0800117 * explicitly add more using
118 * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800119 *
120 * @hide
121 */
122 @NonNull
Felipe Leme87a9dc92018-12-18 14:28:07 -0800123 @UiThread
124 public MainContentCaptureSession getMainContentCaptureSession() {
Adam He6079d152019-01-10 11:37:17 -0800125 synchronized (mLock) {
126 if (mMainSession == null) {
127 mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
128 new AtomicBoolean(mDisabled));
129 if (VERBOSE) {
130 Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
131 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800132 }
Adam He6079d152019-01-10 11:37:17 -0800133 return mMainSession;
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800134 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800135 }
136
137 /** @hide */
138 public void onActivityStarted(@NonNull IBinder applicationToken,
Adam He328c0e32019-01-03 15:19:22 -0800139 @NonNull ComponentName activityComponent, int flags) {
Adam He6079d152019-01-10 11:37:17 -0800140 synchronized (mLock) {
141 mFlags |= flags;
142 getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags);
143 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800144 }
145
146 /** @hide */
147 public void onActivityStopped() {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800148 getMainContentCaptureSession().destroy();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800149 }
150
151 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800152 * Flushes the content of all sessions.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800153 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800154 * <p>Typically called by {@code Activity} when it's paused / resumed.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800155 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800156 * @hide
Felipe Leme88eae3b2018-11-07 15:11:56 -0800157 */
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800158 public void flush() {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800159 getMainContentCaptureSession().flush();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800160 }
161
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700162 /**
Felipe Lemeecb08be2018-11-27 15:48:47 -0800163 * Returns the component name of the system service that is consuming the captured events for
164 * the current user.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700165 */
166 @Nullable
Felipe Lemeecb08be2018-11-27 15:48:47 -0800167 public ComponentName getServiceComponentName() {
Perumaal Saddabba2019-01-04 16:43:35 -0800168 if (!isContentCaptureEnabled()) {
169 return null;
170 }
171 // Wait for system server to return the component name.
172 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
173 mHandler.sendMessage(obtainMessage(
174 ContentCaptureManager::handleReceiverServiceComponentName,
175 this, mContext.getUserId(), resultReceiver));
176
177 try {
178 return resultReceiver.getParcelableResult();
179 } catch (RemoteException e) {
180 // Unable to retrieve component name in a reasonable amount of time.
181 throw e.rethrowFromSystemServer();
182 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700183 }
184
185 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800186 * Checks whether content capture is enabled for this activity.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700187 */
188 public boolean isContentCaptureEnabled() {
Adam He6079d152019-01-10 11:37:17 -0800189 synchronized (mLock) {
190 return mService != null && !mDisabled;
191 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700192 }
193
194 /**
Felipe Leme284ad1c2018-11-15 18:16:12 -0800195 * Called by apps to explicitly enable or disable content capture.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700196 *
197 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
198 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
199 */
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800200 public void setContentCaptureEnabled(boolean enabled) {
Adam He6079d152019-01-10 11:37:17 -0800201 synchronized (mLock) {
202 mFlags |= enabled ? 0 : ContentCaptureContext.FLAG_DISABLED_BY_APP;
203 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800204 }
205
206 /**
207 * Called by the ap to request the Content Capture service to remove user-data associated with
208 * some context.
209 *
210 * @param request object specifying what user data should be removed.
211 */
212 public void removeUserData(@NonNull UserDataRemovalRequest request) {
Felipe Lemee348dc32018-11-05 12:35:29 -0800213 //TODO(b/111276913): implement
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700214 }
215
Felipe Lemee348dc32018-11-05 12:35:29 -0800216 /** @hide */
217 public void dump(String prefix, PrintWriter pw) {
Adam He6079d152019-01-10 11:37:17 -0800218 synchronized (mLock) {
219 pw.print(prefix); pw.println("ContentCaptureManager");
220 pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
221 pw.print(prefix); pw.print("Context: "); pw.println(mContext);
222 pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
223 if (mService != null) {
224 pw.print(prefix); pw.print("Service: "); pw.println(mService);
225 }
226 pw.print(prefix); pw.print("Flags: "); pw.println(mFlags);
227 if (mMainSession != null) {
228 final String prefix2 = prefix + " ";
229 pw.print(prefix); pw.println("Main session:");
230 mMainSession.dump(prefix2, pw);
231 } else {
232 pw.print(prefix); pw.println("No sessions");
233 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800234 }
235 }
Perumaal Saddabba2019-01-04 16:43:35 -0800236
237
238 /** Retrieves the component name of the target content capture service through system_server. */
239 private void handleReceiverServiceComponentName(int userId, IResultReceiver resultReceiver) {
240 try {
241 mService.getReceiverServiceComponentName(userId, resultReceiver);
242 } catch (RemoteException e) {
243 Log.w(TAG, "Unable to retrieve service component name: " + e);
244 }
245 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700246}