1 /*
2  * Copyright (C) 2024 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 android.car.app;
17 
18 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
19 
20 import android.annotation.FlaggedApi;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SystemApi;
25 import android.annotation.UiThread;
26 import android.app.Activity;
27 import android.car.Car;
28 import android.car.builtin.util.Slogf;
29 import android.car.builtin.view.ViewHelper;
30 import android.car.feature.Flags;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.pm.ActivityInfo;
35 import android.content.pm.PackageInfo;
36 import android.content.pm.PackageManager;
37 import android.graphics.Rect;
38 import android.os.Bundle;
39 import android.view.SurfaceView;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.function.Consumer;
44 
45 /**
46  * A container that's used by the display compat host.
47  *
48  * @hide
49  */
50 //TODO: b/329677726 Allow setting width, height and density from CarDisplayCompatContainer
51 @FlaggedApi(Flags.FLAG_DISPLAY_COMPATIBILITY)
52 @SystemApi
53 public final class CarDisplayCompatContainer {
54     private static final String TAG = CarDisplayCompatContainer.class.getSimpleName();
55     @NonNull
56     private final CarActivityManager mCarActivityManager;
57     @NonNull
58     private Activity mActivity;
59     @Nullable
60     private Consumer<SurfaceView> mSurfaceViewCallback;
61     @Nullable
62     private CarTaskViewController mCarTaskViewController;
63     @Nullable
64     private RemoteCarTaskView mRemoteCarTaskView;
65     @Nullable
66     private Intent mIntent;
67 
68     @FlaggedApi(Flags.FLAG_DISPLAY_COMPATIBILITY)
69     public static final class Builder {
70         @NonNull
71         private Activity mActivity;
72         @Nullable
73         private Consumer<SurfaceView> mSurfaceViewCallBack;
74         @NonNull
75         private CarActivityManager mCarActivityManager;
76 
Builder(@onNull Activity activity)77         public Builder(@NonNull Activity activity) {
78             mActivity = activity;
79         }
80 
81         /**
82          * set density in dpi
83          */
84         @NonNull
setSurfaceViewCallback(@ullable Consumer<SurfaceView> callback)85         public Builder setSurfaceViewCallback(@Nullable Consumer<SurfaceView> callback) {
86             mSurfaceViewCallBack = callback;
87             return this;
88         }
89 
90         /**
91          * set a car instance
92          */
93         @NonNull
setCarActivityManager(@onNull CarActivityManager carActivityManager)94         Builder setCarActivityManager(@NonNull CarActivityManager carActivityManager) {
95             mCarActivityManager = carActivityManager;
96             return this;
97         }
98 
99         /**
100          * Returns a new instance of {@link CarDisplayCompatContainer}
101          */
102         @NonNull
build()103         CarDisplayCompatContainer build() {
104             return new CarDisplayCompatContainer(
105                     mActivity, mSurfaceViewCallBack, mCarActivityManager);
106         }
107     }
108 
109     /**
110      * @hide
111      */
CarDisplayCompatContainer(@onNull Activity activity, @Nullable Consumer<SurfaceView> callback, @NonNull CarActivityManager carActivityManager)112     CarDisplayCompatContainer(@NonNull Activity activity,
113             @Nullable Consumer<SurfaceView> callback,
114             @NonNull CarActivityManager carActivityManager) {
115         mActivity = activity;
116         mSurfaceViewCallback = callback;
117         mCarActivityManager = carActivityManager;
118     }
119 
120     /**
121      * Set bounds of the display compat container
122      *
123      * @hide
124      */
125     @UiThread
126     @SystemApi
127     @RequiresPermission(Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY)
128     @NonNull
setWindowBounds(@onNull Rect windowBounds)129     public Rect setWindowBounds(@NonNull Rect windowBounds) {
130         if (mRemoteCarTaskView != null) {
131             mRemoteCarTaskView.setWindowBounds(windowBounds);
132         }
133         Rect actualWindowBounds = new Rect();
134         ViewHelper.getBoundsOnScreen(mRemoteCarTaskView, actualWindowBounds);
135         return actualWindowBounds;
136     }
137 
138     /**
139      * Set the visibility of the display compat container
140      * see {@link android.view.View#setVisibility(int)}
141      *
142      * @hide
143      */
144     @UiThread
145     @SystemApi
146     @RequiresPermission(Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY)
setVisibility(int visibility)147     public void setVisibility(int visibility) {
148         if (mRemoteCarTaskView != null) {
149             mRemoteCarTaskView.setVisibility(visibility);
150         }
151     }
152 
153     /**
154      * Launch an activity on the display compat container
155      *
156      * @hide
157      */
158     @UiThread
159     @SystemApi
160     @RequiresPermission(allOf = {Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY,
161             Car.PERMISSION_MANAGE_CAR_SYSTEM_UI,
162             INTERACT_ACROSS_USERS})
startActivity(@onNull Intent intent, @Nullable Bundle bundle)163     public void startActivity(@NonNull Intent intent, @Nullable Bundle bundle) {
164         mIntent = intent;
165         Context context = mActivity.getApplicationContext();
166         RemoteCarRootTaskViewCallback remoteCarTaskViewCallback =
167                 new RemoteCarRootTaskViewCallbackImpl(mRemoteCarTaskView,
168                         intent, context, mSurfaceViewCallback);
169         CarTaskViewControllerCallback carTaskViewControllerCallback =
170                 new CarTaskViewControllerCallbackImpl(remoteCarTaskViewCallback);
171 
172         mCarActivityManager.getCarTaskViewController(mActivity, mActivity.getMainExecutor(),
173                 carTaskViewControllerCallback);
174     }
175 
176     /**
177      * Called when the user clicks the back button
178      *
179      * @hide
180      */
181     @UiThread
182     @SystemApi
183     @RequiresPermission(Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY)
notifyBackPressed()184     public void notifyBackPressed() {
185     }
186 
187     private static final class RemoteCarRootTaskViewCallbackImpl implements
188             RemoteCarRootTaskViewCallback {
189         private RemoteCarTaskView mRemoteCarTaskView;
190         private final Intent mIntent;
191         private final Context mContext;
192         private Consumer<SurfaceView> mSurfaceViewCallback;
RemoteCarRootTaskViewCallbackImpl( RemoteCarTaskView remoteCarTaskView, Intent intent, Context context, Consumer<SurfaceView> callback)193         private RemoteCarRootTaskViewCallbackImpl(
194                 RemoteCarTaskView remoteCarTaskView,
195                 Intent intent,
196                 Context context,
197                 Consumer<SurfaceView> callback) {
198             mRemoteCarTaskView = remoteCarTaskView;
199             mIntent = intent;
200             mContext = context;
201             mSurfaceViewCallback = callback;
202         }
203 
204         @Override
onTaskViewCreated(@onNull RemoteCarRootTaskView taskView)205         public void onTaskViewCreated(@NonNull RemoteCarRootTaskView taskView) {
206             mRemoteCarTaskView = taskView;
207             if (mSurfaceViewCallback != null) {
208                 mSurfaceViewCallback.accept(mRemoteCarTaskView);
209             }
210             if (mIntent != null) {
211                 mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
212                 mContext.startActivity(mIntent);
213             }
214         }
215     }
216 
217     private final class CarTaskViewControllerCallbackImpl implements CarTaskViewControllerCallback {
218         private final RemoteCarRootTaskViewCallback mRemoteCarRootTaskViewCallback;
219 
CarTaskViewControllerCallbackImpl( @ullable RemoteCarRootTaskViewCallback remoteCarRootTaskViewCallback)220         private CarTaskViewControllerCallbackImpl(
221                 @Nullable RemoteCarRootTaskViewCallback remoteCarRootTaskViewCallback) {
222             mRemoteCarRootTaskViewCallback = remoteCarRootTaskViewCallback;
223         }
224 
225         @RequiresPermission("android.car.permission.CONTROL_CAR_APP_LAUNCH")
226         @Override
onConnected(@onNull CarTaskViewController carTaskViewController)227         public void onConnected(@NonNull CarTaskViewController carTaskViewController) {
228             mCarTaskViewController = carTaskViewController;
229             if (mIntent != null) {
230                 String packageName = mIntent.getComponent().getPackageName();
231                 if (packageName != null) {
232                     List<ComponentName> componentNames = queryActivitiesFromPackage(mActivity,
233                             packageName);
234                     mCarTaskViewController.createRemoteCarRootTaskView(
235                             new RemoteCarRootTaskViewConfig.Builder()
236                                     .setAllowListedActivities(componentNames)
237                                     .build(),
238                             mActivity.getMainExecutor(),
239                             mRemoteCarRootTaskViewCallback);
240                 }
241             }
242         }
243 
244         @Override
onDisconnected(@onNull CarTaskViewController carTaskViewController)245         public void onDisconnected(@NonNull CarTaskViewController carTaskViewController) {
246             mRemoteCarTaskView = null;
247         }
248     }
249 
queryActivitiesFromPackage( Activity activity, String packageName)250     private static List<ComponentName> queryActivitiesFromPackage(
251             Activity activity, String packageName) {
252         List<ComponentName> componentNames = new ArrayList<>();
253         PackageInfo packageInfo;
254         try {
255             packageInfo = activity.getPackageManager().getPackageInfo(
256                     packageName,
257                     PackageManager.GET_ACTIVITIES);
258             for (ActivityInfo info: packageInfo.activities) {
259                 ComponentName componentName = new ComponentName(packageName, info.name);
260                 componentNames.add(componentName);
261             }
262         } catch (PackageManager.NameNotFoundException e) {
263             Slogf.e(TAG, "Package '%s' not found : %s", packageName , e);
264         }
265         return componentNames;
266     }
267 }
268