blob: ddc8facb889f3502156f7f91009ddeedc8c4cc28 [file] [log] [blame]
/*
* Copyright (C) 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 com.android.car.messenger.common;
import static com.android.car.apps.common.util.SafeLog.logd;
import static com.android.car.apps.common.util.SafeLog.loge;
import static com.android.car.apps.common.util.SafeLog.logi;
import android.bluetooth.BluetoothDevice;
import android.car.Car;
import android.car.CarProjectionManager;
import android.car.projection.ProjectionStatus;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import java.util.Collections;
import java.util.List;
/**
* {@link ProjectionStatus} listener that exposes APIs to detect whether a projection application
* is active.
*/
public class ProjectionStateListener {
private static final String TAG = "CMC.ProjectionStateHandler";
static final String PROJECTION_STATUS_EXTRA_DEVICE_STATE =
"android.car.projection.DEVICE_STATE";
private CarProjectionManager mCarProjectionManager = null;
private final CarProjectionManager.ProjectionStatusListener mListener =
(state, packageName, details) -> {
mProjectionState = state;
mProjectionDetails = details;
};
private Car mCar;
private int mProjectionState = ProjectionStatus.PROJECTION_STATE_INACTIVE;
private List<ProjectionStatus> mProjectionDetails = Collections.emptyList();
public ProjectionStateListener(Context context) {
Car.createCar(context, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
(car, ready) -> {
mCar = car;
mCarProjectionManager = (CarProjectionManager) mCar.getCarManager(
Car.PROJECTION_SERVICE);
if (mCarProjectionManager != null) {
mCarProjectionManager.registerProjectionStatusListener(mListener);
}
});
}
/** Unregisters the listener. Should be called when the caller's lifecycle is ending. **/
public void destroy() {
if (mCarProjectionManager != null) {
mCarProjectionManager.unregisterProjectionStatusListener(mListener);
}
if (mCar != null) {
mCar.disconnect();
mCar = null;
}
mProjectionState = ProjectionStatus.PROJECTION_STATE_INACTIVE;
mProjectionDetails = Collections.emptyList();
}
/**
* Returns {@code true} if the input device currently has a projection app running in the
* foreground.
* @param bluetoothAddress of the device that should be checked. If null, return whether any
* device is currently running a projection app in the foreground.
*/
public boolean isProjectionInActiveForeground(@Nullable String bluetoothAddress) {
if (bluetoothAddress == null) {
logi(TAG, "returning non-device-specific projection status");
return isProjectionInActiveForeground();
}
if (!isProjectionInActiveForeground()) {
return false;
}
for (ProjectionStatus status : mProjectionDetails) {
if (!status.isActive()) {
// Don't suppress UI for packages that aren't actively projecting.
logd(TAG, "skip non-projecting package " + status.getPackageName());
continue;
}
for (ProjectionStatus.MobileDevice device : status.getConnectedMobileDevices()) {
if (!device.isProjecting()) {
// Don't suppress UI for devices that aren't foreground.
logd(TAG, "skip non-projecting device " + device.getName());
continue;
}
Bundle extras = device.getExtras();
if (extras.getInt(PROJECTION_STATUS_EXTRA_DEVICE_STATE,
ProjectionStatus.PROJECTION_STATE_ACTIVE_FOREGROUND)
!= ProjectionStatus.PROJECTION_STATE_ACTIVE_FOREGROUND) {
logd(TAG, "skip device " + device.getName() + " - not foreground");
continue;
}
Parcelable projectingBluetoothDevice =
extras.getParcelable(BluetoothDevice.EXTRA_DEVICE);
logd(TAG, "Device " + device.getName() + " has BT device "
+ projectingBluetoothDevice);
if (projectingBluetoothDevice == null) {
logi(TAG, "Suppressing message notification - device " + device
+ " is projection, and does not specify a Bluetooth address");
return true;
} else if (!(projectingBluetoothDevice instanceof BluetoothDevice)) {
loge(TAG, "Device " + device + " has bad EXTRA_DEVICE value "
+ projectingBluetoothDevice + " - treating as unspecified");
return true;
} else if (bluetoothAddress.equals(
((BluetoothDevice) projectingBluetoothDevice).getAddress())) {
logi(TAG, "Suppressing message notification - device " + device
+ "is projecting, and message is coming from device's Bluetooth address"
+ bluetoothAddress);
return true;
}
}
}
// No projecting apps want to suppress this device, so let it through.
return false;
}
/** Returns {@code true} if a projection app is active in the foreground. **/
private boolean isProjectionInActiveForeground() {
return mProjectionState == ProjectionStatus.PROJECTION_STATE_ACTIVE_FOREGROUND;
}
}