Add surface view to IME
Applications that choose to hide the content area and draw their own view will use this surface area to draw on.
Fix: 171056783
Bug: 162268101
Bug: 170343703
Test: Manual
Change-Id: Ia6899bbe54773ae3752131dd662508bfe69c9a0e
Merged-In: Ia6899bbe54773ae3752131dd662508bfe69c9a0e
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java
index 07c3347..11d33f4 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java
@@ -102,8 +102,8 @@
onView(withId(R.id.car_ui_wideScreenSearchResultList)).check(matches(not(isDisplayed())));
onView(withId(R.id.car_ui_wideScreenErrorMessage)).check(matches(not(isDisplayed())));
onView(withId(R.id.car_ui_wideScreenError)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.car_ui_contentAreaAutomotive)).check(matches(not(isDisplayed())));
- onView(withId(R.id.car_ui_contentAreaAutomotive)).check(matches(isDisplayed()));
onView(withId(R.id.car_ui_wideScreenExtractedTextIcon)).check(matches(isDisplayed()));
onView(withId(R.id.car_ui_wideScreenClearData)).check(matches(isDisplayed()));
onView(withId(R.id.car_ui_fullscreenArea)).check(matches(isDisplayed()));
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java
index 98fddc7..953cf4d 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java
@@ -23,11 +23,16 @@
import android.graphics.drawable.Drawable;
import android.inputmethodservice.ExtractEditText;
import android.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.os.IBinder;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
+import android.view.SurfaceControlViewHost.SurfacePackage;
+import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
@@ -64,9 +69,9 @@
* <li>return {@link #onEvaluateFullscreenMode(boolean)} from
* {@link InputMethodService#onEvaluateFullscreenMode()}</li>
* <li>return the view created by
- * {@link #createWideScreenImeView(View, Context, InputMethodService)}
+ * {@link #createWideScreenImeView(View)}
* from {@link InputMethodService#onCreateInputView()}</li>
- * <li>{@link #onComputeInsets(InputMethodService.Insets, View)} should be called from
+ * <li>{@link #onComputeInsets(InputMethodService.Insets) should be called from
* {@link InputMethodService#onComputeInsets(InputMethodService.Insets)}</li>
* <li>{@link #onAppPrivateCommand(String, Bundle) should be called from {
* @link InputMethodService#onAppPrivateCommand(String, Bundle)}}</li>
@@ -144,9 +149,20 @@
// ArrayList<Integer>
public static final String SEARCH_RESULT_SECONDARY_IMAGE_RES_ID_LIST =
"search_result_image_list";
+ // key used to provide the surface package information by the application to the IME. IME
+ // will send the surface info each time its being displayed.
+ public static final String CONTENT_AREA_SURFACE_PACKAGE = "content_area_surface_package";
+ // key to provide the host token of surface view by IME to the application.
+ public static final String CONTENT_AREA_SURFACE_HOST_TOKEN = "content_area_surface_host_token";
+ // key to provide the display id of surface view by IME to the application.
+ public static final String CONTENT_AREA_SURFACE_DISPLAY_ID = "content_area_surface_display_id";
+ // key to provide the height of surface view by IME to the application.
+ public static final String CONTENT_AREA_SURFACE_HEIGHT = "content_area_surface_height";
+ // key to provide the width of surface view by IME to the application.
+ public static final String CONTENT_AREA_SURFACE_WIDTH = "content_area_surface_width";
private View mRootView;
- private Context mContext;
+ private final Context mContext;
@Nullable
private View mExtractActionAutomotive;
@NonNull
@@ -154,7 +170,8 @@
// whether to render the content area for automotive when in wide screen mode.
private boolean mImeRendersAllContent = true;
private boolean mAllowAppToHideContentArea;
- private ArrayList<CarUiListItem> mAutomotiveSearchItems = new ArrayList<>();
+ @Nullable
+ private ArrayList<CarUiListItem> mAutomotiveSearchItems;
@NonNull
private TextView mWideScreenDescriptionTitle;
@NonNull
@@ -180,8 +197,10 @@
@NonNull
private View mFullscreenArea;
@NonNull
+ private SurfaceView mContentAreaSurfaceView;
+ @NonNull
private FrameLayout mInputExtractEditTextContainer;
- private InputMethodService mInputMethodService;
+ private final InputMethodService mInputMethodService;
public CarUiImeWideScreenController(@NonNull Context context, @NonNull InputMethodService ims) {
mContext = context;
@@ -209,6 +228,8 @@
mContext.getResources().getBoolean(
R.bool.car_ui_ime_wide_screen_allow_app_hide_content_area);
+ mContentAreaSurfaceView = mRootView.requireViewById(R.id.car_ui_ime_surface);
+ mContentAreaSurfaceView.setZOrderOnTop(true);
mWideScreenDescriptionTitle =
mRootView.requireViewById(R.id.car_ui_wideScreenDescriptionTitle);
mWideScreenDescription = mRootView.requireViewById(R.id.car_ui_wideScreenDescription);
@@ -281,15 +302,27 @@
* @param data Any data to include with the command.
*/
public void onAppPrivateCommand(String action, Bundle data) {
- if (!isWideScreenMode() || data == null || !WIDE_SCREEN_ACTION.equals(action)) {
+ if (!isWideScreenMode() || !WIDE_SCREEN_ACTION.equals(action)) {
return;
}
resetAutomotiveWideScreenViews();
+ if (data == null) {
+ return;
+ }
if (mAllowAppToHideContentArea || (mInputEditorInfo != null && allowPackageList().contains(
mInputEditorInfo.packageName))) {
mImeRendersAllContent = data.getBoolean(REQUEST_RENDER_CONTENT_AREA, true);
}
+ if (data.getParcelable(CONTENT_AREA_SURFACE_PACKAGE) != null
+ && Build.VERSION.SDK_INT >= VERSION_CODES.R) {
+ SurfacePackage surfacePackage = (SurfacePackage) data.getParcelable(
+ CONTENT_AREA_SURFACE_PACKAGE);
+ mContentAreaSurfaceView.setChildSurfacePackage(surfacePackage);
+ mContentAreaSurfaceView.setVisibility(View.VISIBLE);
+ mContentAreaAutomotive.setVisibility(View.GONE);
+ }
+
String discTitle = data.getString(ADD_DESC_TITLE_TO_CONTENT_AREA);
if (!TextUtils.isEmpty(discTitle)) {
mWideScreenDescriptionTitle.setText(discTitle);
@@ -337,7 +370,7 @@
if (mExtractActionAutomotive != null) {
mExtractActionAutomotive.setVisibility(View.VISIBLE);
}
- if (!mAutomotiveSearchItems.isEmpty()) {
+ if (mAutomotiveSearchItems != null) {
mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
mRecyclerView.setVerticalScrollBarEnabled(true);
mRecyclerView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_LEFT);
@@ -352,7 +385,6 @@
}
private void loadSearchItems(Bundle data) {
- mAutomotiveSearchItems = new ArrayList<>();
ArrayList<String> itemIdList = data.getStringArrayList(SEARCH_RESULT_ITEM_ID);
ArrayList<String> titleList = data.getStringArrayList(SEARCH_RESULT_TITLE_LIST);
ArrayList<String> subTitleList = data.getStringArrayList(SEARCH_RESULT_SUB_TITLE_LIST);
@@ -365,6 +397,7 @@
if (itemIdList == null) {
return;
}
+ mAutomotiveSearchItems = new ArrayList<>();
for (int i = 0; i < itemIdList.size(); i++) {
int index = i;
CarUiImeSearchListItem searchItem;
@@ -494,6 +527,40 @@
if (hasLabel) {
intiExtractAction(textForImeAction);
}
+
+ sendSurfaceInfo();
+ }
+
+ /**
+ * Sends the information for surface view to the application on which they can draw on. This
+ * information will ONLY be sent if OEM allows an application to hide the content area and let
+ * it draw its own content.
+ */
+ private void sendSurfaceInfo() {
+ if (!mAllowAppToHideContentArea && !(mInputEditorInfo != null
+ && allowPackageList().contains(mInputEditorInfo.packageName))) {
+ return;
+ }
+ int displayId = mContentAreaSurfaceView.getDisplay().getDisplayId();
+ IBinder hostToken = mContentAreaSurfaceView.getHostToken();
+
+ Bundle bundle = new Bundle();
+ bundle.putBinder(CONTENT_AREA_SURFACE_HOST_TOKEN, hostToken);
+ bundle.putInt(CONTENT_AREA_SURFACE_DISPLAY_ID, displayId);
+ bundle.putInt(CONTENT_AREA_SURFACE_HEIGHT,
+ mContentAreaSurfaceView.getHeight() + getNavBarHeight());
+ bundle.putInt(CONTENT_AREA_SURFACE_WIDTH, mContentAreaSurfaceView.getWidth());
+
+ mInputConnection.performPrivateCommand(WIDE_SCREEN_ACTION, bundle);
+ }
+
+ private int getNavBarHeight() {
+ Resources resources = mContext.getResources();
+ int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
+ if (resourceId > 0) {
+ return resources.getDimensionPixelSize(resourceId);
+ }
+ return 0;
}
/**
@@ -560,8 +627,8 @@
}
private List<String> allowPackageList() {
- String value = mContext.getString(R.string.car_ui_ime_wide_screen_allowed_package_list);
- String[] packages = value.split(",");
+ String[] packages = mContext.getResources()
+ .getStringArray(R.array.car_ui_ime_wide_screen_allowed_package_list);
return Arrays.asList(packages);
}
@@ -617,6 +684,7 @@
private void resetAutomotiveWideScreenViews() {
mWideScreenDescriptionTitle.setVisibility(View.GONE);
+ mContentAreaSurfaceView.setVisibility(View.GONE);
mWideScreenErrorMessage.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.GONE);
mWideScreenDescription.setVisibility(View.GONE);
@@ -629,6 +697,7 @@
if (mExtractActionAutomotive != null) {
mExtractActionAutomotive.setVisibility(View.GONE);
}
+ mContentAreaAutomotive.setVisibility(View.VISIBLE);
mContentAreaAutomotive.setBackground(
mContext.getDrawable(R.drawable.car_ui_ime_wide_screen_no_content_background));
setExtractedEditTextBackground(R.drawable.car_ui_ime_wide_screen_input_area_tint_color);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java
index faedd7e..ab85ac8 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java
@@ -16,12 +16,17 @@
package com.android.car.ui.toolbar;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_DISPLAY_ID;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_HEIGHT;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_HOST_TOKEN;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_WIDTH;
import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_ITEM_ID;
import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SECONDARY_IMAGE_ID;
import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_CLEAR_DATA_ACTION;
import android.content.Context;
import android.os.Bundle;
+import android.os.IBinder;
import android.util.AttributeSet;
import android.widget.EditText;
@@ -33,28 +38,6 @@
* allow car-ui-lib to receive responses (like onClick events) from the IMS
*/
class CarUiEditText extends EditText {
- /**
- * Interface for {@link CarUiEditText} to support different actions and callbacks from IME
- * when running in wide screen mode.
- */
- interface PrivateImeCommandCallback {
- /**
- * Called when user clicks on an item in the search results.
- *
- * @param itemId the id of the item clicked. This will be the same id that was passed by the
- * application to the IMS template to display search results.
- */
- void onItemClicked(String itemId);
-
- /**
- * Called when user clicks on a secondary image within item in the search results.
- *
- * @param secondaryImageId the id of the secondary image clicked. This will be the same id
- * that was passed by the application to the IMS template to display
- * search results.
- */
- void onSecondaryImageClicked(String secondaryImageId);
- }
private final Set<PrivateImeCommandCallback> mPrivateImeCommandCallback = new HashSet<>();
@@ -104,6 +87,17 @@
}
}
+ int displayId = data.getInt(CONTENT_AREA_SURFACE_DISPLAY_ID);
+ int height = data.getInt(CONTENT_AREA_SURFACE_HEIGHT);
+ int width = data.getInt(CONTENT_AREA_SURFACE_WIDTH);
+ IBinder binder = data.getBinder(CONTENT_AREA_SURFACE_HOST_TOKEN);
+
+ if (binder != null) {
+ for (PrivateImeCommandCallback listener : mPrivateImeCommandCallback) {
+ listener.onSurfaceInfo(displayId, binder, height, width);
+ }
+ }
+
return false;
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/PrivateImeCommandCallback.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/PrivateImeCommandCallback.java
new file mode 100644
index 0000000..49e7fa4
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/PrivateImeCommandCallback.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ui.toolbar;
+
+import android.os.IBinder;
+
+/**
+ * Interface for {@link CarUiEditText} to support different actions and callbacks from IME
+ * when running in wide screen mode.
+ */
+public interface PrivateImeCommandCallback {
+ /**
+ * Called when user clicks on an item in the search results.
+ *
+ * @param itemId the id of the item clicked. This will be the same id that was passed by the
+ * application to the IMS template to display search results.
+ */
+ void onItemClicked(String itemId);
+
+ /**
+ * Called when user clicks on a secondary image within item in the search results.
+ *
+ * @param secondaryImageId the id of the secondary image clicked. This will be the same id
+ * that was passed by the application to the IMS template to display
+ * search results.
+ */
+ void onSecondaryImageClicked(String secondaryImageId);
+
+ /**
+ * Called when the edit text is clicked and IME is about to launch. IME provides the surface
+ * view information through this call that applications can use to display a view on the
+ * IME surface.
+ *
+ * This method will NOT be called if an OEM has not allowed an application to hide the
+ * content area.
+ */
+ void onSurfaceInfo(int displayId, IBinder binder, int height, int width);
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
index 55a044d..7b0fe0a 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
@@ -17,6 +17,7 @@
import static android.view.WindowInsets.Type.ime;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_PACKAGE;
import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_ITEM_ID;
import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_PRIMARY_IMAGE_RES_ID_LIST;
import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SECONDARY_IMAGE_ID;
@@ -28,14 +29,21 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
+import android.view.Display;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -43,6 +51,7 @@
import android.widget.ImageView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.car.ui.R;
@@ -68,7 +77,14 @@
private final int mStartPaddingWithoutIcon;
private final int mStartPadding;
private final int mEndPadding;
- private List<? extends CarUiImeSearchListItem> mWideScreenSearchItemList = new ArrayList<>();
+ @Nullable
+ private View mWideScreenImeContentAreaView;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+ private int mSurfaceHeight;
+ private int mSurfaceWidth;
+ private List<? extends CarUiImeSearchListItem> mWideScreenSearchItemList;
private final Map<String, CarUiImeSearchListItem> mIdToListItem = new HashMap<>();
private Set<Toolbar.OnSearchListener> mSearchListeners = Collections.emptySet();
@@ -152,31 +168,7 @@
if (mSearchText instanceof CarUiEditText) {
((CarUiEditText) mSearchText).registerOnPrivateImeCommandListener(
- new CarUiEditText.PrivateImeCommandCallback() {
- @Override
- public void onItemClicked(String itemId) {
- CarUiImeSearchListItem item = mIdToListItem.get(itemId);
- if (item != null) {
- CarUiContentListItem.OnClickListener listener =
- item.getOnClickListener();
- if (listener != null) {
- listener.onClick(item);
- }
- }
- }
-
- @Override
- public void onSecondaryImageClicked(String secondaryImageId) {
- CarUiImeSearchListItem item = mIdToListItem.get(secondaryImageId);
- if (item != null) {
- CarUiContentListItem.OnClickListener listener =
- item.getSupplementalIconOnClickListener();
- if (listener != null) {
- listener.onClick(item);
- }
- }
- }
- });
+ new SearchViewImeCallback());
}
}
@@ -192,11 +184,29 @@
searchContainer.getRootView().setOnApplyWindowInsetsListener((v, insets) -> {
if (insets.isVisible(ime())) {
displaySearchWideScreen();
+ mHandler.post(() -> {
+ if (mSurfaceControlViewHost != null && mWideScreenImeContentAreaView != null
+ && mSurfaceControlViewHost.getView() == null) {
+ mSurfaceControlViewHost.setView(
+ mWideScreenImeContentAreaView, mSurfaceWidth, mSurfaceHeight);
+ }
+ });
}
return insets;
});
}
+ void setViewToImeWideScreenSurface(View view) {
+ if (view == null && mSurfaceControlViewHost != null) {
+ mSurfaceControlViewHost.release();
+ }
+
+ if (view != null && view.getParent() != null) {
+ throw new IllegalStateException("view should not have a parent");
+ }
+ mWideScreenImeContentAreaView = view;
+ }
+
private boolean isEnter(KeyEvent event) {
boolean result = false;
if (event != null) {
@@ -250,15 +260,24 @@
* template.
*/
public void setSearchItemsForWideScreen(List<? extends CarUiImeSearchListItem> searchItems) {
- mWideScreenSearchItemList = new ArrayList<>(searchItems);
+ mWideScreenSearchItemList = searchItems != null ? new ArrayList<>(searchItems) : null;
displaySearchWideScreen();
}
private void displaySearchWideScreen() {
mIdToListItem.clear();
- if (mWideScreenSearchItemList.isEmpty()) {
+ // mWideScreenImeContentAreaView will only be set when running in widescreen mode and
+ // apps allowed by OEMs are trying to set their own view. In that case we did not want to
+ // send the information to IME for templatized solution.
+ if (mWideScreenImeContentAreaView != null) {
return;
}
+
+ if (mWideScreenSearchItemList == null) {
+ mInputMethodManager.sendAppPrivateCommand(mSearchText, WIDE_SCREEN_ACTION, null);
+ return;
+ }
+
ArrayList<String> itemIdList = new ArrayList<>();
ArrayList<String> titleList = new ArrayList<>();
ArrayList<String> subTitleList = new ArrayList<>();
@@ -357,4 +376,62 @@
mSearchText.setText(query);
mSearchText.setSelection(mSearchText.getText().length());
}
+
+ private class SearchViewImeCallback implements PrivateImeCommandCallback {
+
+ @Override
+ public void onItemClicked(String itemId) {
+ CarUiImeSearchListItem item = mIdToListItem.get(itemId);
+ if (item != null) {
+ CarUiContentListItem.OnClickListener listener =
+ item.getOnClickListener();
+ if (listener != null) {
+ listener.onClick(item);
+ }
+ }
+ }
+
+ @Override
+ public void onSecondaryImageClicked(String secondaryImageId) {
+ CarUiImeSearchListItem item = mIdToListItem.get(secondaryImageId);
+ if (item != null) {
+ CarUiContentListItem.OnClickListener listener =
+ item.getSupplementalIconOnClickListener();
+ if (listener != null) {
+ listener.onClick(item);
+ }
+ }
+ }
+
+ @Override
+ public void onSurfaceInfo(int displayId, IBinder binder, int height,
+ int width) {
+ if (Build.VERSION.SDK_INT < VERSION_CODES.R
+ || mWideScreenImeContentAreaView == null) {
+ // SurfaceControlViewHost is only available on R and above
+ return;
+ }
+
+ DisplayManager dm = (DisplayManager) getContext().getSystemService(
+ Context.DISPLAY_SERVICE);
+
+ Display display = dm.getDisplay(displayId);
+
+ if (mSurfaceControlViewHost != null) {
+ mSurfaceControlViewHost.release();
+ }
+
+ mSurfaceControlViewHost = new SurfaceControlViewHost(getContext(),
+ display, binder);
+
+ mSurfaceHeight = height;
+ mSurfaceWidth = width;
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(CONTENT_AREA_SURFACE_PACKAGE,
+ mSurfaceControlViewHost.getSurfacePackage());
+ mInputMethodManager.sendAppPrivateCommand(mSearchText,
+ WIDE_SCREEN_ACTION, bundle);
+ }
+ }
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
index c9a96ce..45e344d 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
@@ -22,6 +22,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.DrawableRes;
@@ -273,6 +274,7 @@
/**
* Gets the {@link TabLayout} for this toolbar.
+ *
* @deprecated Use other tab-related functions in the ToolbarController interface.
*/
@Deprecated
@@ -653,13 +655,46 @@
}
/**
+ * Returns true if the toolbar can display search result items. One example of this is when the
+ * system is configured to display search items in the IME instead of in the app.
+ */
+ @Override
+ public boolean canShowSearchResultItems() {
+ return mController.canShowSearchResultItems();
+ }
+
+ /**
+ * Returns true if the app is allowed to set search results view.
+ */
+ @Override
+ public boolean canShowSearchResultsView() {
+ return mController.canShowSearchResultsView();
+ }
+
+ /**
+ * Add a view within a container that will animate with the wide screen IME to display search
+ * results.
+ *
+ * <p>Note: Apps can only call this method if the package name is allowed via OEM to render
+ * their view. To check if the application have the permission to do so or not first call
+ * {@link #canShowSearchResultsView()}. If the app is not allowed this method will throw an
+ * {@link RuntimeException}
+ *
+ * @param view to be added in the container.
+ */
+ @Override
+ public void setSearchResultsView(View view) {
+ mController.setSearchResultsView(view);
+ }
+
+ /**
* Sets a list of search items to be displayed in the IME window when running as a wide screen
* mode. This should be called each time the list is updated. For example, when a user is typing
* in the input field and the list gets filtered this method should be invoked each time.
*/
@Override
- public void setSearchItemsForWideScreen(List<? extends CarUiImeSearchListItem> searchItems) {
- mController.setSearchItemsForWideScreen(searchItems);
+ public void setSearchResultItems(List<? extends CarUiImeSearchListItem> searchItems) {
+ mController.setSearchResultItems(searchItems);
}
/** Registers a new {@link OnSearchCompletedListener} to the list of listeners. */
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
index 6bed46c..654fe93 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
@@ -17,6 +17,7 @@
package com.android.car.ui.toolbar;
import android.graphics.drawable.Drawable;
+import android.view.View;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
@@ -80,6 +81,7 @@
/**
* Gets the {@link TabLayout} for this toolbar.
+ *
* @deprecated Use other tab-related functions in this interface.
*/
@Deprecated
@@ -277,10 +279,36 @@
boolean unregisterOnSearchListener(Toolbar.OnSearchListener listener);
/**
- * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
- * template.
+ * Returns true if the toolbar can display search result items. One example of this is when the
+ * system is configured to display search items in the IME instead of in the app.
*/
- void setSearchItemsForWideScreen(List<? extends CarUiImeSearchListItem> searchItems);
+ boolean canShowSearchResultItems();
+
+ /**
+ * Returns true if the app is allowed to set search results view.
+ */
+ boolean canShowSearchResultsView();
+
+ /**
+ * Add a view within a container that will animate with the wide screen IME to display search
+ * results.
+ *
+ * <p>Note: Apps can only call this method if the package name is allowed via OEM to render
+ * their view. To check if the application have the permission to do so or not first call
+ * {@link #canShowSearchResultsView()}. If the app is not allowed this method will throw an
+ * {@link RuntimeException}
+ *
+ * @param view to be added in the container.
+ */
+ void setSearchResultsView(View view);
+
+ /**
+ * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
+ * template. This method should be called when system is running in a wide screen mode. Apps
+ * can check that by using {@link #canShowSearchResultItems()}
+ * Else, this method will throw an {@link RuntimeException}
+ */
+ void setSearchResultItems(List<? extends CarUiImeSearchListItem> searchItems);
/** Registers a new {@link Toolbar.OnSearchCompletedListener} to the list of listeners. */
void registerOnSearchCompletedListener(Toolbar.OnSearchCompletedListener listener);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
index 92d57b6..e1397a4 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
@@ -21,6 +21,7 @@
import static android.view.View.VISIBLE;
import static com.android.car.ui.utils.CarUiUtils.findViewByRefId;
+import static com.android.car.ui.utils.CarUiUtils.getBooleanSystemProperty;
import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
import android.app.Activity;
@@ -51,6 +52,7 @@
import com.android.car.ui.utils.CarUxRestrictionsUtil;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -112,6 +114,7 @@
private AlertDialog mOverflowDialog;
private boolean mNavIconSpaceReserved;
private boolean mLogoFillsNavIconSpace;
+ private View mViewForContentAreaInWideScreenMode;
private boolean mShowLogo;
private List<? extends CarUiImeSearchListItem> mSearchItems;
private final ProgressBarController mProgressBar;
@@ -268,6 +271,7 @@
/**
* Gets the {@link TabLayout} for this toolbar.
+ *
* @deprecated Use other tab-related functions in the ToolbarController interface.
*/
@Deprecated
@@ -714,6 +718,9 @@
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mSearchViewContainer.addView(searchView, layoutParams);
+ if (canShowSearchResultsView()) {
+ searchView.setViewToImeWideScreenSurface(mViewForContentAreaInWideScreenMode);
+ }
searchView.installWindowInsetsListener(mSearchViewContainer);
@@ -817,13 +824,93 @@
mOverflowButton.setVisible(showButtons && countVisibleOverflowItems() > 0);
}
+ /**
+ * Return the list of package names allowed to hide the content area in wide screen IME.
+ */
+ private List<String> allowPackageList(Context context) {
+ String[] packages = context.getResources()
+ .getStringArray(R.array.car_ui_ime_wide_screen_allowed_package_list);
+ return Arrays.asList(packages);
+ }
+
+ /**
+ * Returns true if the toolbar can display search result items. One example of this is when the
+ * system is configured to display search items in the IME instead of in the app.
+ */
+ @Override
+ public boolean canShowSearchResultItems() {
+ return isWideScreenMode(mContext);
+ }
+
+ /**
+ * Returns whether or not system is running in a wide screen mode.
+ */
+ private static boolean isWideScreenMode(Context context) {
+ return getBooleanSystemProperty(context.getResources(),
+ R.string.car_ui_ime_wide_screen_system_property_name, false);
+ }
+
+ /**
+ * Returns true if the app is allowed to set search results view.
+ */
+ @Override
+ public boolean canShowSearchResultsView() {
+ boolean allowAppsToHideContentArea = mContext.getResources().getBoolean(
+ R.bool.car_ui_ime_wide_screen_allow_app_hide_content_area);
+ return isWideScreenMode(mContext) && (allowPackageList(mContext).contains(
+ mContext.getPackageName()) || allowAppsToHideContentArea);
+ }
+
+ /**
+ * Add a view within a container that will animate with the wide screen IME to display search
+ * results.
+ *
+ * <p>Note: Apps can only call this method if the package name is allowed via OEM to render
+ * their view. If the app is not allowed this method will throw an
+ * {@link IllegalAccessException}
+ *
+ * @param view to be added in the container.
+ */
+ @Override
+ public void setSearchResultsView(View view) {
+ if (!canShowSearchResultsView()) {
+ throw new RuntimeException(
+ "not allowed to add view to wide screen IME, package name: "
+ + mContext.getPackageName());
+ }
+
+ if (mSearchView != null) {
+ mSearchView.setViewToImeWideScreenSurface(view);
+ }
+
+ mViewForContentAreaInWideScreenMode = view;
+ }
+
+ /**
+ * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
+ * template. This method should be called when system is running in a wide screen mode. Apps
+ * can check that by using {@link #canShowSearchResultItems()}
+ * Else, this method would have no effect.
+ */
+ @Override
+ public void setSearchResultItems(List<? extends CarUiImeSearchListItem> searchItems) {
+ if (!canShowSearchResultItems()) {
+ throw new RuntimeException(
+ "system not in wide screen mode, not allowed to set search result items ");
+ }
+ mSearchItems = searchItems;
+ if (mSearchView != null) {
+ mSearchView.setSearchItemsForWideScreen(searchItems);
+ }
+ }
+
+
/** Gets the current {@link Toolbar.State} of the toolbar. */
@Override
public Toolbar.State getState() {
return mState;
}
-
/**
* Registers a new {@link Toolbar.OnHeightChangedListener} to the list of listeners. Register a
* {@link com.android.car.ui.recyclerview.CarUiRecyclerView} only if there is a toolbar at
@@ -871,14 +958,6 @@
return mOnSearchListeners.remove(listener);
}
- @Override
- public void setSearchItemsForWideScreen(List<? extends CarUiImeSearchListItem> searchItems) {
- mSearchItems = searchItems;
- if (mSearchView != null) {
- mSearchView.setSearchItemsForWideScreen(searchItems);
- }
- }
-
/** Registers a new {@link Toolbar.OnSearchCompletedListener} to the list of listeners. */
@Override
public void registerOnSearchCompletedListener(Toolbar.OnSearchCompletedListener listener) {
diff --git a/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
index 43c582a..3ad5398 100644
--- a/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
@@ -16,6 +16,7 @@
<resources>
<overlayable name="car-ui-lib">
<policy type="public">
+ <item type="array" name="car_ui_ime_wide_screen_allowed_package_list"/>
<item type="attr" name="CarUiToolbarStyle"/>
<item type="attr" name="barrierDirection"/>
<item type="attr" name="carUiPreferenceStyle"/>
@@ -322,6 +323,7 @@
<item type="id" name="car_ui_focus_area"/>
<item type="id" name="car_ui_fullscreenArea"/>
<item type="id" name="car_ui_imeWideScreenInputArea"/>
+ <item type="id" name="car_ui_ime_surface"/>
<item type="id" name="car_ui_inputExtractActionAutomotive"/>
<item type="id" name="car_ui_inputExtractEditTextContainer"/>
<item type="id" name="car_ui_list_item_end_guideline"/>
@@ -450,7 +452,6 @@
<item type="string" name="car_ui_dialog_preference_negative"/>
<item type="string" name="car_ui_dialog_preference_positive"/>
<item type="string" name="car_ui_ellipsis"/>
- <item type="string" name="car_ui_ime_wide_screen_allowed_package_list"/>
<item type="string" name="car_ui_ime_wide_screen_system_property_name"/>
<item type="string" name="car_ui_installer_process_name"/>
<item type="string" name="car_ui_preference_switch_off"/>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_ims_wide_screen_input_view.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_ims_wide_screen_input_view.xml
index f630bdc..fd7539b 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_ims_wide_screen_input_view.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_ims_wide_screen_input_view.xml
@@ -127,10 +127,18 @@
android:layout_height="match_parent"
android:background="@color/car_ui_ime_wide_screen_divider_color"/>
+ <SurfaceView
+ android:id="@id/car_ui_ime_surface"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:focusable="false"/>
+
<RelativeLayout
android:id="@id/car_ui_contentAreaAutomotive"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:visibility="gone"
android:background="@drawable/car_ui_ime_wide_screen_no_content_background">
<androidx.recyclerview.widget.RecyclerView
android:id="@id/car_ui_wideScreenSearchResultList"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml b/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
index 2f0b555..44a162e 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
@@ -24,6 +24,7 @@
<item type="id" name="car_ui_wideScreenInputArea"/>
<item type="id" name="car_ui_imeWideScreenInputArea"/>
<item type="id" name="car_ui_closeKeyboard"/>
+ <item type="id" name="car_ui_ime_surface"/>
<item type="id" name="car_ui_fullscreenArea"/>
<item type="id" name="car_ui_wideScreenErrorMessage"/>
<item type="id" name="car_ui_contentAreaAutomotive"/>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
index 8956411..a3f0539 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
@@ -70,7 +70,8 @@
should be separated by a ",". For example, "com.package1,com.package2,com.package3" will allow
packages "com.package1", "com.package2" and "com.package3" to hide the content area.
-->
- <string name="car_ui_ime_wide_screen_allowed_package_list" translatable="false"></string>
+ <string-array name="car_ui_ime_wide_screen_allowed_package_list" translatable="false">
+ </string-array>
<!-- Name of system property used to determine when wide screen mode is used. -->
<string name="car_ui_ime_wide_screen_system_property_name" translatable="false">
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java
index e7bc6f5..1d6a27d 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java
@@ -66,6 +66,8 @@
*/
public class WideScreenImeActivity extends AppCompatActivity implements InsetsChangedListener {
+ private static final String TAG = "WideScreenImeActivity";
+
private final List<MenuItem> mMenuItems = new ArrayList<>();
private final List<Pair<CharSequence, View.OnFocusChangeListener>> mEditText =
new ArrayList<>();
@@ -125,7 +127,26 @@
searchItems.add(item);
// initial list to display in search view.
- toolbar.setSearchItemsForWideScreen(searchItems);
+ if (toolbar.canShowSearchResultItems()) {
+ toolbar.setSearchResultItems(searchItems);
+ }
+
+ LayoutInflater inflater = LayoutInflater.from(this);
+ View contentArea = inflater.inflate(R.layout.ime_wide_screen_dummy_view, null, true);
+
+ if (toolbar.canShowSearchResultsView()) {
+ toolbar.setSearchResultsView(contentArea);
+ }
+
+ contentArea.findViewById(R.id.button_1).setOnClickListener(v ->
+ Toast.makeText(this, "Button 1 clicked", Toast.LENGTH_SHORT).show()
+ );
+
+ contentArea.findViewById(R.id.button_2).setOnClickListener(v -> {
+ Toast.makeText(this, "Clearing the view...", Toast.LENGTH_SHORT).show();
+ toolbar.setSearchResultsView(null);
+ }
+ );
toolbar.registerOnSearchListener((query) -> {
count[0]++;
@@ -139,7 +160,9 @@
item1.setOnItemClickedListener(mainClickListener);
searchItems.add(item1);
- toolbar.setSearchItemsForWideScreen(searchItems);
+ if (toolbar.canShowSearchResultItems()) {
+ toolbar.setSearchResultItems(searchItems);
+ }
});
mMenuItems.add(MenuItem.builder(this)
@@ -171,7 +194,7 @@
mSecondaryImageResId.add(R.drawable.ic_launcher);
}
- mEditText.add(Pair.create("Show IME list view", this::showImeListView));
+ mEditText.add(Pair.create("Show IME list view", this::showImeListView));
mEditText.add(Pair.create("Add icon to extracted view", this::addIconToExtractedView));
diff --git a/car-ui-lib/paintbooth/src/main/res/layout/ime_wide_screen_dummy_view.xml b/car-ui-lib/paintbooth/src/main/res/layout/ime_wide_screen_dummy_view.xml
new file mode 100644
index 0000000..1118ce9
--- /dev/null
+++ b/car-ui-lib/paintbooth/src/main/res/layout/ime_wide_screen_dummy_view.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/car_ui_activity_background">
+ <Button
+ android:id="@+id/button_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button 1"
+ android:theme="@android:style/Theme.DeviceDefault"
+ android:textSize="@dimen/car_ui_ime_wide_screen_action_button_text_size"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+ <Button
+ android:id="@+id/button_2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:text="Clear View"
+ android:theme="@android:style/Theme.DeviceDefault"
+ android:textSize="@dimen/car_ui_ime_wide_screen_action_button_text_size"
+ app:layout_constraintTop_toBottomOf="@+id/button_1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
+
+
diff --git a/car-ui-lib/tests/apitest/current.xml b/car-ui-lib/tests/apitest/current.xml
index f037dbd..461ce2c 100644
--- a/car-ui-lib/tests/apitest/current.xml
+++ b/car-ui-lib/tests/apitest/current.xml
@@ -1,6 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--This file is AUTO GENERATED, DO NOT EDIT MANUALLY.-->
<resources>
+ <public type="array" name="car_ui_ime_wide_screen_allowed_package_list"/>
<public type="attr" name="CarUiToolbarStyle"/>
<public type="attr" name="carUiPreferenceStyle"/>
<public type="attr" name="carUiRecyclerViewStyle"/>
@@ -257,6 +258,7 @@
<public type="id" name="car_ui_focus_area"/>
<public type="id" name="car_ui_fullscreenArea"/>
<public type="id" name="car_ui_imeWideScreenInputArea"/>
+ <public type="id" name="car_ui_ime_surface"/>
<public type="id" name="car_ui_inputExtractActionAutomotive"/>
<public type="id" name="car_ui_inputExtractEditTextContainer"/>
<public type="id" name="car_ui_list_item_end_guideline"/>
@@ -385,7 +387,6 @@
<public type="string" name="car_ui_dialog_preference_negative"/>
<public type="string" name="car_ui_dialog_preference_positive"/>
<public type="string" name="car_ui_ellipsis"/>
- <public type="string" name="car_ui_ime_wide_screen_allowed_package_list"/>
<public type="string" name="car_ui_ime_wide_screen_system_property_name"/>
<public type="string" name="car_ui_installer_process_name"/>
<public type="string" name="car_ui_preference_switch_off"/>
diff --git a/car-ui-lib/tests/apitest/resource_utils.py b/car-ui-lib/tests/apitest/resource_utils.py
index 763c5a0..7da5642 100755
--- a/car-ui-lib/tests/apitest/resource_utils.py
+++ b/car-ui-lib/tests/apitest/resource_utils.py
@@ -108,6 +108,8 @@
resName = resource.get('name')
resType = resource.tag
+ if resType == "string-array":
+ resType = "array"
if resource.tag == 'item' or resource.tag == 'public':
resType = resource.get('type')