1 /* 2 * Copyright 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 17 package com.android.car.rotary; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.content.pm.ResolveInfo; 24 import android.text.TextUtils; 25 import android.view.accessibility.AccessibilityNodeInfo; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 import androidx.annotation.VisibleForTesting; 30 31 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 32 import com.android.internal.util.dump.DualDumpOutputStream; 33 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Set; 37 38 /** 39 * A helper class to support apps using {@link android.view.SurfaceView} for off-process rendering. 40 * <p> 41 * There are two kinds of apps involved in the off-process rendering process: the client apps and 42 * the host app. A client app holds a {@link android.view.SurfaceView} and delegates its rendering 43 * process to the host app. The host app uses the data provided by the client app to render the app 44 * content in the surface provided by the SurfaceView. 45 * <p> 46 * Although both the client app and the host app have independent <strong>view</strong> hierarchies, 47 * their <strong>node</strong> hierarchies are connected. The node hierarchy of the host app is 48 * embedded into the node hierarchy of the client app. To be more specific, the root node of the 49 * host app is the only child of the SurfaceView node, which is a leaf node of the client app. 50 */ 51 class SurfaceViewHelper { 52 53 /** The intent action to be used by the host app to bind to the RendererService. */ 54 private static final String RENDER_ACTION = "android.car.template.host.RendererService"; 55 56 /** Package names of the client apps. */ 57 private final Set<CharSequence> mClientApps = new HashSet<>(); 58 59 /** Package name of the host app. */ 60 @Nullable 61 @VisibleForTesting 62 String mHostApp; 63 64 /** Initializes the package name of the host app. */ initHostApp(@onNull PackageManager packageManager)65 void initHostApp(@NonNull PackageManager packageManager) { 66 List<ResolveInfo> rendererServices = packageManager.queryIntentServices( 67 new Intent(RENDER_ACTION), PackageManager.GET_RESOLVED_FILTER); 68 if (rendererServices == null || rendererServices.isEmpty()) { 69 L.v("No host app found"); 70 return; 71 } 72 mHostApp = rendererServices.get(0).serviceInfo.packageName; 73 L.v("Host app has been initialized: " + mHostApp); 74 } 75 76 /** Clears the package name of the host app if the given {@code packageName} matches. */ clearHostApp(@onNull String packageName)77 void clearHostApp(@NonNull String packageName) { 78 if (packageName.equals(mHostApp)) { 79 mHostApp = null; 80 L.v("Host app has been set to null"); 81 } 82 } 83 84 /** Adds the package name of the client app. */ addClientApp(@onNull CharSequence clientAppPackageName)85 void addClientApp(@NonNull CharSequence clientAppPackageName) { 86 mClientApps.add(clientAppPackageName); 87 } 88 89 /** Returns whether the given {@code node} represents a view of the host app. */ isHostNode(@onNull AccessibilityNodeInfo node)90 boolean isHostNode(@NonNull AccessibilityNodeInfo node) { 91 return !TextUtils.isEmpty(mHostApp) && mHostApp.equals(node.getPackageName()); 92 } 93 94 /** Returns whether the given {@code node} represents a view of the client app. */ isClientNode(@onNull AccessibilityNodeInfo node)95 boolean isClientNode(@NonNull AccessibilityNodeInfo node) { 96 return mClientApps.contains(node.getPackageName()); 97 } 98 99 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(@onNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, @NonNull String fieldName, long fieldId)100 void dump(@NonNull DualDumpOutputStream dumpOutputStream, boolean dumpAsProto, 101 @NonNull String fieldName, long fieldId) { 102 long fieldToken = dumpOutputStream.start(fieldName, fieldId); 103 dumpOutputStream.write("hostApp", RotaryProtos.SurfaceViewHelper.HOST_APP, mHostApp); 104 DumpUtils.writeCharSequences(dumpOutputStream, dumpAsProto, "clientApps", 105 RotaryProtos.SurfaceViewHelper.CLIENT_APPS, mClientApps); 106 dumpOutputStream.end(fieldToken); 107 } 108 109 } 110