• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 Size mSize;
55     private View mCardBackground;
56     private CrossfadeImageView mCardBackgroundImage;
57     private View mRootView;
58     private TextView mCardTitle;
59     private ImageView mCardIcon;
60 
61     // Views from card_content_text_block.xml
62     private View mTextBlockLayoutView;
63     private TextView mTextBlock;
64     private TextView mTextBlockTapForMore;
65 
66     // Views from card_content_descriptive_text_only.xml
67     private View mDescriptiveTextOnlyLayoutView;
68     private ImageView mDescriptiveTextOnlyOptionalImage;
69     private TextView mDescriptiveTextOnlyTitle;
70     private TextView mDescriptiveTextOnlySubtitle;
71     private TextView mDescriptiveTextOnlyTapForMore;
72 
73     // Views from card_content_descriptive_text_with_controls.xml
74     private View mDescriptiveTextWithControlsLayoutView;
75     private ImageView mDescriptiveTextWithControlsOptionalImage;
76     private TextView mDescriptiveTextWithControlsTitle;
77     private TextView mDescriptiveTextWithControlsSubtitle;
78     private View mControlBarView;
79     private ImageButton mControlBarLeftButton;
80     private ImageButton mControlBarCenterButton;
81     private ImageButton mControlBarRightButton;
82 
83 
84     private boolean mViewCreated;
85     private OnViewLifecycleChangeListener mOnViewLifecycleChangeListener;
86 
87     private OnViewClickListener mOnViewClickListener;
88 
89     /**
90      * Interface definition for a callback to be invoked for a view lifecycle changes.
91      */
92     public interface OnViewLifecycleChangeListener {
93 
94         /**
95          * Called when a view has been Created.
96          */
onViewCreated()97         void onViewCreated();
98 
99         /**
100          * Called when a view has been destroyed.
101          */
onViewDestroyed()102         void onViewDestroyed();
103     }
104 
105     /**
106      * Interface definition for a callback to be invoked when a view is clicked.
107      */
108     public interface OnViewClickListener {
109 
110         /**
111          * Called when a view has been clicked.
112          */
onViewClicked()113         void onViewClicked();
114     }
115 
116     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)117     public View onCreateView(LayoutInflater inflater, ViewGroup container,
118             Bundle savedInstanceState) {
119         mRootView = inflater.inflate(R.layout.card_fragment, container, false);
120         mCardTitle = mRootView.findViewById(R.id.card_name);
121         mCardIcon = mRootView.findViewById(R.id.card_icon);
122         return mRootView;
123     }
124 
125     @Override
onViewCreated(View view, Bundle savedInstanceState)126     public void onViewCreated(View view, Bundle savedInstanceState) {
127         super.onViewCreated(view, savedInstanceState);
128         mViewCreated = true;
129         if (mOnViewLifecycleChangeListener != null) {
130             mOnViewLifecycleChangeListener.onViewCreated();
131         }
132         mRootView.setOnClickListener(v -> mOnViewClickListener.onViewClicked());
133     }
134 
135     @Override
onDestroy()136     public void onDestroy() {
137         super.onDestroy();
138         mViewCreated = false;
139         if (mOnViewLifecycleChangeListener != null) {
140             mOnViewLifecycleChangeListener.onViewDestroyed();
141         }
142         mSize = null;
143     }
144 
145     @Override
getFragment()146     public Fragment getFragment() {
147         return this;
148     }
149 
150     /**
151      * Register a callback to be invoked when this view lifecycle changes.
152      *
153      * @param onViewLifecycleChangeListener The callback that will run
154      */
setOnViewLifecycleChangeListener( OnViewLifecycleChangeListener onViewLifecycleChangeListener)155     public void setOnViewLifecycleChangeListener(
156             OnViewLifecycleChangeListener onViewLifecycleChangeListener) {
157         mOnViewLifecycleChangeListener = onViewLifecycleChangeListener;
158         if (mViewCreated) {
159             mOnViewLifecycleChangeListener.onViewCreated();
160         }
161     }
162 
163     /**
164      * Register a callback to be invoked when this view is clicked.
165      *
166      * @param onViewClickListener The callback that will run
167      */
setOnViewClickListener(OnViewClickListener onViewClickListener)168     public void setOnViewClickListener(OnViewClickListener onViewClickListener) {
169         mOnViewClickListener = onViewClickListener;
170     }
171 
172     /**
173      * Returns the size of the card or null if the view hasn't yet been laid out
174      */
getCardSize()175     protected Size getCardSize() {
176         if (mSize == null && mRootView.isLaidOut()) {
177             mSize = new Size(mRootView.getWidth(), mRootView.getHeight());
178         }
179         return mSize;
180     }
181 
182     /**
183      * Removes the audio card from view
184      */
185     @Override
hideCard()186     public void hideCard() {
187         hideAllViews();
188         mRootView.setVisibility(View.GONE);
189     }
190 
191     /**
192      * Updates the card's header: name and icon of source app
193      */
updateHeaderView(CardHeader header)194     public void updateHeaderView(CardHeader header) {
195         requireActivity().runOnUiThread(() -> {
196             mRootView.setVisibility(View.VISIBLE);
197             mCardTitle.setText(header.getCardTitle());
198             mCardIcon.setImageDrawable(header.getCardIcon());
199         });
200     }
201 
202     /**
203      * Updates the card's content
204      */
updateContentView(CardContent content)205     public final void updateContentView(CardContent content) {
206         requireActivity().runOnUiThread(() -> {
207             hideAllViews();
208             updateContentViewInternal(content);
209         });
210     }
211 
212 
213     /**
214      * Child classes can override this method for updating their specific types of card content
215      */
updateContentViewInternal(CardContent content)216     protected void updateContentViewInternal(CardContent content) {
217         switch (content.getType()) {
218             case DESCRIPTIVE_TEXT:
219                 DescriptiveTextView descriptiveTextContent = (DescriptiveTextView) content;
220                 updateDescriptiveTextOnlyView(descriptiveTextContent.getTitle(),
221                         descriptiveTextContent.getSubtitle(), descriptiveTextContent.getImage(),
222                         descriptiveTextContent.getFooter());
223                 break;
224             case DESCRIPTIVE_TEXT_WITH_CONTROLS:
225                 DescriptiveTextWithControlsView descriptiveTextWithControlsContent =
226                         (DescriptiveTextWithControlsView) content;
227                 updateDescriptiveTextWithControlsView(descriptiveTextWithControlsContent.getTitle(),
228                         descriptiveTextWithControlsContent.getSubtitle(),
229                         descriptiveTextWithControlsContent.getImage(),
230                         descriptiveTextWithControlsContent.getLeftControl(),
231                         descriptiveTextWithControlsContent.getCenterControl(),
232                         descriptiveTextWithControlsContent.getRightControl());
233                 break;
234             case TEXT_BLOCK:
235                 TextBlockView textBlockContent = (TextBlockView) content;
236                 updateTextBlock(textBlockContent.getText(), textBlockContent.getFooter());
237                 break;
238         }
239     }
240 
updateDescriptiveTextOnlyView(CharSequence primaryText, CharSequence secondaryText, Drawable optionalImage, CharSequence tapForMoreText)241     protected final void updateDescriptiveTextOnlyView(CharSequence primaryText,
242             CharSequence secondaryText, Drawable optionalImage, CharSequence tapForMoreText) {
243         getDescriptiveTextOnlyLayoutView().setVisibility(View.VISIBLE);
244         mDescriptiveTextOnlyTitle.setText(primaryText);
245         mDescriptiveTextOnlySubtitle.setText(secondaryText);
246         mDescriptiveTextOnlyOptionalImage.setImageDrawable(optionalImage);
247         mDescriptiveTextOnlyOptionalImage.setVisibility(
248                 optionalImage == null ? View.GONE : View.VISIBLE);
249         mDescriptiveTextOnlyTapForMore.setText(tapForMoreText);
250         mDescriptiveTextOnlyTapForMore.setVisibility(
251                 tapForMoreText == null ? View.GONE : View.VISIBLE);
252     }
253 
updateDescriptiveTextWithControlsView(CharSequence primaryText, CharSequence secondaryText, CardContent.CardBackgroundImage optionalImage, DescriptiveTextWithControlsView.Control leftButton, DescriptiveTextWithControlsView.Control centerButton, DescriptiveTextWithControlsView.Control rightButton)254     protected final void updateDescriptiveTextWithControlsView(CharSequence primaryText,
255             CharSequence secondaryText, CardContent.CardBackgroundImage optionalImage,
256             DescriptiveTextWithControlsView.Control leftButton,
257             DescriptiveTextWithControlsView.Control centerButton,
258             DescriptiveTextWithControlsView.Control rightButton) {
259         getDescriptiveTextWithControlsLayoutView().setVisibility(View.VISIBLE);
260         mDescriptiveTextWithControlsTitle.setText(primaryText);
261         mDescriptiveTextWithControlsSubtitle.setText(secondaryText);
262         if (optionalImage != null) {
263             mDescriptiveTextWithControlsOptionalImage.setImageDrawable(
264                     optionalImage.getForeground());
265         }
266         mDescriptiveTextWithControlsOptionalImage.setVisibility(
267                 (optionalImage == null || optionalImage.getForeground() == null) ? View.GONE
268                         : View.VISIBLE);
269 
270         updateControlBarButton(leftButton, mControlBarLeftButton);
271         updateControlBarButton(centerButton, mControlBarCenterButton);
272         updateControlBarButton(rightButton, mControlBarRightButton);
273     }
274 
updateControlBarButton(DescriptiveTextWithControlsView.Control buttonContent, ImageButton buttonView)275     private void updateControlBarButton(DescriptiveTextWithControlsView.Control buttonContent,
276             ImageButton buttonView) {
277         if (buttonContent != null) {
278             buttonView.setImageDrawable(buttonContent.getIcon());
279             if (buttonContent.getIcon() != null) {
280                 // update the button view according to icon's selected state
281                 buttonView.setSelected(ArrayUtils.contains(buttonContent.getIcon().getState(),
282                         android.R.attr.state_selected));
283             }
284             buttonView.setOnClickListener(buttonContent.getOnClickListener());
285             buttonView.setVisibility(View.VISIBLE);
286         } else {
287             buttonView.setVisibility(View.GONE);
288         }
289     }
290 
updateTextBlock(CharSequence mainText, CharSequence tapForMoreText)291     protected final void updateTextBlock(CharSequence mainText, CharSequence tapForMoreText) {
292         getTextBlockLayoutView().setVisibility(View.VISIBLE);
293         mTextBlock.setText(mainText);
294         mTextBlockTapForMore.setText(tapForMoreText);
295         mTextBlockTapForMore.setVisibility(tapForMoreText == null ? View.GONE : View.VISIBLE);
296     }
297 
hideAllViews()298     protected void hideAllViews() {
299         getTextBlockLayoutView().setVisibility(View.GONE);
300         getDescriptiveTextOnlyLayoutView().setVisibility(View.GONE);
301         getDescriptiveTextWithControlsLayoutView().setVisibility(View.GONE);
302     }
303 
getRootView()304     protected final View getRootView() {
305         return mRootView;
306     }
307 
getCardBackground()308     protected final View getCardBackground() {
309         if (mCardBackground == null) {
310             mCardBackground = getRootView().findViewById(R.id.card_background);
311         }
312         return mCardBackground;
313     }
314 
getCardBackgroundImage()315     protected final CrossfadeImageView getCardBackgroundImage() {
316         if (mCardBackgroundImage == null) {
317             mCardBackgroundImage = getRootView().findViewById(R.id.card_background_image);
318         }
319         return mCardBackgroundImage;
320     }
321 
getDescriptiveTextOnlyLayoutView()322     protected final View getDescriptiveTextOnlyLayoutView() {
323         if (mDescriptiveTextOnlyLayoutView == null) {
324             ViewStub stub = mRootView.findViewById(R.id.descriptive_text_layout);
325             mDescriptiveTextOnlyLayoutView = stub.inflate();
326             mDescriptiveTextOnlyTitle = mDescriptiveTextOnlyLayoutView.findViewById(
327                     R.id.primary_text);
328             mDescriptiveTextOnlySubtitle = mDescriptiveTextOnlyLayoutView.findViewById(
329                     R.id.secondary_text);
330             mDescriptiveTextOnlyOptionalImage = mDescriptiveTextOnlyLayoutView.findViewById(
331                     R.id.optional_image);
332             mDescriptiveTextOnlyTapForMore = mDescriptiveTextOnlyLayoutView.findViewById(
333                     R.id.tap_for_more_text);
334         }
335         return mDescriptiveTextOnlyLayoutView;
336     }
337 
getDescriptiveTextWithControlsLayoutView()338     protected final View getDescriptiveTextWithControlsLayoutView() {
339         if (mDescriptiveTextWithControlsLayoutView == null) {
340             ViewStub stub = mRootView.findViewById(R.id.descriptive_text_with_controls_layout);
341             mDescriptiveTextWithControlsLayoutView = stub.inflate();
342             mDescriptiveTextWithControlsTitle = mDescriptiveTextWithControlsLayoutView.findViewById(
343                     R.id.primary_text);
344             mDescriptiveTextWithControlsSubtitle =
345                     mDescriptiveTextWithControlsLayoutView.findViewById(R.id.secondary_text);
346             mDescriptiveTextWithControlsOptionalImage =
347                     mDescriptiveTextWithControlsLayoutView.findViewById(R.id.optional_image);
348             mControlBarView = mDescriptiveTextWithControlsLayoutView.findViewById(R.id.button_trio);
349             mControlBarLeftButton = mDescriptiveTextWithControlsLayoutView.findViewById(
350                     R.id.button_left);
351             mControlBarCenterButton = mDescriptiveTextWithControlsLayoutView.findViewById(
352                     R.id.button_center);
353             mControlBarRightButton = mDescriptiveTextWithControlsLayoutView.findViewById(
354                     R.id.button_right);
355         }
356         return mDescriptiveTextWithControlsLayoutView;
357     }
358 
getTextBlockLayoutView()359     private View getTextBlockLayoutView() {
360         if (mTextBlockLayoutView == null) {
361             ViewStub stub = mRootView.findViewById(R.id.text_block_layout);
362             mTextBlockLayoutView = stub.inflate();
363             mTextBlock = mTextBlockLayoutView.findViewById(R.id.text_block);
364             mTextBlockTapForMore = mTextBlockLayoutView.findViewById(R.id.tap_for_more_text);
365         }
366         return mTextBlockLayoutView;
367     }
368 
369     @VisibleForTesting
setControlBarLeftButton(ImageButton controlBarLeftButton)370     void setControlBarLeftButton(ImageButton controlBarLeftButton) {
371         mControlBarLeftButton = controlBarLeftButton;
372     }
373 }
374