/* * Copyright 2020 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 androidx.car.app; import static androidx.car.app.utils.LogTags.TAG; import static androidx.car.app.utils.ThreadUtils.runOnMain; import static java.util.Objects.requireNonNull; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.car.app.annotations.ExperimentalCarApi; import androidx.car.app.annotations.RequiresCarApi; import androidx.car.app.validation.HostValidator; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * The base class for implementing a car app that runs in the car *
intent-filter
to the service in the AndroidManifest.xml
that handles
* the {@link #SERVICE_INTERFACE} action. The app must also declare what category of application
* it is (e.g. {@link #CATEGORY_NAVIGATION_APP}). For example:
*
* {@code ** ** * }* ** *
For a list of all the supported categories see * Supported App Categories. * *
To reliably get location for your car app, we recommended that you use a foreground * service. If you have a service other than your {@link CarAppService} that accesses * location, run the service and your `CarAppService` in the same process. Also note that * accessing location may become unreliable when the phone is in the battery saver mode. */ public abstract class CarAppService extends Service { /** * The full qualified name of the {@link CarAppService} class. * *
This is the same name that must be used to declare the action of the intent filter for * the app's {@link CarAppService} in the app's manifest. * * @see CarAppService */ public static final String SERVICE_INTERFACE = "androidx.car.app.CarAppService"; /** * Used to declare that this app supports cluster in the manifest. */ @RequiresCarApi(6) public static final String CATEGORY_FEATURE_CLUSTER = "androidx.car.app.category.FEATURE_CLUSTER"; /** * Used to declare that this app is a navigation app in the manifest. */ public static final String CATEGORY_NAVIGATION_APP = "androidx.car.app.category.NAVIGATION"; /** * Used to declare that this app is a parking app in the manifest. * * @deprecated use {@link #CATEGORY_POI_APP} instead */ @Deprecated public static final String CATEGORY_PARKING_APP = "androidx.car.app.category.PARKING"; /** * Used to declare that this app is a charging app in the manifest. * * @deprecated use {@link #CATEGORY_POI_APP} instead */ @Deprecated public static final String CATEGORY_CHARGING_APP = "androidx.car.app.category.CHARGING"; /** * Used in the app manifest. It declares that this app finds points of interests (POI). */ public static final String CATEGORY_POI_APP = "androidx.car.app.category.POI"; /** * Used in the app manifest. It declares that this app declares physical objects with sensors, * that connect and exchange data with other devices and systems. */ @ExperimentalCarApi public static final String CATEGORY_IOT_APP = "androidx.car.app.category.IOT"; /** * Used to declare that this app is a settings app in the manifest. This app can be used to * provide screens corresponding to the settings page and/or any error resolution screens e.g. * sign-in screen. */ @ExperimentalCarApi public static final String CATEGORY_SETTINGS_APP = "androidx.car.app.category.SETTINGS"; /** * Used to declare that this app is a messaging app in the manifest. * *
This app can be used to send and receive short-form chat messages (IM/SMS).
*/
@ExperimentalCarApi
public static final String CATEGORY_MESSAGING_APP = "androidx.car.app.category.MESSAGING";
private static final String AUTO_DRIVE = "AUTO_DRIVE";
@NonNull
private final Map This method is final to ensure this car app's lifecycle is handled properly.
*
* Use {@link #onCreateSession(SessionInfo)} and {@link Session#onNewIntent} instead to
* handle incoming {@link Intent}s.
*/
@Override
@CallSuper
@NonNull
public final IBinder onBind(@NonNull Intent intent) {
SessionInfo sessionInfo = SessionInfoIntentEncoder.containsSessionInfo(intent)
? SessionInfoIntentEncoder.decode(intent)
: SessionInfo.DEFAULT_SESSION_INFO;
synchronized (mBinders) {
if (!mBinders.containsKey(sessionInfo)) {
mBinders.put(sessionInfo, new CarAppBinder(this, sessionInfo));
}
return requireNonNull(mBinders.get(sessionInfo));
}
}
/**
* Handles the host unbinding from this car app.
*
* This method is final to ensure this car app's lifecycle is handled properly.
*/
@Override
public final boolean onUnbind(@NonNull Intent intent) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onUnbind intent: " + intent);
}
SessionInfo sessionInfo = SessionInfoIntentEncoder.containsSessionInfo(intent)
? SessionInfoIntentEncoder.decode(intent)
: SessionInfo.DEFAULT_SESSION_INFO;
runOnMain(() -> {
synchronized (mBinders) {
CarAppBinder binder = mBinders.remove(sessionInfo);
if (binder != null) {
binder.onDestroyLifecycle();
}
}
});
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onUnbind completed");
}
// Return true to request an onRebind call. This means that the process will cache this
// instance of the Service to return on future bind calls.
return true;
}
/**
* Returns the {@link HostValidator} this service will use to accept or reject host connections.
*
* By default, the provided {@link HostValidator.Builder} would produce a validator that
* only accepts connections from hosts holding
* {@link HostValidator#TEMPLATE_RENDERER_PERMISSION} permission.
*
* Application developers are expected to also allow connections from known hosts which
* don't hold the aforementioned permission (for example, Android Auto and Android
* Automotive OS hosts below API level 31), by allow-listing the signatures of those hosts.
*
* Refer to {@code androidx.car.app.R.array.hosts_allowlist_sample} to obtain a
* list of package names and signatures that should be allow-listed by default.
*
* It is also advised to allow connections from unknown hosts in debug builds to facilitate
* debugging and testing.
*
* Below is an example of this method implementation:
*
* This method is invoked the first time the app is started, or if the previous
* {@link Session} instance has been destroyed and the system has not yet destroyed
* this service.
*
* Once the method returns, {@link Session#onCreateScreen(Intent)} will be called on the
* {@link Session} returned.
*
* Called by the system, do not call this method directly.
*
* @see CarContext#startCarApp(Intent)
*/
@NonNull
public Session onCreateSession() {
throw new RuntimeException(
"Please override and implement CarAppService#onCreateSession(SessionInfo).");
}
// TODO(b/236140507): Link AndroidManifest.xml documentation or equivalent in this javadoc
/**
* Creates a new {@link Session}.
*
* This method is invoked once per app-supported physical display with a unique
* {@link SessionInfo} identifying the type of display. Support for displays is declared within
* the AndroidManifest.xml. This method can also be invoked if the previous instance has been
* destroyed (ie. due to memory pressure) and the system has not yet destroyed this service.
*
* This method is called by the system and should not be called directly.
*
* @see CarContext#startCarApp(Intent)
*/
@NonNull
@SuppressWarnings("deprecation")
@RequiresCarApi(6)
public Session onCreateSession(@NonNull SessionInfo sessionInfo) {
return onCreateSession();
}
@Override
@CallSuper
public final void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
@Nullable String[] args) {
super.dump(fd, writer, args);
for (String arg : args) {
if (AUTO_DRIVE.equals(arg)) {
runOnMain(() -> {
synchronized (mBinders) {
for (CarAppBinder binder : mBinders.values()) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Executing onAutoDriveEnabled for "
+ binder.getCurrentSessionInfo());
}
binder.onAutoDriveEnabled();
}
}
});
}
}
}
/**
* Returns information about the host attached to this service.
*
* @see HostInfo
*/
@Nullable
public final HostInfo getHostInfo() {
return mHostInfo;
}
void setHostInfo(@Nullable HostInfo hostInfo) {
mHostInfo = hostInfo;
}
/**
* Returns the current {@link Session} for the main display if one exists, otherwise returns
* {@code null}.
*
* @deprecated use {@link #getSession(SessionInfo)}
*/
@Nullable
@Deprecated
public final Session getCurrentSession() {
synchronized (mBinders) {
for (Map.Entry
* @Override
* @NonNull
* public HostValidator createHostValidator() {
* if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
* return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR;
* } else {
* return new HostValidator.Builder(context)
* .addAllowedHosts(androidx.car.app.R.array.hosts_allowlist_sample)
* .build();
* }
* }
*
*/
@NonNull
public abstract HostValidator createHostValidator();
/**
* Creates a new {@link Session} for the application.
*
*