1 /* 2 * Copyright (C) 2021 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 com.android.bedstead.dpmwrapper; 17 18 import android.app.ActivityManager; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.os.Build; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.UserHandle; 27 import android.os.UserManager; 28 import android.util.Log; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import java.util.Set; 33 import java.util.concurrent.Callable; 34 import java.util.concurrent.CountDownLatch; 35 import java.util.concurrent.TimeUnit; 36 import java.util.concurrent.TimeoutException; 37 import java.util.concurrent.atomic.AtomicReference; 38 39 /** 40 * Generic helpers. 41 */ 42 public final class Utils { 43 44 private static final String TAG = "DpmWrapperUtils"; 45 46 static final boolean VERBOSE = false; 47 48 static final int MY_USER_ID = UserHandle.myUserId(); 49 50 static final String ACTION_WRAPPED_MANAGER_CALL = 51 "com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL"; 52 static final String EXTRA_CLASS = "className"; 53 static final String EXTRA_METHOD = "methodName"; 54 static final String EXTRA_NUMBER_ARGS = "number_args"; 55 static final String EXTRA_ARG_PREFIX = "arg_"; 56 57 private static final Object LOCK = new Object(); 58 59 @GuardedBy("LOCK") 60 private static HandlerThread sHandlerThread; 61 62 @GuardedBy("LOCK") 63 private static Handler sHandler; 64 isHeadlessSystemUserMode()65 static boolean isHeadlessSystemUserMode() { 66 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S 67 && UserManager.isHeadlessSystemUserMode(); 68 } 69 isHeadlessSystemUser()70 static boolean isHeadlessSystemUser() { 71 return isHeadlessSystemUserMode() && MY_USER_ID == UserHandle.USER_SYSTEM; 72 } 73 isCurrentUserOnHeadlessSystemUser(Context context)74 static boolean isCurrentUserOnHeadlessSystemUser(Context context) { 75 return isHeadlessSystemUserMode() 76 && context.getSystemService(UserManager.class).isUserForeground(); 77 } 78 assertCurrentUserOnHeadlessSystemMode(Context context)79 static void assertCurrentUserOnHeadlessSystemMode(Context context) { 80 if (isCurrentUserOnHeadlessSystemUser(context)) return; 81 82 throw new IllegalStateException("Should only be called by current user (" 83 + ActivityManager.getCurrentUser() + ") on headless system user device, but was " 84 + "called by process from user " + MY_USER_ID); 85 } 86 toString(IntentFilter filter)87 static String toString(IntentFilter filter) { 88 StringBuilder builder = new StringBuilder("["); 89 filter.actionsIterator().forEachRemaining((s) -> builder.append(s).append(",")); 90 builder.deleteCharAt(builder.length() - 1); 91 return builder.append(']').toString(); 92 } 93 getHandler()94 static Handler getHandler() { 95 synchronized (LOCK) { 96 if (sHandler == null) { 97 sHandlerThread = new HandlerThread("DpmWrapperHandlerThread"); 98 Log.i(TAG, "Starting handler thread " + sHandlerThread); 99 sHandlerThread.start(); 100 sHandler = new Handler(sHandlerThread.getLooper()); 101 } 102 } 103 return sHandler; 104 } 105 callOnHandlerThread(Callable<T> callable)106 static <T> T callOnHandlerThread(Callable<T> callable) throws Exception { 107 if (VERBOSE) Log.v(TAG, "callOnHandlerThread(): called from " + Thread.currentThread()); 108 109 final CountDownLatch latch = new CountDownLatch(1); 110 final AtomicReference<T> returnRef = new AtomicReference<>(); 111 final AtomicReference<Exception> exceptionRef = new AtomicReference<>(); 112 113 getHandler().post(() -> { 114 Log.d(TAG, "Calling callable on handler thread " + Thread.currentThread()); 115 try { 116 T result = callable.call(); 117 if (VERBOSE) Log.v(TAG, "Got result: " + result); 118 returnRef.set(result); 119 } catch (Exception e) { 120 Log.e(TAG, "Got exception: " + e); 121 exceptionRef.set(e); 122 } finally { 123 latch.countDown(); 124 } 125 }); 126 127 if (!latch.await(50, TimeUnit.SECONDS)) { 128 throw new TimeoutException("didn't get result in 50 seconds"); 129 } 130 131 Exception exception = exceptionRef.get(); 132 if (exception != null) throw exception; 133 134 return returnRef.get(); 135 } 136 137 /** 138 * Gets a more detailed description of an intent (for example, including extras). 139 */ toString(Intent intent)140 public static String toString(Intent intent) { 141 StringBuilder builder = new StringBuilder("[Intent: action="); 142 String action = intent.getAction(); 143 if (action == null) { 144 builder.append("null"); 145 } else { 146 builder.append(action); 147 } 148 Set<String> categories = intent.getCategories(); 149 if (categories == null || categories.isEmpty()) { 150 builder.append(", no_categories"); 151 } else { 152 builder.append(", ").append(categories.size()).append(" categories: ") 153 .append(categories); 154 } 155 Bundle extras = intent.getExtras(); 156 builder.append(", "); 157 if (extras == null || extras.isEmpty()) { 158 builder.append("no_extras"); 159 } else { 160 appendBundleExtras(builder, extras); 161 } 162 return builder.append(']').toString(); 163 } 164 appendBundleExtras(StringBuilder builder, Bundle bundle)165 public static void appendBundleExtras(StringBuilder builder, Bundle bundle) { 166 builder.append(bundle.size()).append(" extras: "); 167 bundle.keySet().forEach( 168 (key) -> builder.append(key).append('=').append(bundle.get(key)).append(',')); 169 builder.deleteCharAt(builder.length() - 1); 170 } 171 Utils()172 private Utils() { 173 throw new UnsupportedOperationException("contains only static methods"); 174 } 175 } 176