1 /*
2  * Copyright (C) 2016 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.incallui.sessiondata;
18 
19 import android.graphics.drawable.Drawable;
20 import android.location.Location;
21 import android.net.Uri;
22 import android.os.Bundle;
23 import android.support.annotation.NonNull;
24 import android.support.annotation.Nullable;
25 import android.support.annotation.VisibleForTesting;
26 import android.support.v4.app.Fragment;
27 import android.text.TextUtils;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.FrameLayout;
32 import android.widget.ImageView;
33 import android.widget.TextView;
34 import com.android.dialer.common.FragmentUtils;
35 import com.android.dialer.common.LogUtil;
36 import com.android.dialer.multimedia.MultimediaData;
37 import com.android.dialer.theme.base.ThemeComponent;
38 import com.android.incallui.maps.MapsComponent;
39 import com.bumptech.glide.Glide;
40 import com.bumptech.glide.load.DataSource;
41 import com.bumptech.glide.load.engine.GlideException;
42 import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
43 import com.bumptech.glide.request.RequestListener;
44 import com.bumptech.glide.request.target.Target;
45 
46 /**
47  * Displays info from {@link MultimediaData MultimediaData}.
48  *
49  * <p>Currently displays image, location (as a map), and message that come bundled with
50  * MultimediaData when calling {@link #newInstance(MultimediaData, boolean, boolean, boolean)}.
51  */
52 public class MultimediaFragment extends Fragment implements AvatarPresenter {
53 
54   private static final String ARG_SUBJECT = "subject";
55   private static final String ARG_IMAGE = "image";
56   private static final String ARG_LOCATION = "location";
57   private static final String ARG_INTERACTIVE = "interactive";
58   private static final String ARG_SHOW_AVATAR = "show_avatar";
59   private static final String ARG_IS_SPAM = "is_spam";
60   private ImageView avatarImageView;
61 
62   private boolean showAvatar;
63   private boolean isSpam;
64 
newInstance( @onNull MultimediaData multimediaData, boolean isInteractive, boolean showAvatar, boolean isSpam)65   public static MultimediaFragment newInstance(
66       @NonNull MultimediaData multimediaData,
67       boolean isInteractive,
68       boolean showAvatar,
69       boolean isSpam) {
70     return newInstance(
71         multimediaData.getText(),
72         multimediaData.getImageUri(),
73         multimediaData.getLocation(),
74         isInteractive,
75         showAvatar,
76         isSpam);
77   }
78 
79   @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
newInstance( @ullable String subject, @Nullable Uri imageUri, @Nullable Location location, boolean isInteractive, boolean showAvatar, boolean isSpam)80   public static MultimediaFragment newInstance(
81       @Nullable String subject,
82       @Nullable Uri imageUri,
83       @Nullable Location location,
84       boolean isInteractive,
85       boolean showAvatar,
86       boolean isSpam) {
87     Bundle args = new Bundle();
88     args.putString(ARG_SUBJECT, subject);
89     args.putParcelable(ARG_IMAGE, imageUri);
90     args.putParcelable(ARG_LOCATION, location);
91     args.putBoolean(ARG_INTERACTIVE, isInteractive);
92     args.putBoolean(ARG_SHOW_AVATAR, showAvatar);
93     args.putBoolean(ARG_IS_SPAM, isSpam);
94     MultimediaFragment fragment = new MultimediaFragment();
95     fragment.setArguments(args);
96     return fragment;
97   }
98 
99   @Override
onCreate(@ullable Bundle bundle)100   public void onCreate(@Nullable Bundle bundle) {
101     super.onCreate(bundle);
102     showAvatar = getArguments().getBoolean(ARG_SHOW_AVATAR);
103     isSpam = getArguments().getBoolean(ARG_IS_SPAM);
104   }
105 
106   @Nullable
107   @Override
onCreateView( LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle)108   public View onCreateView(
109       LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
110     layoutInflater =
111         layoutInflater.cloneInContext(
112             ThemeComponent.get(getContext()).theme().getThemedContext(getContext()));
113 
114     if (isSpam) {
115       LogUtil.i("MultimediaFragment.onCreateView", "show spam layout");
116       return layoutInflater.inflate(R.layout.fragment_spam, viewGroup, false);
117     }
118 
119     boolean hasImage = getImageUri() != null;
120     boolean hasSubject = !TextUtils.isEmpty(getSubject());
121     boolean hasMap = getLocation() != null;
122     if (hasMap && MapsComponent.get(getContext()).getMaps().isAvailable()) {
123       if (hasImage) {
124         if (hasSubject) {
125           LogUtil.i("MultimediaFragment.onCreateView", "show text, image, location layout");
126           return layoutInflater.inflate(
127               R.layout.fragment_composer_text_image_frag, viewGroup, false);
128         } else {
129           LogUtil.i("MultimediaFragment.onCreateView", "show image, location layout");
130           return layoutInflater.inflate(R.layout.fragment_composer_image_frag, viewGroup, false);
131         }
132       } else if (hasSubject) {
133         LogUtil.i("MultimediaFragment.onCreateView", "show text, location layout");
134         return layoutInflater.inflate(R.layout.fragment_composer_text_frag, viewGroup, false);
135       } else {
136         LogUtil.i("MultimediaFragment.onCreateView", "show location layout");
137         return layoutInflater.inflate(R.layout.fragment_composer_frag, viewGroup, false);
138       }
139     } else if (hasImage) {
140       if (hasSubject) {
141         LogUtil.i("MultimediaFragment.onCreateView", "show text, image layout");
142         return layoutInflater.inflate(R.layout.fragment_composer_text_image, viewGroup, false);
143       } else {
144         LogUtil.i("MultimediaFragment.onCreateView", "show image layout");
145         return layoutInflater.inflate(R.layout.fragment_composer_image, viewGroup, false);
146       }
147     } else {
148       LogUtil.i("MultimediaFragment.onCreateView", "show text layout");
149       return layoutInflater.inflate(R.layout.fragment_composer_text, viewGroup, false);
150     }
151   }
152 
153   @Override
onViewCreated(View view, @Nullable Bundle bundle)154   public void onViewCreated(View view, @Nullable Bundle bundle) {
155     super.onViewCreated(view, bundle);
156     View container = view.findViewById(R.id.answer_message_container);
157     if (container != null) {
158       container.setClipToOutline(true);
159     }
160 
161     // If the call is spam and only has a subject, update the view to reflect that.
162     if (isSpam
163         && getLocation() == null
164         && getImageUri() == null
165         && !TextUtils.isEmpty(getSubject())) {
166       ((ImageView) view.findViewById(R.id.spam_image))
167           .setImageResource(R.drawable.quantum_ic_message_white_24);
168       ((TextView) view.findViewById(R.id.spam_text)).setText(R.string.spam_message_text);
169     }
170 
171     TextView messageText = view.findViewById(R.id.answer_message_text);
172     if (messageText != null) {
173       messageText.setText(getSubject());
174     }
175     ImageView mainImage = view.findViewById(R.id.answer_message_image);
176     if (mainImage != null) {
177       Glide.with(this)
178           .load(getImageUri())
179           .transition(DrawableTransitionOptions.withCrossFade())
180           .listener(
181               new RequestListener<Drawable>() {
182                 @Override
183                 public boolean onLoadFailed(
184                     @Nullable GlideException e,
185                     Object model,
186                     Target<Drawable> target,
187                     boolean isFirstResource) {
188                   view.findViewById(R.id.loading_spinner).setVisibility(View.GONE);
189                   LogUtil.e("MultimediaFragment.onLoadFailed", null, e);
190                   // TODO(a bug) handle error cases nicely
191                   return false; // Let Glide handle the rest
192                 }
193 
194                 @Override
195                 public boolean onResourceReady(
196                     Drawable drawable,
197                     Object model,
198                     Target<Drawable> target,
199                     DataSource dataSource,
200                     boolean isFirstResource) {
201                   LogUtil.enterBlock("MultimediaFragment.onResourceReady");
202                   view.findViewById(R.id.loading_spinner).setVisibility(View.GONE);
203                   return false;
204                 }
205               })
206           .into(mainImage);
207       mainImage.setClipToOutline(true);
208     }
209     FrameLayout fragmentHolder = view.findViewById(R.id.answer_message_frag);
210     if (fragmentHolder != null) {
211       fragmentHolder.setClipToOutline(true);
212       Fragment mapFragment =
213           MapsComponent.get(getContext()).getMaps().createStaticMapFragment(getLocation());
214       getChildFragmentManager()
215           .beginTransaction()
216           .replace(R.id.answer_message_frag, mapFragment)
217           .commitNow();
218     }
219     avatarImageView = view.findViewById(R.id.answer_message_avatar);
220     if (avatarImageView != null) {
221       avatarImageView.setVisibility(showAvatar ? View.VISIBLE : View.GONE);
222     }
223 
224     Holder parent = FragmentUtils.getParent(this, Holder.class);
225     if (parent != null) {
226       parent.updateAvatar(this);
227     }
228   }
229 
230   @Nullable
231   @Override
getAvatarImageView()232   public ImageView getAvatarImageView() {
233     return avatarImageView;
234   }
235 
236   @Override
getAvatarSize()237   public int getAvatarSize() {
238     return getResources().getDimensionPixelSize(R.dimen.answer_message_avatar_size);
239   }
240 
241   @Override
shouldShowAnonymousAvatar()242   public boolean shouldShowAnonymousAvatar() {
243     return showAvatar;
244   }
245 
246   @Nullable
getSubject()247   public String getSubject() {
248     return getArguments().getString(ARG_SUBJECT);
249   }
250 
251   @Nullable
getImageUri()252   public Uri getImageUri() {
253     return getArguments().getParcelable(ARG_IMAGE);
254   }
255 
256   @Nullable
getLocation()257   public Location getLocation() {
258     return getArguments().getParcelable(ARG_LOCATION);
259   }
260 
261   /** Interface for notifying the fragment parent of changes. */
262   public interface Holder {
updateAvatar(AvatarPresenter sessionDataScreen)263     void updateAvatar(AvatarPresenter sessionDataScreen);
264   }
265 }
266