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