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