blob: 680e17ebe643ffba6389e1a096209cfea958d123 [file] [log] [blame]
/*
* Copyright (C) 2023 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.chassis.car.ui.plugin;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build.VERSION;
import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.car.ui.CarUiText;
import com.android.car.ui.appstyledview.AppStyledViewControllerImpl;
import com.android.car.ui.plugin.PluginContextWrapper;
import com.android.car.ui.plugin.oemapis.FocusAreaOEMV1;
import com.android.car.ui.plugin.oemapis.FocusParkingViewOEMV1;
import com.android.car.ui.plugin.oemapis.InsetsOEMV1;
import com.android.car.ui.plugin.oemapis.PluginFactoryOEMV6;
import com.android.car.ui.plugin.oemapis.TextOEMV1;
import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV3;
import com.android.car.ui.plugin.oemapis.preference.PreferenceOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.ContentListItemOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.HeaderListItemOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.ListItemOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV2;
import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV2;
import com.android.car.ui.recyclerview.CarUiContentListItem;
import com.android.car.ui.recyclerview.CarUiHeaderListItem;
import com.android.car.ui.recyclerview.CarUiListItem;
import com.android.car.ui.recyclerview.CarUiListItemAdapter;
import com.android.car.ui.recyclerview.CarUiRecyclerViewImpl;
import com.android.car.ui.toolbar.ToolbarControllerImpl;
import com.android.car.ui.utils.CarUiUtils;
import com.chassis.car.ui.plugin.appstyledview.AppStyledViewControllerAdapterProxy;
import com.chassis.car.ui.plugin.preference.PreferenceAdapterProxy;
import com.chassis.car.ui.plugin.recyclerview.CarListItemAdapterAdapterProxy;
import com.chassis.car.ui.plugin.recyclerview.RecyclerViewAdapterProxy;
import com.chassis.car.ui.plugin.toolbar.ToolbarAdapterProxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
/**
* An implication of the plugin factory that delegates back to the car-ui-lib implementation.
* The main benefit of this is so that customizations can be applied to the car-ui-lib via a RRO
* without the need to target each app specifically. Note: it only applies to the components that
* come through the plugin system.
*/
public class PluginFactoryImpl implements PluginFactoryOEMV6 {
private final Context mPluginContext;
Map<Context, Context> mAppToPluginContextMap = new WeakHashMap<>();
public PluginFactoryImpl(Context pluginContext) {
mPluginContext = pluginContext;
}
@Override
public void setRotaryFactories(
com.android.car.ui.plugin.oemapis.Function<Context, FocusParkingViewOEMV1> function,
com.android.car.ui.plugin.oemapis.Function<Context, FocusAreaOEMV1> function1) {
}
@Nullable
@Override
public ToolbarControllerOEMV2 installBaseLayoutAround(@NonNull Context context,
@NonNull View view,
@Nullable com.android.car.ui.plugin.oemapis.Consumer<InsetsOEMV1> consumer,
boolean b,
boolean b1) {
Context pluginContext = getPluginUiContext(context, mPluginContext);
ToolbarControllerImpl toolbarController = new ToolbarControllerImpl(view);
return new ToolbarAdapterProxy(pluginContext, toolbarController);
}
@Override
public boolean customizesBaseLayout() {
return false;
}
@Override
public PreferenceOEMV1 createCarUiPreference(@NonNull Context sourceContext) {
Context pluginContext = getPluginUiContext(sourceContext, mPluginContext);
return new PreferenceAdapterProxy(pluginContext, sourceContext);
}
@Nullable
@Override
public AppStyledViewControllerOEMV3 createAppStyledView(@NonNull Context context) {
Context pluginContext = getPluginUiContext(context, mPluginContext);
// build the app styled controller that will be delegated to
AppStyledViewControllerImpl appStyledViewController = new AppStyledViewControllerImpl(
pluginContext);
return new AppStyledViewControllerAdapterProxy(appStyledViewController);
}
@Nullable
@Override
public RecyclerViewOEMV2 createRecyclerView(@NonNull Context context,
@Nullable RecyclerViewAttributesOEMV1 recyclerViewAttributesOEMV1) {
Context pluginContext = getPluginUiContext(context, mPluginContext);
CarUiRecyclerViewImpl recyclerView =
new CarUiRecyclerViewImpl(pluginContext, recyclerViewAttributesOEMV1);
return new RecyclerViewAdapterProxy(pluginContext, recyclerView,
recyclerViewAttributesOEMV1);
}
@Override
public AdapterOEMV1<? extends ViewHolderOEMV1> createListItemAdapter(
List<ListItemOEMV1> items) {
// TODO: add this here? Context pluginContext = getPluginUiContext(context, mPluginContext);
List<? extends CarUiListItem> staticItems = CarUiUtils.convertList(items,
PluginFactoryImpl::toStaticListItem);
// Build the CarUiListItemAdapter that will be delegated to
CarUiListItemAdapter carUiListItemAdapter = new CarUiListItemAdapter(staticItems);
return new CarListItemAdapterAdapterProxy(carUiListItemAdapter, mPluginContext);
}
/**
* The plugin was passed the list items as {@link ListItemOEMV1}s and thus must be converted
* back to use the "original" {@link CarUiListItem}s that's expected by the
* {@link CarUiListItemAdapter}.
*/
private static CarUiListItem toStaticListItem(ListItemOEMV1 item) {
if (item instanceof HeaderListItemOEMV1) {
HeaderListItemOEMV1 header = (HeaderListItemOEMV1) item;
return new CarUiHeaderListItem(header.getTitle(), header.getBody());
} else if (item instanceof ContentListItemOEMV1) {
ContentListItemOEMV1 contentItem = (ContentListItemOEMV1) item;
CarUiContentListItem listItem = new CarUiContentListItem(
toCarUiContentListItemAction(contentItem.getAction()));
if (contentItem.getTitle() != null) {
listItem.setTitle(toCarUiText(contentItem.getTitle()));
}
if (contentItem.getBody() != null) {
listItem.setBody(toCarUiText(contentItem.getBody()));
}
listItem.setIcon(contentItem.getIcon());
listItem.setPrimaryIconType(
toCarUiConteentListItemIconType(contentItem.getPrimaryIconType()));
if (contentItem.getAction() == ContentListItemOEMV1.Action.ICON) {
CarUiContentListItem.OnClickListener listener =
contentItem.getSupplementalIconOnClickListener() != null
? carUiContentListItem ->
contentItem.getSupplementalIconOnClickListener().accept(
contentItem) : null;
listItem.setSupplementalIcon(contentItem.getSupplementalIcon(), listener);
}
if (contentItem.getOnClickListener() != null) {
CarUiContentListItem.OnClickListener listener =
contentItem.getOnClickListener() != null
? carUiContentListItem ->
contentItem.getOnClickListener().accept(
contentItem) : null;
listItem.setOnItemClickedListener(listener);
}
listItem.setOnCheckedChangeListener((carUiContentListItem, checked) ->
carUiContentListItem.setChecked(checked));
listItem.setActionDividerVisible(contentItem.isActionDividerVisible());
listItem.setEnabled(contentItem.isEnabled());
listItem.setChecked(contentItem.isChecked());
listItem.setActivated(contentItem.isActivated());
listItem.setSecure(contentItem.isSecure());
return listItem;
} else {
throw new IllegalStateException("Unknown view type.");
}
}
private static CarUiText toCarUiText(TextOEMV1 text) {
return new CarUiText.Builder(text.getTextVariants()).setMaxChars(
text.getMaxChars()).setMaxLines(text.getMaxLines()).build();
}
private static List<CarUiText> toCarUiText(List<TextOEMV1> lines) {
List<CarUiText> oemLines = new ArrayList<>();
for (TextOEMV1 line : lines) {
oemLines.add(new CarUiText.Builder(line.getTextVariants()).setMaxChars(
line.getMaxChars()).setMaxLines(line.getMaxLines()).build());
}
return oemLines;
}
private static CarUiContentListItem.Action toCarUiContentListItemAction(
ContentListItemOEMV1.Action action) {
switch (action) {
case NONE:
return CarUiContentListItem.Action.NONE;
case SWITCH:
return CarUiContentListItem.Action.SWITCH;
case CHECK_BOX:
return CarUiContentListItem.Action.CHECK_BOX;
case RADIO_BUTTON:
return CarUiContentListItem.Action.RADIO_BUTTON;
case ICON:
return CarUiContentListItem.Action.ICON;
case CHEVRON:
return CarUiContentListItem.Action.CHEVRON;
default:
throw new IllegalStateException("Unexpected list item action type");
}
}
private static CarUiContentListItem.IconType toCarUiConteentListItemIconType(
ContentListItemOEMV1.IconType iconType) {
switch (iconType) {
case CONTENT:
return CarUiContentListItem.IconType.CONTENT;
case STANDARD:
return CarUiContentListItem.IconType.STANDARD;
case AVATAR:
return CarUiContentListItem.IconType.AVATAR;
default:
throw new IllegalStateException("Unexpected list item icon type");
}
}
/**
* This method tries to return a ui-context for usage in the plugin that has the same
* configuration as the given source ui context.
*
* @param sourceContext a UI context, normally an Activity context.
*/
private Context getPluginUiContext(@NonNull Context sourceContext,
@NonNull Context pluginContext) {
Context uiContext = mAppToPluginContextMap.get(sourceContext);
if (uiContext == null) {
uiContext = pluginContext;
if (VERSION.SDK_INT >= 34 /* Android U */ && !uiContext.isUiContext()) {
// On U and above we need a UiContext for initializing the proxy plugin.
uiContext = pluginContext
.createWindowContext(sourceContext.getDisplay(), TYPE_APPLICATION, null);
}
}
Configuration currentConfiguration = uiContext.getResources()
.getConfiguration();
Configuration newConfiguration = sourceContext.getResources().getConfiguration();
if (currentConfiguration.diff(newConfiguration) != 0) {
uiContext = uiContext.createConfigurationContext(newConfiguration);
}
uiContext = new PluginContextWrapper(uiContext);
// add a custom layout inflater that can handle things like CarUiTextView that is in the
// layout files of the car-ui-lib static implementation
LayoutInflater inflater = LayoutInflater.from(uiContext);
if (inflater.getFactory2() == null) {
inflater.setFactory2(new CarUiProxyLayoutInflaterFactory());
}
mAppToPluginContextMap.put(sourceContext, uiContext);
return uiContext;
}
}