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