blob: aa925d3b5d44c9fd1deaa9b92902c2160840c291 [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.carlauncher.homescreen.assistive;
import android.car.Car;
import android.car.CarProjectionManager;
import android.car.projection.ProjectionStatus;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.icu.text.MessageFormat;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.car.carlauncher.R;
import com.android.car.carlauncher.homescreen.HomeCardInterface;
import com.android.car.carlauncher.homescreen.ui.CardContent;
import com.android.car.carlauncher.homescreen.ui.CardHeader;
import com.android.car.carlauncher.homescreen.ui.DescriptiveTextView;
import java.util.List;
import java.util.Map;
/**
* The {@link HomeCardInterface.Model} for projection status
*/
public class ProjectionModel implements CarProjectionManager.ProjectionStatusListener,
AssistiveModel {
private static final String TAG = "ProjectionModel";
private HomeCardInterface.Presenter mPresenter;
@Nullable
private Car mCar;
@Nullable
private CarProjectionManager mCarProjectionManager;
private PackageManager mPackageManager;
private Resources mResources;
private CharSequence mAppName;
private Drawable mAppIcon;
private CharSequence mLaunchMessage;
private CharSequence mStatusMessage;
private CharSequence mTapToLaunchText;
private Intent mIntent;
@Override
public void onCreate(Context context) {
mCar = Car.createCar(context.getApplicationContext(), null,
Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
(Car car, boolean ready) -> {
if (ready) {
mCarProjectionManager = car.getCarManager(CarProjectionManager.class);
} else {
mCarProjectionManager = null;
}
if (mCarProjectionManager != null) {
mCarProjectionManager.registerProjectionStatusListener(this);
} else {
onProjectionStatusChanged(
ProjectionStatus.PROJECTION_STATE_INACTIVE, null, null);
}
});
mPackageManager = context.getPackageManager();
mResources = context.getResources();
mLaunchMessage = context.getResources().getString(R.string.projected_launch_text);
mTapToLaunchText = context.getResources().getString(R.string.tap_to_launch_text);
}
@Override
public void onDestroy(Context context) {
if (mCarProjectionManager != null) {
mCarProjectionManager.unregisterProjectionStatusListener(this);
mCarProjectionManager = null;
}
if (mCar != null) {
mCar.disconnect();
mCar = null;
}
}
@Override
public CardHeader getCardHeader() {
return mAppName == null ? null : new CardHeader(mAppName, mAppIcon);
}
@Override
public CardContent getCardContent() {
return mAppName == null ? null : new DescriptiveTextView(mAppIcon, mLaunchMessage,
mStatusMessage, mTapToLaunchText);
}
@Override
public Intent getIntent() {
return mIntent;
}
@Override
public void setPresenter(HomeCardInterface.Presenter presenter) {
mPresenter = presenter;
}
@Override
public void onProjectionStatusChanged(int state, String packageName,
List<ProjectionStatus> details) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onProjectionStatusChanged state=" + state + " package=" + packageName);
}
if (state == ProjectionStatus.PROJECTION_STATE_INACTIVE || packageName == null) {
if (mAppName != null) {
mAppName = null;
mPresenter.onModelUpdated(this);
}
return;
}
ApplicationInfo applicationInfo;
try {
applicationInfo = mPackageManager.getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Could not load projection package information", e);
return;
}
mAppName = applicationInfo.loadLabel(mPackageManager);
mAppIcon = applicationInfo.loadIcon(mPackageManager);
mStatusMessage = getStatusMessage(packageName, details);
mIntent = mPackageManager.getLaunchIntentForPackage(packageName);
mPresenter.onModelUpdated(this);
}
@Nullable
private String getStatusMessage(String packageName, List<ProjectionStatus> details) {
for (ProjectionStatus status : details) {
if (packageName.equals(status.getPackageName())) {
return getStatusMessage(status);
}
}
return null;
}
@Nullable
private String getStatusMessage(ProjectionStatus status) {
// The status message is as follows:
// - If there is an unambiguous "best" device, the name of that device.
// "Unambiguous" is defined as only one projecting device, or no projecting devices
// and only one non-projecting device.
// - If there are multiple projecting or non-projecting devices, "N devices", where N
// is the total number of projecting and non-projecting devices.
// - If there are no devices at all, no message. This should not happen if projection
// apps are behaving properly, but may happen in the event of a projection app bug.
String projectingDevice = null;
String nonProjectingDevice = null;
int projectingDeviceCount = 0;
int nonProjectingDeviceCount = 0;
for (ProjectionStatus.MobileDevice device : status.getConnectedMobileDevices()) {
if (device.isProjecting()) {
projectingDevice = device.getName();
projectingDeviceCount++;
} else {
nonProjectingDevice = device.getName();
nonProjectingDeviceCount++;
}
}
if (projectingDeviceCount == 1) {
return projectingDevice;
} else if (projectingDeviceCount == 0 && nonProjectingDeviceCount == 1) {
return nonProjectingDevice;
}
int totalDeviceCount = projectingDeviceCount + nonProjectingDeviceCount;
if (totalDeviceCount > 0) {
return MessageFormat.format(mResources.getString(R.string.projection_devices),
Map.of("count", totalDeviceCount));
} else {
return null;
}
}
}