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 Log.i(TAG, "onDisconnected: sServiceWatcher=" + sServiceWatcher); 88 89 if (sServiceWatcher == null) { 90 Log.e(TAG, "onDisconnected() without a watcher"); 91 return; 92 } 93 if (sServiceWatcher.mService == null) { 94 Log.e(TAG, "onDisconnected(): no service on " + sServiceWatcher); 95 return; 96 } 97 98 sServiceWatcher.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 } 118 119 @Override onActivityEvent(ActivityEvent event)120 public void onActivityEvent(ActivityEvent event) { 121 Log.i(TAG, "onActivityEvent(): " + event); 122 } 123 124 public static final class ServiceWatcher { 125 126 private static final long GENERIC_TIMEOUT_MS = 10_000; 127 private final CountDownLatch mCreated = new CountDownLatch(1); 128 private final CountDownLatch mDestroyed = new CountDownLatch(1); 129 private boolean mReadyToClear = true; 130 private Pair<Set<String>, Set<ComponentName>> mAllowList; 131 132 private MyContentCaptureService mService; 133 134 @NonNull waitOnCreate()135 public MyContentCaptureService waitOnCreate() throws InterruptedException { 136 await(mCreated, "not created"); 137 138 if (mService == null) { 139 throw new IllegalStateException("not created"); 140 } 141 142 if (mAllowList != null) { 143 Log.d(TAG, "Allow after created: " + mAllowList); 144 mService.setContentCaptureWhitelist(mAllowList.first, mAllowList.second); 145 } 146 147 return mService; 148 } 149 waitOnDestroy()150 public void waitOnDestroy() throws InterruptedException { 151 await(mDestroyed, "not destroyed"); 152 } 153 154 /** 155 * Allow just this package. 156 */ setAllowSelf()157 public void setAllowSelf() { 158 final ArraySet<String> pkgs = new ArraySet<>(1); 159 pkgs.add(MY_PACKAGE); 160 mAllowList = new Pair<>(pkgs, null); 161 } 162 163 @Override toString()164 public String toString() { 165 return "mService: " + mService + " created: " + (mCreated.getCount() == 0) 166 + " destroyed: " + (mDestroyed.getCount() == 0); 167 } 168 169 /** 170 * Awaits for a latch to be counted down. 171 */ await(@onNull CountDownLatch latch, @NonNull String fmt, @Nullable Object... args)172 private static void await(@NonNull CountDownLatch latch, @NonNull String fmt, 173 @Nullable Object... args) 174 throws InterruptedException { 175 final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS); 176 if (!called) { 177 throw new IllegalStateException(String.format(fmt, args) 178 + " in " + GENERIC_TIMEOUT_MS + "ms"); 179 } 180 } 181 } 182 } 183