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