1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.ui.paintbooth.widescreenime; 18 19 import static android.view.inputmethod.EditorInfo.IME_FLAG_NO_EXTRACT_UI; 20 21 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TITLE_TO_CONTENT_AREA; 22 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TO_CONTENT_AREA; 23 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_ERROR_DESC_TO_INPUT_AREA; 24 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.REQUEST_RENDER_CONTENT_AREA; 25 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ACTION; 26 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_EXTRACTED_TEXT_ICON_RES_ID; 27 28 import android.content.Context; 29 import android.graphics.drawable.Drawable; 30 import android.os.Bundle; 31 import android.util.Log; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 import android.view.View.OnFocusChangeListener; 35 import android.view.ViewGroup; 36 import android.view.inputmethod.InputMethodManager; 37 import android.widget.EditText; 38 import android.widget.Toast; 39 40 import androidx.annotation.NonNull; 41 import androidx.appcompat.app.AppCompatActivity; 42 import androidx.core.content.ContextCompat; 43 import androidx.recyclerview.widget.RecyclerView; 44 45 import com.android.car.ui.FocusArea; 46 import com.android.car.ui.baselayout.Insets; 47 import com.android.car.ui.baselayout.InsetsChangedListener; 48 import com.android.car.ui.core.CarUi; 49 import com.android.car.ui.imewidescreen.CarUiImeSearchListItem; 50 import com.android.car.ui.paintbooth.R; 51 import com.android.car.ui.recyclerview.CarUiContentListItem; 52 import com.android.car.ui.recyclerview.CarUiRecyclerView; 53 import com.android.car.ui.toolbar.MenuItem; 54 import com.android.car.ui.toolbar.NavButtonMode; 55 import com.android.car.ui.toolbar.SearchConfig; 56 import com.android.car.ui.toolbar.SearchMode; 57 import com.android.car.ui.toolbar.ToolbarController; 58 59 import java.util.ArrayList; 60 import java.util.List; 61 62 /** 63 * Activity that shows different scenarios for wide screen ime. 64 */ 65 public class WideScreenImeActivity extends AppCompatActivity implements InsetsChangedListener { 66 private static final String TAG = "PaintBooth"; 67 68 private final List<MenuItem> mMenuItems = new ArrayList<>(); 69 private final List<ListElement> mWidescreenItems = new ArrayList<>(); 70 private final List<CarUiImeSearchListItem> mSearchItems = new ArrayList<>(); 71 72 private InputMethodManager mInputMethodManager; 73 74 @Override onCreate(Bundle savedInstanceState)75 protected void onCreate(Bundle savedInstanceState) { 76 super.onCreate(savedInstanceState); 77 setContentView(R.layout.car_ui_recycler_view_activity); 78 79 mInputMethodManager = (InputMethodManager) 80 getSystemService(Context.INPUT_METHOD_SERVICE); 81 82 ToolbarController toolbar = CarUi.requireToolbar(this); 83 toolbar.setTitle(getTitle()); 84 toolbar.setNavButtonMode(NavButtonMode.BACK); 85 toolbar.setLogo(R.drawable.ic_launcher); 86 boolean[] isSearching = new boolean[]{false}; 87 toolbar.registerBackListener( 88 () -> { 89 if (isSearching[0]) { 90 toolbar.setSearchMode(SearchMode.DISABLED); 91 isSearching[0] = false; 92 return true; 93 } 94 return false; 95 }); 96 97 CarUiContentListItem.OnClickListener mainClickListener = i -> { 98 CharSequence title = i.getTitle() != null 99 ? i.getTitle().getPreferredText() 100 : null; 101 Toast.makeText(this, "Item clicked! " + title, Toast.LENGTH_SHORT).show(); 102 Log.i(TAG, "Item clicked! " + title); 103 }; 104 CarUiContentListItem.OnClickListener secondaryClickListener = i -> { 105 Toast.makeText(this, "Item's secondary action clicked!", Toast.LENGTH_SHORT).show(); 106 Log.i(TAG, "Item's secondary action clicked!"); 107 }; 108 109 Drawable icon = ContextCompat.getDrawable(this, R.drawable.ic_launcher); 110 111 for (int i = 1; i <= 100; i++) { 112 CarUiImeSearchListItem item = new CarUiImeSearchListItem( 113 CarUiContentListItem.Action.ICON); 114 item.setTitle("Title " + i); 115 item.setBody("Sub title " + i); 116 item.setIcon(icon); 117 item.setSupplementalIcon(icon, secondaryClickListener); 118 item.setOnItemClickedListener(mainClickListener); 119 120 mSearchItems.add(item); 121 } 122 123 mMenuItems.add(MenuItem.builder(this) 124 .setToSearch() 125 .setOnClickListener(i -> { 126 isSearching[0] = true; 127 toolbar.setSearchMode(SearchMode.SEARCH); 128 if (toolbar.getSearchCapabilities().canShowSearchResultItems()) { 129 toolbar.setSearchConfig(SearchConfig.builder() 130 .setSearchResultItems(mSearchItems) 131 .setSearchResultsInputViewIcon(ContextCompat.getDrawable( 132 this, R.drawable.car_ui_icon_search)) 133 .build()); 134 } 135 }) 136 .build()); 137 138 toolbar.setMenuItems(mMenuItems); 139 140 mWidescreenItems.add(new EditTextElement("Default Input Edit Text field", null)); 141 mWidescreenItems.add( 142 new EditTextElement("Add Desc to content area", this::addDescToContentArea)); 143 mWidescreenItems.add(new EditTextElement("Hide the content area", this::hideContentArea)); 144 mWidescreenItems.add(new EditTextElement("Hide extraction view", this::hideExtractionView)); 145 146 mWidescreenItems.add( 147 new EditTextElement("Add icon to extracted view", this::addIconToExtractedView)); 148 mWidescreenItems.add(new EditTextElement("Add error message to content area", 149 this::addErrorDescToContentArea)); 150 151 CarUiRecyclerView recyclerView = requireViewById(R.id.list); 152 recyclerView.setAdapter(mAdapter); 153 } 154 addIconToExtractedView(View view, boolean hasFocus)155 private void addIconToExtractedView(View view, boolean hasFocus) { 156 if (!hasFocus) { 157 return; 158 } 159 160 Bundle bundle = new Bundle(); 161 bundle.putInt(WIDE_SCREEN_EXTRACTED_TEXT_ICON_RES_ID, R.drawable.car_ui_icon_edit); 162 mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle); 163 } 164 addErrorDescToContentArea(View view, boolean hasFocus)165 private void addErrorDescToContentArea(View view, boolean hasFocus) { 166 if (!hasFocus) { 167 return; 168 } 169 170 Bundle bundle = new Bundle(); 171 bundle.putString(ADD_ERROR_DESC_TO_INPUT_AREA, "Some error message"); 172 bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, "Title"); 173 bundle.putString(ADD_DESC_TO_CONTENT_AREA, "Description provided by the application"); 174 mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle); 175 } 176 hideExtractionView(View view, boolean hasFocus)177 private void hideExtractionView(View view, boolean hasFocus) { 178 if (!hasFocus) { 179 return; 180 } 181 182 EditText editText = (EditText) view; 183 editText.setImeOptions(IME_FLAG_NO_EXTRACT_UI); 184 185 Bundle bundle = new Bundle(); 186 bundle.putBoolean(REQUEST_RENDER_CONTENT_AREA, false); 187 mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle); 188 } 189 addDescToContentArea(View view, boolean hasFocus)190 private void addDescToContentArea(View view, boolean hasFocus) { 191 if (!hasFocus) { 192 return; 193 } 194 195 Bundle bundle = new Bundle(); 196 bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, "Title"); 197 bundle.putString(ADD_DESC_TO_CONTENT_AREA, "Description provided by the application"); 198 mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle); 199 } 200 hideContentArea(View view, boolean hasFocus)201 private void hideContentArea(View view, boolean hasFocus) { 202 if (!hasFocus) { 203 return; 204 } 205 206 Bundle bundle = new Bundle(); 207 bundle.putBoolean(REQUEST_RENDER_CONTENT_AREA, false); 208 mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle); 209 } 210 211 private abstract static class ViewHolder extends RecyclerView.ViewHolder { 212 ViewHolder(@onNull View itemView)213 ViewHolder(@NonNull View itemView) { 214 super(itemView); 215 } 216 bind(ListElement element)217 public abstract void bind(ListElement element); 218 } 219 220 private static class EditTextViewHolder extends ViewHolder { 221 222 private final EditText mEditText; 223 EditTextViewHolder(@onNull View itemView)224 EditTextViewHolder(@NonNull View itemView) { 225 super(itemView); 226 mEditText = itemView.requireViewById(R.id.edit_text); 227 } 228 229 @Override bind(ListElement e)230 public void bind(ListElement e) { 231 if (!(e instanceof EditTextElement)) { 232 throw new IllegalArgumentException("Expected an EditTextElement"); 233 } 234 EditTextElement element = (EditTextElement) e; 235 mEditText.setText(element.getText()); 236 mEditText.setOnFocusChangeListener(element.getOnFocusChangeListener()); 237 } 238 } 239 240 private final RecyclerView.Adapter<ViewHolder> mAdapter = 241 new RecyclerView.Adapter<ViewHolder>() { 242 @Override 243 public int getItemCount() { 244 return mWidescreenItems.size(); 245 } 246 247 @Override 248 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 249 LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 250 if (viewType == ListElement.TYPE_EDIT_TEXT) { 251 return new EditTextViewHolder( 252 inflater.inflate(R.layout.edit_text_list_item, parent, false)); 253 } else { 254 throw new IllegalArgumentException("Unknown viewType: " + viewType); 255 } 256 } 257 258 @Override 259 public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 260 holder.bind(mWidescreenItems.get(position)); 261 } 262 263 @Override 264 public int getItemViewType(int position) { 265 return mWidescreenItems.get(position).getType(); 266 } 267 }; 268 269 @Override onCarUiInsetsChanged(@onNull Insets insets)270 public void onCarUiInsetsChanged(@NonNull Insets insets) { 271 FocusArea focusArea = requireViewById(R.id.focus_area); 272 focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom()); 273 focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom()); 274 requireViewById(R.id.list) 275 .setPadding(0, insets.getTop(), 0, insets.getBottom()); 276 requireViewById(android.R.id.content) 277 .setPadding(insets.getLeft(), 0, insets.getRight(), 0); 278 } 279 280 private abstract static class ListElement { 281 282 static final int TYPE_EDIT_TEXT = 0; 283 284 private final String mText; 285 ListElement(String text)286 ListElement(String text) { 287 mText = text; 288 } 289 getText()290 String getText() { 291 return mText; 292 } 293 getType()294 abstract int getType(); 295 } 296 297 private static class EditTextElement extends ListElement { 298 299 private final OnFocusChangeListener mListener; 300 EditTextElement(String text, OnFocusChangeListener listener)301 EditTextElement(String text, OnFocusChangeListener listener) { 302 super(text); 303 mListener = listener; 304 } 305 getOnFocusChangeListener()306 OnFocusChangeListener getOnFocusChangeListener() { 307 return mListener; 308 } 309 310 @Override getType()311 int getType() { 312 return TYPE_EDIT_TEXT; 313 } 314 } 315 } 316