1 /* 2 * Copyright (C) 2020 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 */ 16 package android.view.contentcapture; 17 18 import android.content.ComponentName; 19 import android.service.contentcapture.ActivityEvent; 20 import android.service.contentcapture.ContentCaptureService; 21 import android.util.ArraySet; 22 import android.util.Log; 23 import android.util.Pair; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 28 import java.util.Set; 29 import java.util.concurrent.CountDownLatch; 30 import java.util.concurrent.TimeUnit; 31 32 public class MyContentCaptureService extends ContentCaptureService { 33 34 private static final String TAG = MyContentCaptureService.class.getSimpleName(); 35 private static final String MY_PACKAGE = "com.android.perftests.contentcapture"; 36 public static final String SERVICE_NAME = MY_PACKAGE + "/" 37 + MyContentCaptureService.class.getName(); 38 39 private static ServiceWatcher sServiceWatcher; 40 41 @NonNull setServiceWatcher()42 public static ServiceWatcher setServiceWatcher() { 43 if (sServiceWatcher != null) { 44 throw new IllegalStateException("There Can Be Only One!"); 45 } 46 sServiceWatcher = new ServiceWatcher(); 47 return sServiceWatcher; 48 } 49 resetStaticState()50 public static void resetStaticState() { 51 sServiceWatcher = null; 52 } 53 clearServiceWatcher()54 private static void clearServiceWatcher() { 55 final ServiceWatcher sw = sServiceWatcher; 56 if (sw != null) { 57 if (sw.mReadyToClear) { 58 sw.mService = null; 59 sServiceWatcher = null; 60 } else { 61 sw.mReadyToClear = true; 62 } 63 } 64 } 65 66 @Override onConnected()67 public void onConnected() { 68 Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher); 69 70 if (sServiceWatcher == null) { 71 Log.e(TAG, "onConnected() without a watcher"); 72 return; 73 } 74 75 if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) { 76 Log.e(TAG, "onConnected(): already created: " + sServiceWatcher); 77 return; 78 } 79 80 sServiceWatcher.mService = this; 81 sServiceWatcher.mCreated.countDown(); 82 sServiceWatcher.mReadyToClear = false; 83 } 84 85 @Override onDisconnected()86 public void onDisconnected() { 87 final ServiceWatcher sw = sServiceWatcher; 88 Log.i(TAG, "onDisconnected: sServiceWatcher=" + sw); 89 if (sw == null) { 90 Log.e(TAG, "onDisconnected() without a watcher"); 91 return; 92 } 93 if (sw.mService == null) { 94 Log.e(TAG, "onDisconnected(): no service on " + sw); 95 return; 96 } 97 98 sw.mDestroyed.countDown(); 99 clearServiceWatcher(); 100 } 101 102 @Override onCreateContentCaptureSession(ContentCaptureContext context, ContentCaptureSessionId sessionId)103 public void onCreateContentCaptureSession(ContentCaptureContext context, 104 ContentCaptureSessionId sessionId) { 105 Log.i(TAG, "onCreateContentCaptureSession(ctx=" + context + ", session=" + sessionId); 106 } 107 108 @Override onDestroyContentCaptureSession(ContentCaptureSessionId sessionId)109 public void onDestroyContentCaptureSession(ContentCaptureSessionId sessionId) { 110 Log.i(TAG, "onDestroyContentCaptureSession(session=" + sessionId + ")"); 111 } 112 113 @Override onContentCaptureEvent(ContentCaptureSessionId sessionId, ContentCaptureEvent event)114 public void onContentCaptureEvent(ContentCaptureSessionId sessionId, 115 ContentCaptureEvent event) { 116 Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event); 117 if (sServiceWatcher != null 118 && event.getType() == ContentCaptureEvent.TYPE_SESSION_PAUSED) { 119 sServiceWatcher.mSessionPaused.countDown(); 120 } 121 } 122 123 @Override onActivityEvent(ActivityEvent event)124 public void onActivityEvent(ActivityEvent event) { 125 Log.i(TAG, "onActivityEvent(): " + event); 126 } 127 128 public static final class ServiceWatcher { 129 130 private static final long GENERIC_TIMEOUT_MS = 10_000; 131 private final CountDownLatch mCreated = new CountDownLatch(1); 132 private final CountDownLatch mDestroyed = new CountDownLatch(1); 133 private final CountDownLatch mSessionPaused = new CountDownLatch(1); 134 private boolean mReadyToClear = true; 135 private Pair<Set<String>, Set<ComponentName>> mAllowList; 136 137 private MyContentCaptureService mService; 138 139 @NonNull waitOnCreate()140 public MyContentCaptureService waitOnCreate() throws InterruptedException { 141 await(mCreated, "not created"); 142 143 if (mService == null) { 144 throw new IllegalStateException("not created"); 145 } 146 147 if (mAllowList != null) { 148 Log.d(TAG, "Allow after created: " + mAllowList); 149 mService.setContentCaptureWhitelist(mAllowList.first, mAllowList.second); 150 } 151 152 return mService; 153 } 154 waitOnDestroy()155 public void waitOnDestroy() throws InterruptedException { 156 await(mDestroyed, "not destroyed"); 157 } 158 159 /** Wait for session paused. */ waitSessionPaused()160 public void waitSessionPaused() throws InterruptedException { 161 await(mSessionPaused, "no Paused"); 162 } 163 164 /** 165 * Allow just this package. 166 */ setAllowSelf()167 public void setAllowSelf() { 168 final ArraySet<String> pkgs = new ArraySet<>(1); 169 pkgs.add(MY_PACKAGE); 170 mAllowList = new Pair<>(pkgs, null); 171 } 172 173 @Override toString()174 public String toString() { 175 return "mService: " + mService + " created: " + (mCreated.getCount() == 0) 176 + " destroyed: " + (mDestroyed.getCount() == 0); 177 } 178 179 /** 180 * Awaits for a latch to be counted down. 181 */ await(@onNull CountDownLatch latch, @NonNull String fmt, @Nullable Object... args)182 private static void await(@NonNull CountDownLatch latch, @NonNull String fmt, 183 @Nullable Object... args) 184 throws InterruptedException { 185 final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS); 186 if (!called) { 187 throw new IllegalStateException(String.format(fmt, args) 188 + " in " + GENERIC_TIMEOUT_MS + "ms"); 189 } 190 } 191 } 192 } 193