• 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.ActivityOptions;
21 import android.app.ActivityView;
22 import android.app.UserSwitchObserver;
23 import android.content.Intent;
24 import android.content.res.Configuration;
25 import android.os.Bundle;
26 import android.os.IRemoteCallback;
27 import android.os.RemoteException;
28 import android.util.Log;
29 import android.widget.FrameLayout;
30 
31 import androidx.fragment.app.FragmentActivity;
32 import androidx.fragment.app.FragmentTransaction;
33 
34 import com.android.car.media.common.PlaybackFragment;
35 
36 import java.util.Set;
37 
38 /**
39  * Basic Launcher for Android Automotive which demonstrates the use of {@link ActivityView} to host
40  * maps content.
41  *
42  * <p>Note: On some devices, the ActivityView may render with a width, height, and/or aspect
43  * ratio that does not meet Android compatibility definitions. Developers should work with content
44  * owners to ensure content renders correctly when extending or emulating this class.
45  *
46  * <p>Note: Since the hosted maps Activity in ActivityView is currently in a virtual display, the
47  * system considers the Activity to always be in front. Launching the maps Activity with a direct
48  * Intent will not work. To start the maps Activity on the real display, send the Intent to the
49  * Launcher with the {@link Intent#CATEGORY_APP_MAPS} category, and the launcher will start the
50  * Activity on the real display.
51  *
52  * <p>Note: The state of the virtual display in the ActivityView is nondeterministic when
53  * switching away from and back to the current user. To avoid a crash, this Activity will finish
54  * when switching users.
55  */
56 public class CarLauncher extends FragmentActivity {
57     private static final String TAG = "CarLauncher";
58 
59     private ActivityView mActivityView;
60     private boolean mActivityViewReady = false;
61     private boolean mIsStarted = false;
62 
63     private final ActivityView.StateCallback mActivityViewCallback =
64             new ActivityView.StateCallback() {
65                 @Override
66                 public void onActivityViewReady(ActivityView view) {
67                     mActivityViewReady = true;
68                     startMapsInActivityView();
69                 }
70 
71                 @Override
72                 public void onActivityViewDestroyed(ActivityView view) {
73                     mActivityViewReady = false;
74                 }
75 
76                 @Override
77                 public void onTaskMovedToFront(int taskId) {
78                     try {
79                         if (mIsStarted) {
80                             ActivityManager am =
81                                     (ActivityManager) getSystemService(ACTIVITY_SERVICE);
82                             am.moveTaskToFront(CarLauncher.this.getTaskId(), /* flags= */ 0);
83                         }
84                     } catch (RuntimeException e) {
85                         Log.w(TAG, "Failed to move CarLauncher to front.");
86                     }
87                 }
88             };
89 
90     @Override
onCreate(Bundle savedInstanceState)91     protected void onCreate(Bundle savedInstanceState) {
92         super.onCreate(savedInstanceState);
93         // Don't show the maps panel in multi window mode.
94         // NOTE: CTS tests for split screen are not compatible with activity views on the default
95         // activity of the launcher
96         if (isInMultiWindowMode() || isInPictureInPictureMode()) {
97             setContentView(R.layout.car_launcher_multiwindow);
98         } else {
99             setContentView(R.layout.car_launcher);
100         }
101         initializeFragments();
102         mActivityView = findViewById(R.id.maps);
103         if (mActivityView != null) {
104             mActivityView.setCallback(mActivityViewCallback);
105         }
106     }
107 
108     @Override
onNewIntent(Intent intent)109     protected void onNewIntent(Intent intent) {
110         Set<String> categories = intent.getCategories();
111         if (categories != null && categories.size() == 1 && categories.contains(
112                 Intent.CATEGORY_APP_MAPS)) {
113             launchMapsActivity();
114         }
115     }
116 
117     @Override
onRestart()118     protected void onRestart() {
119         super.onRestart();
120         startMapsInActivityView();
121     }
122 
123     @Override
onStart()124     protected void onStart() {
125         super.onStart();
126         mIsStarted = true;
127     }
128 
129     @Override
onStop()130     protected void onStop() {
131         super.onStop();
132         mIsStarted = false;
133     }
134 
135     @Override
onDestroy()136     protected void onDestroy() {
137         super.onDestroy();
138         if (mActivityView != null && mActivityViewReady) {
139             mActivityView.release();
140         }
141     }
142 
startMapsInActivityView()143     private void startMapsInActivityView() {
144         // If we happen to be be resurfaced into a multi display mode we skip launching content
145         // in the activity view as we will get recreated anyway.
146         if (!mActivityViewReady || isInMultiWindowMode() || isInPictureInPictureMode()) {
147             return;
148         }
149         if (mActivityView != null) {
150             mActivityView.startActivity(getMapsIntent());
151         }
152     }
153 
launchMapsActivity()154     private void launchMapsActivity() {
155         // Make sure the Activity launches on the current display instead of in the ActivityView
156         // virtual display.
157         final ActivityOptions options = ActivityOptions.makeBasic();
158         options.setLaunchDisplayId(getDisplay().getDisplayId());
159         startActivity(getMapsIntent(), options.toBundle());
160     }
161 
getMapsIntent()162     private Intent getMapsIntent() {
163         return Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_MAPS);
164     }
165 
166     @Override
onConfigurationChanged(Configuration newConfig)167     public void onConfigurationChanged(Configuration newConfig) {
168         super.onConfigurationChanged(newConfig);
169         initializeFragments();
170     }
171 
initializeFragments()172     private void initializeFragments() {
173         PlaybackFragment playbackFragment = new PlaybackFragment();
174         ContextualFragment contextualFragment = null;
175         FrameLayout contextual = findViewById(R.id.contextual);
176         if(contextual != null) {
177             contextualFragment = new ContextualFragment();
178         }
179 
180         FragmentTransaction fragmentTransaction =
181                 getSupportFragmentManager().beginTransaction();
182         fragmentTransaction.replace(R.id.playback, playbackFragment);
183         if(contextual != null) {
184             fragmentTransaction.replace(R.id.contextual, contextualFragment);
185         }
186         fragmentTransaction.commitNow();
187     }
188 }
189