blob: a30d77e4d446c4ad4d6b79157d2f95fd0f1fea2b [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 */
16package android.view.intelligence;
17
18import android.annotation.NonNull;
19import android.annotation.Nullable;
20import android.annotation.SystemApi;
Felipe Lemee348dc32018-11-05 12:35:29 -080021import android.annotation.SystemService;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070022import android.content.ComponentName;
23import android.content.Context;
Felipe Lemee348dc32018-11-05 12:35:29 -080024import android.os.Bundle;
25import android.os.IBinder;
26import android.os.RemoteException;
Felipe Leme7a534082018-11-05 15:03:04 -080027import android.os.SystemClock;
Felipe Lemee348dc32018-11-05 12:35:29 -080028import android.util.Log;
Felipe Leme7a534082018-11-05 15:03:04 -080029import android.view.intelligence.ContentCaptureEvent.EventType;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070030
Felipe Lemee348dc32018-11-05 12:35:29 -080031import com.android.internal.annotations.GuardedBy;
32import com.android.internal.os.IResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070033import com.android.internal.util.Preconditions;
34
Felipe Lemee348dc32018-11-05 12:35:29 -080035import java.io.PrintWriter;
Felipe Leme7a534082018-11-05 15:03:04 -080036import java.util.Arrays;
37import java.util.List;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070038import java.util.Set;
39
40/**
Felipe Lemee348dc32018-11-05 12:35:29 -080041 * TODO(b/111276913): add javadocs / implement
Felipe Leme1dfa9a02018-10-17 17:24:37 -070042 */
Felipe Lemee348dc32018-11-05 12:35:29 -080043@SystemService(Context.INTELLIGENCE_MANAGER_SERVICE)
Felipe Leme1dfa9a02018-10-17 17:24:37 -070044public final class IntelligenceManager {
45
Felipe Lemee348dc32018-11-05 12:35:29 -080046 private static final String TAG = "IntelligenceManager";
47
48 // TODO(b/111276913): define a way to dynamically set it (for example, using settings?)
49 private static final boolean VERBOSE = false;
50
Felipe Leme1dfa9a02018-10-17 17:24:37 -070051 /**
52 * Used to indicate that a text change was caused by user input (for example, through IME).
53 */
54 //TODO(b/111276913): link to notifyTextChanged() method once available
55 public static final int FLAG_USER_INPUT = 0x1;
56
Felipe Leme1dfa9a02018-10-17 17:24:37 -070057
58 /** @hide */
Felipe Lemee348dc32018-11-05 12:35:29 -080059 public static final int NO_SESSION = 0;
60
61 /**
62 * Initial state, when there is no session.
63 *
64 * @hide
65 */
66 public static final int STATE_UNKNOWN = 0;
67
68 /**
69 * Service's startSession() was called, but remote session id was not returned yet.
70 *
71 * @hide
72 */
73 public static final int STATE_WAITING_FOR_SESSION_ID = 1;
74
75 /**
76 * Session is active.
77 *
78 * @hide
79 */
80 public static final int STATE_ACTIVE = 2;
81
82 private static int sNextSessionId;
83
84 private final Context mContext;
85
86 @Nullable
87 private final IIntelligenceManager mService;
88
89 private final Object mLock = new Object();
90
91 // TODO(b/111276913): localSessionId might be an overkill, perhaps just the global id is enough.
92 // Let's keep both for now, and revisit once we decide whether the session id will be persisted
93 // when the activity's process is killed
94 @GuardedBy("mLock")
95 private int mLocalSessionId = NO_SESSION;
96
97 @GuardedBy("mLock")
98 private int mRemoteSessionId = NO_SESSION;
99
100 @GuardedBy("mLock")
101 private int mState = STATE_UNKNOWN;
102
103 @GuardedBy("mLock")
104 private IBinder mApplicationToken;
105
106 // TODO(b/111276913): replace by an interface name implemented by Activity, similar to
107 // AutofillClient
108 @GuardedBy("mLock")
109 private ComponentName mComponentName;
110
111 /** @hide */
112 public IntelligenceManager(@NonNull Context context, @Nullable IIntelligenceManager service) {
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700113 mContext = Preconditions.checkNotNull(context, "context cannot be null");
Felipe Lemee348dc32018-11-05 12:35:29 -0800114 mService = service;
115 }
116
117 /** @hide */
118 public void onActivityCreated(@NonNull IBinder token, @NonNull ComponentName componentName) {
119 if (!isContentCaptureEnabled()) return;
120
121 synchronized (mLock) {
122 if (mState != STATE_UNKNOWN) {
123 Log.w(TAG, "ignoring onActivityStarted(" + token + ") while on state "
124 + getStateAsStringLocked());
125 return;
126 }
127 mState = STATE_WAITING_FOR_SESSION_ID;
128 mLocalSessionId = ++sNextSessionId;
129 mRemoteSessionId = NO_SESSION;
130 mApplicationToken = token;
131 mComponentName = componentName;
132
133 if (VERBOSE) {
134 Log.v(TAG, "onActivityStarted(): token=" + token + ", act=" + componentName
135 + ", localSessionId=" + mLocalSessionId);
136 }
137 final int flags = 0; // TODO(b/111276913): get proper flags
138
139 try {
140 mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
141 mLocalSessionId, flags, new IResultReceiver.Stub() {
142 @Override
143 public void send(int resultCode, Bundle resultData)
144 throws RemoteException {
145 synchronized (mLock) {
146 if (resultCode > 0) {
147 mRemoteSessionId = resultCode;
148 mState = STATE_ACTIVE;
149 } else {
150 // TODO(b/111276913): handle other cases like disabled by
151 // service
152 mState = STATE_UNKNOWN;
153 }
154 if (VERBOSE) {
155 Log.v(TAG, "onActivityStarted() result: code=" + resultCode
156 + ", remoteSession=" + mRemoteSessionId
157 + ", state=" + getStateAsStringLocked());
158 }
159 }
160 }
161 });
162 } catch (RemoteException e) {
163 throw e.rethrowFromSystemServer();
164 }
165 }
166 }
167
Felipe Leme7a534082018-11-05 15:03:04 -0800168 /**
169 * Used for intermediate events (i.e, other than created and destroyed).
170 *
171 * @hide
172 */
173 public void onActivityLifecycleEvent(@EventType int type) {
174 if (!isContentCaptureEnabled()) return;
175
176 //TODO(b/111276913): should buffer event (and call service on handler thread), instead of
177 // calling right away
178 final ContentCaptureEvent event = new ContentCaptureEvent(type, SystemClock.uptimeMillis(),
179 0);
180 final List<ContentCaptureEvent> events = Arrays.asList(event);
181
182 synchronized (mLock) {
183 //TODO(b/111276913): check session state; for example, how to handle if it's waiting for
184 // remote id
185
186 if (VERBOSE) {
187 Log.v(TAG, "onActivityLifecycleEvent() for " + mComponentName.flattenToShortString()
188 + ": " + ContentCaptureEvent.getTypeAsString(type));
189 }
190
191 try {
192 mService.sendEvents(mContext.getUserId(), mApplicationToken, mComponentName,
193 mLocalSessionId, mRemoteSessionId, events);
194 } catch (RemoteException e) {
195 throw e.rethrowFromSystemServer();
196 }
197 }
198 }
199
Felipe Lemee348dc32018-11-05 12:35:29 -0800200 /** @hide */
201 public void onActivityDestroyed() {
202 if (!isContentCaptureEnabled()) return;
203
204 synchronized (mLock) {
205 //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
206 // id) and send it to the cache of batched commands
207
208 if (VERBOSE) {
209 Log.v(TAG, "onActivityDestroyed(): state=" + getStateAsStringLocked()
210 + ", localSessionId=" + mLocalSessionId
211 + ", mRemoteSessionId=" + mRemoteSessionId);
212 }
213
214 try {
215 mService.finishSession(mContext.getUserId(), mApplicationToken, mComponentName,
216 mLocalSessionId, mRemoteSessionId);
217 mState = STATE_UNKNOWN;
218 mLocalSessionId = mRemoteSessionId = NO_SESSION;
219 mApplicationToken = null;
220 mComponentName = null;
221 } catch (RemoteException e) {
222 throw e.rethrowFromSystemServer();
223 }
224 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700225 }
226
227 /**
Felipe Lemef783fa02018-11-05 14:57:32 -0800228 * Returns the component name of the {@code android.service.intelligence.IntelligenceService}
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700229 * that is enabled for the current user.
230 */
231 @Nullable
232 public ComponentName getIntelligenceServiceComponentName() {
233 //TODO(b/111276913): implement
234 return null;
235 }
236
237 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800238 * Checks whether content capture is enabled for this activity.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700239 */
240 public boolean isContentCaptureEnabled() {
Felipe Lemee348dc32018-11-05 12:35:29 -0800241 //TODO(b/111276913): properly implement by checking if it was explicitly disabled by
242 // service, or if service is not set
243 // (and probably renamign to isEnabledLocked()
244 return mService != null;
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700245 }
246
247 /**
248 * Called by apps to disable content capture.
249 *
250 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
251 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
252 */
253 public void disableContentCapture() {
Felipe Lemee348dc32018-11-05 12:35:29 -0800254 //TODO(b/111276913): implement
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700255 }
256
257 /**
258 * Called by the the service {@link android.service.intelligence.IntelligenceService}
259 * to define whether content capture should be enabled for activities with such
260 * {@link android.content.ComponentName}.
261 *
262 * <p>Useful to blacklist a particular activity.
263 *
264 * @throws UnsupportedOperationException if not called by the UID that owns the
265 * {@link android.service.intelligence.IntelligenceService} associated with the
266 * current user.
267 *
268 * @hide
269 */
270 @SystemApi
271 public void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
272 boolean enabled) {
273 //TODO(b/111276913): implement
274 }
275
276 /**
277 * Called by the the service {@link android.service.intelligence.IntelligenceService}
278 * to define whether content capture should be enabled for activities of the app with such
279 * {@code packageName}.
280 *
281 * <p>Useful to blacklist any activity from a particular app.
282 *
283 * @throws UnsupportedOperationException if not called by the UID that owns the
284 * {@link android.service.intelligence.IntelligenceService} associated with the
285 * current user.
286 *
287 * @hide
288 */
289 @SystemApi
290 public void setPackageContentCaptureEnabled(@NonNull String packageName, boolean enabled) {
291 //TODO(b/111276913): implement
292 }
293
294 /**
295 * Gets the activities where content capture was disabled by
296 * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}.
297 *
298 * @throws UnsupportedOperationException if not called by the UID that owns the
299 * {@link android.service.intelligence.IntelligenceService} associated with the
300 * current user.
301 *
302 * @hide
303 */
304 @SystemApi
305 @NonNull
306 public Set<ComponentName> getContentCaptureDisabledActivities() {
307 //TODO(b/111276913): implement
308 return null;
309 }
310
311 /**
312 * Gets the apps where content capture was disabled by
313 * {@link #setPackageContentCaptureEnabled(String, boolean)}.
314 *
315 * @throws UnsupportedOperationException if not called by the UID that owns the
316 * {@link android.service.intelligence.IntelligenceService} associated with the
317 * current user.
318 *
319 * @hide
320 */
321 @SystemApi
322 @NonNull
323 public Set<String> getContentCaptureDisabledPackages() {
324 //TODO(b/111276913): implement
325 return null;
326 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800327
328 /** @hide */
329 public void dump(String prefix, PrintWriter pw) {
330 pw.print(prefix); pw.println("IntelligenceManager");
331 final String prefix2 = prefix + " ";
332 synchronized (mLock) {
333 pw.print(prefix2); pw.print("mContext: "); pw.println(mContext);
334 pw.print(prefix2); pw.print("mService: "); pw.println(mService);
335 pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
336 pw.print(prefix2); pw.print("enabled: "); pw.println(isContentCaptureEnabled());
337 pw.print(prefix2); pw.print("mLocalSessionId: "); pw.println(mLocalSessionId);
338 pw.print(prefix2); pw.print("mRemoteSessionId: "); pw.println(mRemoteSessionId);
339 pw.print(prefix2); pw.print("mState: "); pw.print(mState); pw.print(" (");
340 pw.print(getStateAsStringLocked()); pw.println(")");
341 pw.print(prefix2); pw.print("mAppToken: "); pw.println(mApplicationToken);
342 pw.print(prefix2); pw.print("mComponentName: "); pw.println(mComponentName);
343 }
344 }
345
346 @GuardedBy("mLock")
347 private String getStateAsStringLocked() {
348 return getStateAsString(mState);
349 }
350
351 @NonNull
352 private static String getStateAsString(int state) {
353 switch (state) {
354 case STATE_UNKNOWN:
355 return "UNKNOWN";
356 case STATE_WAITING_FOR_SESSION_ID:
357 return "WAITING_FOR_SESSION_ID";
358 case STATE_ACTIVE:
359 return "ACTIVE";
360 default:
361 return "INVALID:" + state;
362 }
363 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700364}