blob: a3e65496bcd01dc7a2e28c0c8fec9d4f3e25ce87 [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 Lemed32d8f6f2019-02-15 10:25:33 -080018import static android.view.contentcapture.ContentCaptureHelper.sDebug;
19import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
Felipe Lemebe002d82019-01-23 10:22:32 -080020
Felipe Lemed32d8f6f2019-02-15 10:25:33 -080021import android.annotation.IntDef;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070022import android.annotation.NonNull;
23import android.annotation.Nullable;
Felipe Leme91ddeca2019-01-24 18:01:58 -080024import android.annotation.SystemApi;
Felipe Lemee348dc32018-11-05 12:35:29 -080025import android.annotation.SystemService;
Felipe Leme19652c02019-02-04 13:01:29 -080026import android.annotation.TestApi;
Felipe Leme87a9dc92018-12-18 14:28:07 -080027import android.annotation.UiThread;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070028import android.content.ComponentName;
Felipe Leme326f15a2019-02-19 09:42:24 -080029import android.content.ContentCaptureOptions;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070030import android.content.Context;
Felipe Leme88eae3b2018-11-07 15:11:56 -080031import android.os.Handler;
Felipe Lemee348dc32018-11-05 12:35:29 -080032import android.os.IBinder;
Felipe Leme34ccedf2019-01-17 13:42:35 -080033import android.os.Looper;
Perumaal Saddabba2019-01-04 16:43:35 -080034import android.os.RemoteException;
Felipe Lemee348dc32018-11-05 12:35:29 -080035import android.util.Log;
Felipe Leme1af85ea2019-01-16 13:23:40 -080036import android.view.contentcapture.ContentCaptureSession.FlushReason;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070037
Adam He6079d152019-01-10 11:37:17 -080038import com.android.internal.annotations.GuardedBy;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070039import com.android.internal.util.Preconditions;
Perumaal Saddabba2019-01-04 16:43:35 -080040import com.android.internal.util.SyncResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070041
Felipe Lemee348dc32018-11-05 12:35:29 -080042import java.io.PrintWriter;
Felipe Lemed32d8f6f2019-02-15 10:25:33 -080043import java.lang.annotation.Retention;
44import java.lang.annotation.RetentionPolicy;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070045
Felipe Lemeecb08be2018-11-27 15:48:47 -080046/**
Felipe Lemed49d52c2019-02-15 09:48:20 -080047 * TODO(b/123577059): add javadocs / mention it can be null
Felipe Lemeecb08be2018-11-27 15:48:47 -080048 */
49@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
50public final class ContentCaptureManager {
Felipe Leme1dfa9a02018-10-17 17:24:37 -070051
Felipe Leme749b8892018-12-03 16:30:30 -080052 private static final String TAG = ContentCaptureManager.class.getSimpleName();
Felipe Lemee348dc32018-11-05 12:35:29 -080053
Felipe Lemebb0c2a22019-01-25 17:29:29 -080054 /** @hide */
55 public static final int RESULT_CODE_TRUE = 1;
56 /** @hide */
57 public static final int RESULT_CODE_FALSE = 2;
58 /** @hide */
59 public static final int RESULT_CODE_NOT_SERVICE = -1;
60
Perumaal Saddabba2019-01-04 16:43:35 -080061 /**
62 * Timeout for calls to system_server.
63 */
64 private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
65
Felipe Leme14ef4612019-02-07 12:24:38 -080066 /**
67 * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide
68 * whether the Content Capture service should be created or not
69 *
70 * <p>By default it should *NOT* be set (or set to {@code "default"}, so the decision is based
71 * on whether the OEM provides an implementation for the service), but it can be overridden to:
72 *
73 * <ul>
74 * <li>Provide a "kill switch" so OEMs can disable it remotely in case of emergency (when
75 * it's set to {@code "false"}).
76 * <li>Enable the CTS tests to be run on AOSP builds (when it's set to {@code "true"}).
77 * </ul>
78 *
79 * @hide
80 */
81 @TestApi
82 public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED =
83 "service_explicitly_enabled";
84
Felipe Lemed32d8f6f2019-02-15 10:25:33 -080085 /**
86 * Maximum number of events that are buffered before sent to the app.
87 *
88 * @hide
89 */
90 @TestApi
91 public static final String DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE = "max_buffer_size";
92
93 /**
94 * Frequency (in ms) of buffer flushes when no events are received.
95 *
96 * @hide
97 */
98 @TestApi
99 public static final String DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY = "idle_flush_frequency";
100
101 /**
102 * Frequency (in ms) of buffer flushes when no events are received and the last one was a
103 * text change event.
104 *
105 * @hide
106 */
107 @TestApi
108 public static final String DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY =
109 "text_change_flush_frequency";
110
111 /**
112 * Size of events that are logging on {@code dump}.
113 *
114 * <p>Set it to {@code 0} or less to disable history.
115 *
116 * @hide
117 */
118 @TestApi
119 public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size";
120
121 /**
122 * Sets the logging level for {@code logcat} statements.
123 *
124 * <p>Valid values are: {@link #LOGGING_LEVEL_OFF}, {@value #LOGGING_LEVEL_DEBUG}, and
125 * {@link #LOGGING_LEVEL_VERBOSE}.
126 *
127 * @hide
128 */
129 @TestApi
130 public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level";
131
Felipe Lemee764fa22019-02-21 16:45:35 -0800132 /**
133 * Sets how long (in ms) the service is bound while idle.
134 *
135 * <p>Use {@code 0} to keep it permanently bound.
136 *
137 * @hide
138 */
139 public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout";
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800140
141 /** @hide */
142 @TestApi
143 public static final int LOGGING_LEVEL_OFF = 0;
144
145 /** @hide */
146 @TestApi
147 public static final int LOGGING_LEVEL_DEBUG = 1;
148
149 /** @hide */
150 @TestApi
151 public static final int LOGGING_LEVEL_VERBOSE = 2;
152
153 /** @hide */
154 @IntDef(flag = false, value = {
155 LOGGING_LEVEL_OFF,
156 LOGGING_LEVEL_DEBUG,
157 LOGGING_LEVEL_VERBOSE
158 })
159 @Retention(RetentionPolicy.SOURCE)
160 public @interface LoggingLevel {}
161
Felipe Leme326f15a2019-02-19 09:42:24 -0800162
163 /** @hide */
164 public static final int DEFAULT_MAX_BUFFER_SIZE = 100;
165 /** @hide */
166 public static final int DEFAULT_IDLE_FLUSHING_FREQUENCY_MS = 5_000;
167 /** @hide */
168 public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
169 /** @hide */
170 public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
171
Adam He6079d152019-01-10 11:37:17 -0800172 private final Object mLock = new Object();
173
Felipe Lemeb18e3172018-11-27 10:33:41 -0800174 @NonNull
Felipe Lemee348dc32018-11-05 12:35:29 -0800175 private final Context mContext;
176
Felipe Lemed49d52c2019-02-15 09:48:20 -0800177 @NonNull
Felipe Leme749b8892018-12-03 16:30:30 -0800178 private final IContentCaptureManager mService;
Felipe Lemee348dc32018-11-05 12:35:29 -0800179
Felipe Leme326f15a2019-02-19 09:42:24 -0800180 @NonNull
181 final ContentCaptureOptions mOptions;
182
Adam He6079d152019-01-10 11:37:17 -0800183 // Flags used for starting session.
184 @GuardedBy("mLock")
185 private int mFlags;
186
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800187 // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
Felipe Lemeb18e3172018-11-27 10:33:41 -0800188 // held at the Application level
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800189 @NonNull
Felipe Leme88eae3b2018-11-07 15:11:56 -0800190 private final Handler mHandler;
191
Adam He6079d152019-01-10 11:37:17 -0800192 @GuardedBy("mLock")
Felipe Leme87a9dc92018-12-18 14:28:07 -0800193 private MainContentCaptureSession mMainSession;
Felipe Leme4017b202018-12-10 12:13:31 -0800194
Felipe Lemee348dc32018-11-05 12:35:29 -0800195 /** @hide */
Adam He6240eab2019-02-25 13:34:45 -0800196 public interface ContentCaptureClient {
197 /**
198 * Gets the component name of the client.
199 */
200 @NonNull
201 ComponentName contentCaptureClientGetComponentName();
202 }
203
204 /** @hide */
Felipe Leme749b8892018-12-03 16:30:30 -0800205 public ContentCaptureManager(@NonNull Context context,
Felipe Leme326f15a2019-02-19 09:42:24 -0800206 @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800207 mContext = Preconditions.checkNotNull(context, "context cannot be null");
208 mService = Preconditions.checkNotNull(service, "service cannot be null");
Felipe Leme326f15a2019-02-19 09:42:24 -0800209 mOptions = Preconditions.checkNotNull(options, "options cannot be null");
Felipe Lemee348dc32018-11-05 12:35:29 -0800210
Felipe Leme326f15a2019-02-19 09:42:24 -0800211 ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800212
213 if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
214
Felipe Leme34ccedf2019-01-17 13:42:35 -0800215 // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we
216 // do, then we should optimize it to run the tests after the Choreographer finishes the most
217 // important steps of the frame.
218 mHandler = Handler.createAsync(Looper.getMainLooper());
Felipe Leme88eae3b2018-11-07 15:11:56 -0800219 }
220
Felipe Leme7a534082018-11-05 15:03:04 -0800221 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800222 * Gets the main session associated with the context.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800223 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800224 * <p>By default there's just one (associated with the activity lifecycle), but apps could
Felipe Leme87a9dc92018-12-18 14:28:07 -0800225 * explicitly add more using
226 * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800227 *
228 * @hide
229 */
230 @NonNull
Felipe Leme87a9dc92018-12-18 14:28:07 -0800231 @UiThread
232 public MainContentCaptureSession getMainContentCaptureSession() {
Adam He6079d152019-01-10 11:37:17 -0800233 synchronized (mLock) {
234 if (mMainSession == null) {
Felipe Leme609991d2019-01-30 16:27:24 -0800235 mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800236 if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800237 }
Adam He6079d152019-01-10 11:37:17 -0800238 return mMainSession;
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800239 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800240 }
241
242 /** @hide */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800243 @UiThread
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800244 public void onActivityCreated(@NonNull IBinder applicationToken,
Adam He328c0e32019-01-03 15:19:22 -0800245 @NonNull ComponentName activityComponent, int flags) {
Adam He6079d152019-01-10 11:37:17 -0800246 synchronized (mLock) {
247 mFlags |= flags;
248 getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags);
249 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800250 }
251
252 /** @hide */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800253 @UiThread
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800254 public void onActivityResumed() {
255 getMainContentCaptureSession().notifySessionLifecycle(/* started= */ true);
256 }
257
258 /** @hide */
259 @UiThread
260 public void onActivityPaused() {
261 getMainContentCaptureSession().notifySessionLifecycle(/* started= */ false);
262 }
263
264 /** @hide */
265 @UiThread
266 public void onActivityDestroyed() {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800267 getMainContentCaptureSession().destroy();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800268 }
269
270 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800271 * Flushes the content of all sessions.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800272 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800273 * <p>Typically called by {@code Activity} when it's paused / resumed.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800274 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800275 * @hide
Felipe Leme88eae3b2018-11-07 15:11:56 -0800276 */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800277 @UiThread
Felipe Leme1af85ea2019-01-16 13:23:40 -0800278 public void flush(@FlushReason int reason) {
279 getMainContentCaptureSession().flush(reason);
Felipe Leme88eae3b2018-11-07 15:11:56 -0800280 }
281
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700282 /**
Felipe Lemeecb08be2018-11-27 15:48:47 -0800283 * Returns the component name of the system service that is consuming the captured events for
284 * the current user.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700285 */
286 @Nullable
Felipe Lemeecb08be2018-11-27 15:48:47 -0800287 public ComponentName getServiceComponentName() {
Felipe Leme91ddeca2019-01-24 18:01:58 -0800288 if (!isContentCaptureEnabled()) return null;
289
Perumaal Saddabba2019-01-04 16:43:35 -0800290 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Perumaal Saddabba2019-01-04 16:43:35 -0800291 try {
Felipe Lemeeffa5f42019-01-28 14:59:46 -0800292 mService.getServiceComponentName(resultReceiver);
Perumaal Saddabba2019-01-04 16:43:35 -0800293 return resultReceiver.getParcelableResult();
294 } catch (RemoteException e) {
Perumaal Saddabba2019-01-04 16:43:35 -0800295 throw e.rethrowFromSystemServer();
296 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700297 }
298
299 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800300 * Checks whether content capture is enabled for this activity.
Felipe Leme91ddeca2019-01-24 18:01:58 -0800301 *
302 * <p>There are many reasons it could be disabled, such as:
303 * <ul>
304 * <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}.
305 * <li>Service disabled content capture for this specific activity.
306 * <li>Service disabled content capture for all activities of this package.
307 * <li>Service disabled content capture globally.
308 * <li>User disabled content capture globally (through Settings).
309 * <li>OEM disabled content capture globally.
310 * <li>Transient errors.
311 * </ul>
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700312 */
313 public boolean isContentCaptureEnabled() {
Felipe Leme609991d2019-01-30 16:27:24 -0800314 final MainContentCaptureSession mainSession;
Adam He6079d152019-01-10 11:37:17 -0800315 synchronized (mLock) {
Felipe Leme609991d2019-01-30 16:27:24 -0800316 mainSession = mMainSession;
Adam He6079d152019-01-10 11:37:17 -0800317 }
Felipe Leme609991d2019-01-30 16:27:24 -0800318 // The main session is only set when the activity starts, so we need to return true until
319 // then.
320 if (mainSession != null && mainSession.isDisabled()) return false;
321
322 return true;
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700323 }
324
325 /**
Felipe Leme284ad1c2018-11-15 18:16:12 -0800326 * Called by apps to explicitly enable or disable content capture.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700327 *
328 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
329 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
330 */
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800331 public void setContentCaptureEnabled(boolean enabled) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800332 if (sDebug) {
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800333 Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
334 }
335
Adam He6079d152019-01-10 11:37:17 -0800336 synchronized (mLock) {
337 mFlags |= enabled ? 0 : ContentCaptureContext.FLAG_DISABLED_BY_APP;
338 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800339 }
340
341 /**
Felipe Leme91ddeca2019-01-24 18:01:58 -0800342 * Gets whether Content Capture is enabled for the given user.
343 *
344 * <p>This method is typically used by the Content Capture Service settings page, so it can
345 * provide a toggle to enable / disable it.
346 *
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800347 * @throws SecurityException if caller is not the app that owns the Content Capture service
348 * associated with the user.
349 *
Felipe Leme91ddeca2019-01-24 18:01:58 -0800350 * @hide
351 */
352 @SystemApi
Felipe Leme19652c02019-02-04 13:01:29 -0800353 @TestApi
Felipe Leme91ddeca2019-01-24 18:01:58 -0800354 public boolean isContentCaptureFeatureEnabled() {
Felipe Leme91ddeca2019-01-24 18:01:58 -0800355 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800356 final int resultCode;
Felipe Leme91ddeca2019-01-24 18:01:58 -0800357 try {
358 mService.isContentCaptureFeatureEnabled(resultReceiver);
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800359 resultCode = resultReceiver.getIntResult();
Felipe Leme91ddeca2019-01-24 18:01:58 -0800360 } catch (RemoteException e) {
Felipe Leme91ddeca2019-01-24 18:01:58 -0800361 throw e.rethrowFromSystemServer();
362 }
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800363 switch (resultCode) {
364 case RESULT_CODE_TRUE:
365 return true;
366 case RESULT_CODE_FALSE:
367 return false;
368 case RESULT_CODE_NOT_SERVICE:
369 throw new SecurityException("caller is not user's ContentCapture service");
370 default:
Felipe Leme72e83d82019-02-13 14:51:17 -0800371 Log.wtf(TAG, "received invalid result: " + resultCode);
372 return false;
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800373 }
Felipe Leme91ddeca2019-01-24 18:01:58 -0800374 }
375
376 /**
Adam He3d0409b2019-01-15 14:22:04 -0800377 * Called by the app to request the Content Capture service to remove user-data associated with
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800378 * some context.
379 *
380 * @param request object specifying what user data should be removed.
381 */
382 public void removeUserData(@NonNull UserDataRemovalRequest request) {
Adam He3d0409b2019-01-15 14:22:04 -0800383 Preconditions.checkNotNull(request);
384
385 try {
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800386 mService.removeUserData(request);
Adam He3d0409b2019-01-15 14:22:04 -0800387 } catch (RemoteException e) {
388 e.rethrowFromSystemServer();
389 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700390 }
391
Felipe Lemee348dc32018-11-05 12:35:29 -0800392 /** @hide */
393 public void dump(String prefix, PrintWriter pw) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800394 pw.print(prefix); pw.println("ContentCaptureManager");
395 final String prefix2 = prefix + " ";
Adam He6079d152019-01-10 11:37:17 -0800396 synchronized (mLock) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800397 pw.print(prefix2); pw.print("isContentCaptureEnabled(): ");
Felipe Leme609991d2019-01-30 16:27:24 -0800398 pw.println(isContentCaptureEnabled());
Felipe Leme326f15a2019-02-19 09:42:24 -0800399 pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800400 pw.print(" Verbose: "); pw.println(sVerbose);
Felipe Leme326f15a2019-02-19 09:42:24 -0800401 pw.print(prefix2); pw.print("Context: "); pw.println(mContext);
402 pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId());
403 pw.print(prefix2); pw.print("Service: "); pw.println(mService);
404 pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags);
405 pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println();
Adam He6079d152019-01-10 11:37:17 -0800406 if (mMainSession != null) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800407 final String prefix3 = prefix2 + " ";
408 pw.print(prefix2); pw.println("Main session:");
409 mMainSession.dump(prefix3, pw);
Adam He6079d152019-01-10 11:37:17 -0800410 } else {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800411 pw.print(prefix2); pw.println("No sessions");
Adam He6079d152019-01-10 11:37:17 -0800412 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800413 }
414 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700415}