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.carlauncher.homescreen; 18 19 import android.graphics.drawable.Drawable; 20 import android.os.Bundle; 21 import android.util.Size; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.view.ViewStub; 26 import android.widget.ImageButton; 27 import android.widget.ImageView; 28 import android.widget.TextView; 29 30 import androidx.fragment.app.Fragment; 31 32 import com.android.car.apps.common.CrossfadeImageView; 33 import com.android.car.carlauncher.R; 34 import com.android.car.carlauncher.homescreen.ui.CardContent; 35 import com.android.car.carlauncher.homescreen.ui.CardHeader; 36 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextView; 37 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextWithControlsView; 38 import com.android.car.carlauncher.homescreen.ui.TextBlockView; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.util.ArrayUtils; 41 42 /** 43 * Abstract class for a {@link Fragment} that implements the Home App's View interface. 44 * 45 * {@link HomeCardInterface.View} classes that extend HomeCardFragment will override the update 46 * methods of the types of CardContent that they support. Each CardContent class corresponds to a 47 * layout shown on the card. The layout is a combination of xml files. 48 * {@link DescriptiveTextWithControlsView}: card_content_descriptive_text, card_content_button_trio 49 * {@link DescriptiveTextView}: card_content_descriptive_text, card_content_tap_for_more_text 50 * {@link TextBlockView}: card_content_text_block, card_content_tap_for_more_text 51 */ 52 public class HomeCardFragment extends Fragment implements HomeCardInterface.View { 53 54 private static final String TAG = "HomeFragment"; 55 private HomeCardInterface.Presenter mPresenter; 56 private Size mSize; 57 private View mCardBackground; 58 private CrossfadeImageView mCardBackgroundImage; 59 private View mRootView; 60 private TextView mCardTitle; 61 private ImageView mCardIcon; 62 63 // Views from card_content_text_block.xml 64 private View mTextBlockLayoutView; 65 private TextView mTextBlock; 66 private TextView mTextBlockTapForMore; 67 68 // Views from card_content_descriptive_text_only.xml 69 private View mDescriptiveTextOnlyLayoutView; 70 private ImageView mDescriptiveTextOnlyOptionalImage; 71 private TextView mDescriptiveTextOnlyTitle; 72 private TextView mDescriptiveTextOnlySubtitle; 73 private TextView mDescriptiveTextOnlyTapForMore; 74 75 // Views from card_content_descriptive_text_with_controls.xml 76 private View mDescriptiveTextWithControlsLayoutView; 77 private ImageView mDescriptiveTextWithControlsOptionalImage; 78 private TextView mDescriptiveTextWithControlsTitle; 79 private TextView mDescriptiveTextWithControlsSubtitle; 80 private View mControlBarView; 81 private ImageButton mControlBarLeftButton; 82 private ImageButton mControlBarCenterButton; 83 private ImageButton mControlBarRightButton; 84 85 @Override setPresenter(HomeCardInterface.Presenter presenter)86 public void setPresenter(HomeCardInterface.Presenter presenter) { 87 mPresenter = presenter; 88 } 89 90 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)91 public View onCreateView(LayoutInflater inflater, ViewGroup container, 92 Bundle savedInstanceState) { 93 mRootView = inflater.inflate(R.layout.card_fragment, container, false); 94 mCardTitle = mRootView.findViewById(R.id.card_name); 95 mCardIcon = mRootView.findViewById(R.id.card_icon); 96 return mRootView; 97 } 98 99 @Override onViewCreated(View view, Bundle savedInstanceState)100 public void onViewCreated(View view, Bundle savedInstanceState) { 101 super.onViewCreated(view, savedInstanceState); 102 mPresenter.onViewCreated(); 103 mRootView.setOnClickListener(v -> mPresenter.onViewClicked(v)); 104 } 105 106 @Override onDestroy()107 public void onDestroy() { 108 super.onDestroy(); 109 if (mPresenter != null) { 110 mPresenter.onViewDestroyed(); 111 } 112 mSize = null; 113 } 114 115 @Override getFragment()116 public Fragment getFragment() { 117 return this; 118 } 119 120 /** 121 * Returns the size of the card or null if the view hasn't yet been laid out 122 */ getCardSize()123 protected Size getCardSize() { 124 if (mSize == null && mRootView.isLaidOut()) { 125 mSize = new Size(mRootView.getWidth(), mRootView.getHeight()); 126 } 127 return mSize; 128 } 129 130 /** 131 * Removes the audio card from view 132 */ 133 @Override hideCard()134 public void hideCard() { 135 hideAllViews(); 136 mRootView.setVisibility(View.GONE); 137 } 138 139 /** 140 * Updates the card's header: name and icon of source app 141 */ 142 @Override updateHeaderView(CardHeader header)143 public void updateHeaderView(CardHeader header) { 144 requireActivity().runOnUiThread(() -> { 145 mRootView.setVisibility(View.VISIBLE); 146 mCardTitle.setText(header.getCardTitle()); 147 mCardIcon.setImageDrawable(header.getCardIcon()); 148 }); 149 } 150 151 @Override updateContentView(CardContent content)152 public final void updateContentView(CardContent content) { 153 requireActivity().runOnUiThread(() -> { 154 hideAllViews(); 155 updateContentViewInternal(content); 156 }); 157 } 158 159 /** 160 * Child classes can override this method for updating their specific types of card content 161 */ updateContentViewInternal(CardContent content)162 protected void updateContentViewInternal(CardContent content) { 163 switch (content.getType()) { 164 case DESCRIPTIVE_TEXT: 165 DescriptiveTextView descriptiveTextContent = (DescriptiveTextView) content; 166 updateDescriptiveTextOnlyView(descriptiveTextContent.getTitle(), 167 descriptiveTextContent.getSubtitle(), descriptiveTextContent.getImage(), 168 descriptiveTextContent.getFooter()); 169 break; 170 case DESCRIPTIVE_TEXT_WITH_CONTROLS: 171 DescriptiveTextWithControlsView 172 descriptiveTextWithControlsContent = 173 (DescriptiveTextWithControlsView) content; 174 updateDescriptiveTextWithControlsView(descriptiveTextWithControlsContent.getTitle(), 175 descriptiveTextWithControlsContent.getSubtitle(), 176 descriptiveTextWithControlsContent.getImage(), 177 descriptiveTextWithControlsContent.getLeftControl(), 178 descriptiveTextWithControlsContent.getCenterControl(), 179 descriptiveTextWithControlsContent.getRightControl()); 180 break; 181 case TEXT_BLOCK: 182 TextBlockView textBlockContent = (TextBlockView) content; 183 updateTextBlock(textBlockContent.getText(), textBlockContent.getFooter()); 184 break; 185 } 186 } 187 updateDescriptiveTextOnlyView(CharSequence primaryText, CharSequence secondaryText, Drawable optionalImage, CharSequence tapForMoreText)188 protected final void updateDescriptiveTextOnlyView(CharSequence primaryText, 189 CharSequence secondaryText, Drawable optionalImage, CharSequence tapForMoreText) { 190 getDescriptiveTextOnlyLayoutView().setVisibility(View.VISIBLE); 191 mDescriptiveTextOnlyTitle.setText(primaryText); 192 mDescriptiveTextOnlySubtitle.setText(secondaryText); 193 mDescriptiveTextOnlyOptionalImage.setImageDrawable(optionalImage); 194 mDescriptiveTextOnlyOptionalImage.setVisibility( 195 optionalImage == null ? View.GONE : View.VISIBLE); 196 mDescriptiveTextOnlyTapForMore.setText(tapForMoreText); 197 mDescriptiveTextOnlyTapForMore.setVisibility( 198 tapForMoreText == null ? View.GONE : View.VISIBLE); 199 } 200 updateDescriptiveTextWithControlsView(CharSequence primaryText, CharSequence secondaryText, Drawable optionalImage, DescriptiveTextWithControlsView.Control leftButton, DescriptiveTextWithControlsView.Control centerButton, DescriptiveTextWithControlsView.Control rightButton)201 protected final void updateDescriptiveTextWithControlsView(CharSequence primaryText, 202 CharSequence secondaryText, Drawable optionalImage, 203 DescriptiveTextWithControlsView.Control leftButton, 204 DescriptiveTextWithControlsView.Control centerButton, 205 DescriptiveTextWithControlsView.Control rightButton) { 206 getDescriptiveTextWithControlsLayoutView().setVisibility(View.VISIBLE); 207 mDescriptiveTextWithControlsTitle.setText(primaryText); 208 mDescriptiveTextWithControlsSubtitle.setText(secondaryText); 209 mDescriptiveTextWithControlsOptionalImage.setImageDrawable(optionalImage); 210 mDescriptiveTextWithControlsOptionalImage.setVisibility( 211 optionalImage == null ? View.GONE : View.VISIBLE); 212 213 updateControlBarButton(leftButton, mControlBarLeftButton); 214 updateControlBarButton(centerButton, mControlBarCenterButton); 215 updateControlBarButton(rightButton, mControlBarRightButton); 216 } 217 updateControlBarButton(DescriptiveTextWithControlsView.Control buttonContent, ImageButton buttonView)218 private void updateControlBarButton(DescriptiveTextWithControlsView.Control buttonContent, 219 ImageButton buttonView) { 220 if (buttonContent != null) { 221 buttonView.setImageDrawable(buttonContent.getIcon()); 222 if (buttonContent.getIcon() != null) { 223 // update the button view according to icon's selected state 224 buttonView.setSelected( 225 ArrayUtils.contains(buttonContent.getIcon().getState(), 226 android.R.attr.state_selected) 227 ); 228 } 229 buttonView.setOnClickListener(buttonContent.getOnClickListener()); 230 buttonView.setVisibility(View.VISIBLE); 231 } else { 232 buttonView.setVisibility(View.GONE); 233 } 234 } 235 updateTextBlock(CharSequence mainText, CharSequence tapForMoreText)236 protected final void updateTextBlock(CharSequence mainText, CharSequence tapForMoreText) { 237 getTextBlockLayoutView().setVisibility(View.VISIBLE); 238 mTextBlock.setText(mainText); 239 mTextBlockTapForMore.setText(tapForMoreText); 240 mTextBlockTapForMore.setVisibility(tapForMoreText == null ? View.GONE : View.VISIBLE); 241 } 242 hideAllViews()243 protected void hideAllViews() { 244 getTextBlockLayoutView().setVisibility(View.GONE); 245 getDescriptiveTextOnlyLayoutView().setVisibility(View.GONE); 246 getDescriptiveTextWithControlsLayoutView().setVisibility(View.GONE); 247 } 248 getRootView()249 protected final View getRootView() { 250 return mRootView; 251 } 252 getCardBackground()253 protected final View getCardBackground() { 254 if (mCardBackground == null) { 255 mCardBackground = getRootView().findViewById(R.id.card_background); 256 } 257 return mCardBackground; 258 } 259 getCardBackgroundImage()260 protected final CrossfadeImageView getCardBackgroundImage() { 261 if (mCardBackgroundImage == null) { 262 mCardBackgroundImage = getRootView().findViewById(R.id.card_background_image); 263 } 264 return mCardBackgroundImage; 265 } 266 getDescriptiveTextOnlyLayoutView()267 protected final View getDescriptiveTextOnlyLayoutView() { 268 if (mDescriptiveTextOnlyLayoutView == null) { 269 ViewStub stub = mRootView.findViewById(R.id.descriptive_text_layout); 270 mDescriptiveTextOnlyLayoutView = stub.inflate(); 271 mDescriptiveTextOnlyTitle = mDescriptiveTextOnlyLayoutView.findViewById( 272 R.id.primary_text); 273 mDescriptiveTextOnlySubtitle = mDescriptiveTextOnlyLayoutView.findViewById( 274 R.id.secondary_text); 275 mDescriptiveTextOnlyOptionalImage = mDescriptiveTextOnlyLayoutView.findViewById( 276 R.id.optional_image); 277 mDescriptiveTextOnlyTapForMore = mDescriptiveTextOnlyLayoutView.findViewById( 278 R.id.tap_for_more_text); 279 } 280 return mDescriptiveTextOnlyLayoutView; 281 } 282 getDescriptiveTextWithControlsLayoutView()283 protected final View getDescriptiveTextWithControlsLayoutView() { 284 if (mDescriptiveTextWithControlsLayoutView == null) { 285 ViewStub stub = mRootView.findViewById(R.id.descriptive_text_with_controls_layout); 286 mDescriptiveTextWithControlsLayoutView = stub.inflate(); 287 mDescriptiveTextWithControlsTitle = mDescriptiveTextWithControlsLayoutView.findViewById( 288 R.id.primary_text); 289 mDescriptiveTextWithControlsSubtitle = 290 mDescriptiveTextWithControlsLayoutView.findViewById(R.id.secondary_text); 291 mDescriptiveTextWithControlsOptionalImage = 292 mDescriptiveTextWithControlsLayoutView.findViewById(R.id.optional_image); 293 mControlBarView = mDescriptiveTextWithControlsLayoutView.findViewById(R.id.button_trio); 294 mControlBarLeftButton = mDescriptiveTextWithControlsLayoutView.findViewById( 295 R.id.button_left); 296 mControlBarCenterButton = mDescriptiveTextWithControlsLayoutView.findViewById( 297 R.id.button_center); 298 mControlBarRightButton = mDescriptiveTextWithControlsLayoutView.findViewById( 299 R.id.button_right); 300 } 301 return mDescriptiveTextWithControlsLayoutView; 302 } 303 getTextBlockLayoutView()304 private View getTextBlockLayoutView() { 305 if (mTextBlockLayoutView == null) { 306 ViewStub stub = mRootView.findViewById(R.id.text_block_layout); 307 mTextBlockLayoutView = stub.inflate(); 308 mTextBlock = mTextBlockLayoutView.findViewById(R.id.text_block); 309 mTextBlockTapForMore = mTextBlockLayoutView.findViewById(R.id.tap_for_more_text); 310 } 311 return mTextBlockLayoutView; 312 } 313 314 @VisibleForTesting setControlBarLeftButton(ImageButton controlBarLeftButton)315 void setControlBarLeftButton(ImageButton controlBarLeftButton) { 316 mControlBarLeftButton = controlBarLeftButton; 317 } 318 } 319