• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.carlauncher;
18 
19 import android.app.ActivityManager;
20 import android.app.ActivityView;
21 import android.car.app.CarActivityView;
22 import android.content.ActivityNotFoundException;
23 import android.content.Intent;
24 import android.content.res.Configuration;
25 import android.hardware.display.DisplayManager;
26 import android.hardware.display.DisplayManager.DisplayListener;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.util.Log;
31 import android.view.Display;
32 import android.view.WindowInsets;
33 import android.widget.FrameLayout;
34 
35 import androidx.fragment.app.FragmentActivity;
36 import androidx.fragment.app.FragmentTransaction;
37 
38 import com.android.car.media.common.PlaybackFragment;
39 
40 /**
41  * Basic Launcher for Android Automotive which demonstrates the use of {@link ActivityView} to host
42  * maps content.
43  *
44  * <p>Note: On some devices, the ActivityView may render with a width, height, and/or aspect
45  * ratio that does not meet Android compatibility definitions. Developers should work with content
46  * owners to ensure content renders correctly when extending or emulating this class.
47  *
48  * <p>Note: Since the hosted maps Activity in ActivityView is currently in a virtual display, the
49  * system considers the Activity to always be in front. Launching the maps Activity with a direct
50  * Intent will not work. To start the maps Activity on the real display, send the Intent to the
51  * Launcher with the {@link Intent#CATEGORY_APP_MAPS} category, and the launcher will start the
52  * Activity on the real display.
53  *
54  * <p>Note: The state of the virtual display in the ActivityView is nondeterministic when
55  * switching away from and back to the current user. To avoid a crash, this Activity will finish
56  * when switching users.
57  */
58 public class CarLauncher extends FragmentActivity {
59     private static final String TAG = "CarLauncher";
60     private static final boolean DEBUG = false;
61 
62     private CarActivityView mActivityView;
63     private boolean mActivityViewReady;
64     private boolean mIsStarted;
65     private DisplayManager mDisplayManager;
66     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
67 
68     /** Set to {@code true} once we've logged that the Activity is fully drawn. */
69     private boolean mIsReadyLogged;
70 
71     private final ActivityView.StateCallback mActivityViewCallback =
72             new ActivityView.StateCallback() {
73                 @Override
74                 public void onActivityViewReady(ActivityView view) {
75                     if (DEBUG) Log.d(TAG, "onActivityViewReady(" + getUserId() + ")");
76                     mActivityViewReady = true;
77                     startMapsInActivityView();
78                     maybeLogReady();
79                 }
80 
81                 @Override
82                 public void onActivityViewDestroyed(ActivityView view) {
83                     if (DEBUG) Log.d(TAG, "onActivityViewDestroyed(" + getUserId() + ")");
84                     mActivityViewReady = false;
85                 }
86 
87                 @Override
88                 public void onTaskMovedToFront(int taskId) {
89                     if (DEBUG) {
90                         Log.d(TAG, "onTaskMovedToFront(" + getUserId() + "): started="
91                                 + mIsStarted);
92                     }
93                     try {
94                         if (mIsStarted) {
95                             ActivityManager am =
96                                     (ActivityManager) getSystemService(ACTIVITY_SERVICE);
97                             am.moveTaskToFront(CarLauncher.this.getTaskId(), /* flags= */ 0);
98                         }
99                     } catch (RuntimeException e) {
100                         Log.w(TAG, "Failed to move CarLauncher to front.");
101                     }
102                 }
103             };
104 
105     private final DisplayListener mDisplayListener = new DisplayListener() {
106         @Override
107         public void onDisplayAdded(int displayId) {}
108         @Override
109         public void onDisplayRemoved(int displayId) {}
110 
111         @Override
112         public void onDisplayChanged(int displayId) {
113             if (displayId != getDisplay().getDisplayId()) {
114                 return;
115             }
116             // startMapsInActivityView() will check Display's State.
117             startMapsInActivityView();
118         }
119     };
120 
121     @Override
onCreate(Bundle savedInstanceState)122     protected void onCreate(Bundle savedInstanceState) {
123         super.onCreate(savedInstanceState);
124         // Don't show the maps panel in multi window mode.
125         // NOTE: CTS tests for split screen are not compatible with activity views on the default
126         // activity of the launcher
127         if (isInMultiWindowMode() || isInPictureInPictureMode()) {
128             setContentView(R.layout.car_launcher_multiwindow);
129         } else {
130             setContentView(R.layout.car_launcher);
131         }
132         initializeFragments();
133         mActivityView = findViewById(R.id.maps);
134         if (mActivityView != null) {
135             mActivityView.setCallback(mActivityViewCallback);
136         }
137         mDisplayManager = getSystemService(DisplayManager.class);
138         mDisplayManager.registerDisplayListener(mDisplayListener, mMainHandler);
139     }
140 
141     @Override
onRestart()142     protected void onRestart() {
143         super.onRestart();
144         startMapsInActivityView();
145     }
146 
147     @Override
onStart()148     protected void onStart() {
149         super.onStart();
150         mIsStarted = true;
151         maybeLogReady();
152     }
153 
154     @Override
onStop()155     protected void onStop() {
156         super.onStop();
157         mIsStarted = false;
158     }
159 
160     @Override
onDestroy()161     protected void onDestroy() {
162         super.onDestroy();
163         mDisplayManager.unregisterDisplayListener(mDisplayListener);
164         if (mActivityView != null && mActivityViewReady) {
165             mActivityView.release();
166         }
167     }
168 
startMapsInActivityView()169     private void startMapsInActivityView() {
170         if (mActivityView == null || !mActivityViewReady) {
171             return;
172         }
173         // If we happen to be be resurfaced into a multi display mode we skip launching content
174         // in the activity view as we will get recreated anyway.
175         if (isInMultiWindowMode() || isInPictureInPictureMode()) {
176             return;
177         }
178         // Don't start Maps when the display is off for ActivityVisibilityTests.
179         if (getDisplay().getState() != Display.STATE_ON) {
180             return;
181         }
182         try {
183             mActivityView.startActivity(getMapsIntent());
184         } catch (ActivityNotFoundException e) {
185             Log.w(TAG, "Maps activity not found", e);
186         }
187     }
188 
getMapsIntent()189     private Intent getMapsIntent() {
190         return Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_MAPS);
191     }
192 
193     @Override
onConfigurationChanged(Configuration newConfig)194     public void onConfigurationChanged(Configuration newConfig) {
195         super.onConfigurationChanged(newConfig);
196         initializeFragments();
197     }
198 
initializeFragments()199     private void initializeFragments() {
200         PlaybackFragment playbackFragment = new PlaybackFragment();
201         ContextualFragment contextualFragment = null;
202         FrameLayout contextual = findViewById(R.id.contextual);
203         if (contextual != null) {
204             contextualFragment = new ContextualFragment();
205         }
206 
207         FragmentTransaction fragmentTransaction =
208                 getSupportFragmentManager().beginTransaction();
209         fragmentTransaction.replace(R.id.playback, playbackFragment);
210         if (contextual != null) {
211             fragmentTransaction.replace(R.id.contextual, contextualFragment);
212         }
213         fragmentTransaction.commitNow();
214     }
215 
216     /** Logs that the Activity is ready. Used for startup time diagnostics. */
maybeLogReady()217     private void maybeLogReady() {
218         if (DEBUG) {
219             Log.d(TAG, "maybeLogReady(" + getUserId() + "): activityReady=" + mActivityViewReady
220                     + ", started=" + mIsStarted + ", alreadyLogged: " + mIsReadyLogged);
221         }
222         if (mActivityViewReady && mIsStarted) {
223             // We should report everytime - the Android framework will take care of logging just
224             // when it's effectivelly drawn for the first time, but....
225             reportFullyDrawn();
226             if (!mIsReadyLogged) {
227                 // ... we want to manually check that the Log.i below (which is useful to show
228                 // the user id) is only logged once (otherwise it would be logged everytime the user
229                 // taps Home)
230                 Log.i(TAG, "Launcher for user " + getUserId() + " is ready");
231                 mIsReadyLogged = true;
232             }
233         }
234     }
235 }
236