blob: 3fa0cc4887d5ea682e400e8735187281b54474ed [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.carlauncher;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityView;
import android.app.UserSwitchObserver;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.util.Log;
import android.widget.FrameLayout;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;
import com.android.car.media.common.PlaybackFragment;
import java.util.Set;
/**
* Basic Launcher for Android Automotive which demonstrates the use of {@link ActivityView} to host
* maps content.
*
* <p>Note: On some devices, the ActivityView may render with a width, height, and/or aspect
* ratio that does not meet Android compatibility definitions. Developers should work with content
* owners to ensure content renders correctly when extending or emulating this class.
*
* <p>Note: Since the hosted maps Activity in ActivityView is currently in a virtual display, the
* system considers the Activity to always be in front. Launching the maps Activity with a direct
* Intent will not work. To start the maps Activity on the real display, send the Intent to the
* Launcher with the {@link Intent#CATEGORY_APP_MAPS} category, and the launcher will start the
* Activity on the real display.
*
* <p>Note: The state of the virtual display in the ActivityView is nondeterministic when
* switching away from and back to the current user. To avoid a crash, this Activity will finish
* when switching users.
*/
public class CarLauncher extends FragmentActivity {
private static final String TAG = "CarLauncher";
private ActivityView mActivityView;
private boolean mActivityViewReady = false;
private boolean mIsStarted = false;
private final ActivityView.StateCallback mActivityViewCallback =
new ActivityView.StateCallback() {
@Override
public void onActivityViewReady(ActivityView view) {
mActivityViewReady = true;
startMapsInActivityView();
}
@Override
public void onActivityViewDestroyed(ActivityView view) {
mActivityViewReady = false;
}
@Override
public void onTaskMovedToFront(int taskId) {
try {
if (mIsStarted) {
ActivityManager am =
(ActivityManager) getSystemService(ACTIVITY_SERVICE);
am.moveTaskToFront(CarLauncher.this.getTaskId(), /* flags= */ 0);
}
} catch (RuntimeException e) {
Log.w(TAG, "Failed to move CarLauncher to front.");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Don't show the maps panel in multi window mode.
// NOTE: CTS tests for split screen are not compatible with activity views on the default
// activity of the launcher
if (isInMultiWindowMode() || isInPictureInPictureMode()) {
setContentView(R.layout.car_launcher_multiwindow);
} else {
setContentView(R.layout.car_launcher);
}
initializeFragments();
mActivityView = findViewById(R.id.maps);
if (mActivityView != null) {
mActivityView.setCallback(mActivityViewCallback);
}
}
@Override
protected void onNewIntent(Intent intent) {
Set<String> categories = intent.getCategories();
if (categories != null && categories.size() == 1 && categories.contains(
Intent.CATEGORY_APP_MAPS)) {
launchMapsActivity();
}
}
@Override
protected void onRestart() {
super.onRestart();
startMapsInActivityView();
}
@Override
protected void onStart() {
super.onStart();
mIsStarted = true;
}
@Override
protected void onStop() {
super.onStop();
mIsStarted = false;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mActivityView != null && mActivityViewReady) {
mActivityView.release();
}
}
private void startMapsInActivityView() {
// If we happen to be be resurfaced into a multi display mode we skip launching content
// in the activity view as we will get recreated anyway.
if (!mActivityViewReady || isInMultiWindowMode() || isInPictureInPictureMode()) {
return;
}
if (mActivityView != null) {
mActivityView.startActivity(getMapsIntent());
}
}
private void launchMapsActivity() {
// Make sure the Activity launches on the current display instead of in the ActivityView
// virtual display.
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(getDisplay().getDisplayId());
startActivity(getMapsIntent(), options.toBundle());
}
private Intent getMapsIntent() {
return Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_MAPS);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
initializeFragments();
}
private void initializeFragments() {
PlaybackFragment playbackFragment = new PlaybackFragment();
ContextualFragment contextualFragment = null;
FrameLayout contextual = findViewById(R.id.contextual);
if(contextual != null) {
contextualFragment = new ContextualFragment();
}
FragmentTransaction fragmentTransaction =
getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.playback, playbackFragment);
if(contextual != null) {
fragmentTransaction.replace(R.id.contextual, contextualFragment);
}
fragmentTransaction.commitNow();
}
}