1 /*
2  * Copyright (C) 2017 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.calllocation.impl;
18 
19 import android.animation.LayoutTransition;
20 import android.content.Context;
21 import android.graphics.drawable.Drawable;
22 import android.location.Location;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.text.TextUtils;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.ImageView;
30 import android.widget.TextView;
31 import android.widget.ViewAnimator;
32 import com.android.dialer.common.LogUtil;
33 import com.android.dialer.logging.DialerImpression;
34 import com.android.dialer.logging.Logger;
35 import com.android.incallui.baseui.BaseFragment;
36 import java.util.Objects;
37 import java.util.concurrent.TimeUnit;
38 
39 /**
40  * Fragment which shows location during E911 calls, to supplement the user with accurate location
41  * information in case the user is asked for their location by the emergency responder.
42  *
43  * <p>If location data is inaccurate, stale, or unavailable, this should not be shown.
44  */
45 public class LocationFragment extends BaseFragment<LocationPresenter, LocationPresenter.LocationUi>
46     implements LocationPresenter.LocationUi {
47 
48   private static final String ADDRESS_DELIMITER = ",";
49 
50   // Indexes used to animate fading between views
51   private static final int LOADING_VIEW_INDEX = 0;
52   private static final int LOCATION_VIEW_INDEX = 1;
53   private static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
54 
55   private ViewAnimator viewAnimator;
56   private ImageView locationMap;
57   private TextView addressLine1;
58   private TextView addressLine2;
59   private TextView latLongLine;
60   private Location location;
61   private ViewGroup locationLayout;
62 
63   private boolean isMapSet;
64   private boolean isAddressSet;
65   private boolean isLocationSet;
66   private boolean hasTimeoutStarted;
67 
68   private final Handler handler = new Handler();
69   private final Runnable dataTimeoutRunnable =
70       () -> {
71         LogUtil.i(
72             "LocationFragment.dataTimeoutRunnable",
73             "timed out so animate any future layout changes");
74         locationLayout.setLayoutTransition(new LayoutTransition());
75         showLocationNow();
76       };
77 
78   @Override
createPresenter()79   public LocationPresenter createPresenter() {
80     return new LocationPresenter();
81   }
82 
83   @Override
getUi()84   public LocationPresenter.LocationUi getUi() {
85     return this;
86   }
87 
88   @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)89   public View onCreateView(
90       LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
91     LogUtil.enterBlock("LocationFragment.onCreateView");
92     final View view = inflater.inflate(R.layout.location_fragment, container, false);
93     viewAnimator = (ViewAnimator) view.findViewById(R.id.location_view_animator);
94     locationMap = (ImageView) view.findViewById(R.id.location_map);
95     addressLine1 = (TextView) view.findViewById(R.id.address_line_one);
96     addressLine2 = (TextView) view.findViewById(R.id.address_line_two);
97     latLongLine = (TextView) view.findViewById(R.id.lat_long_line);
98     locationLayout = (ViewGroup) view.findViewById(R.id.location_layout);
99     return view;
100   }
101 
102   @Override
onDestroy()103   public void onDestroy() {
104     super.onDestroy();
105     handler.removeCallbacks(dataTimeoutRunnable);
106   }
107 
108   @Override
setMap(Drawable mapImage)109   public void setMap(Drawable mapImage) {
110     LogUtil.enterBlock("LocationFragment.setMap");
111     isMapSet = true;
112     locationMap.setVisibility(View.VISIBLE);
113     locationMap.setImageDrawable(mapImage);
114     displayWhenReady();
115     Logger.get(getContext()).logImpression(DialerImpression.Type.EMERGENCY_GOT_MAP);
116   }
117 
118   @Override
setAddress(String address)119   public void setAddress(String address) {
120     LogUtil.i("LocationFragment.setAddress", address);
121     isAddressSet = true;
122     addressLine1.setVisibility(View.VISIBLE);
123     addressLine2.setVisibility(View.VISIBLE);
124     if (TextUtils.isEmpty(address)) {
125       addressLine1.setText(null);
126       addressLine2.setText(null);
127     } else {
128 
129       // Split the address after the first delimiter for display, if present.
130       // For example, "1600 Amphitheatre Parkway, Mountain View, CA 94043"
131       //     => "1600 Amphitheatre Parkway"
132       //     => "Mountain View, CA 94043"
133       int splitIndex = address.indexOf(ADDRESS_DELIMITER);
134       if (splitIndex >= 0) {
135         updateText(addressLine1, address.substring(0, splitIndex).trim());
136         updateText(addressLine2, address.substring(splitIndex + 1).trim());
137       } else {
138         updateText(addressLine1, address);
139         updateText(addressLine2, null);
140       }
141 
142       Logger.get(getContext()).logImpression(DialerImpression.Type.EMERGENCY_GOT_ADDRESS);
143     }
144     displayWhenReady();
145   }
146 
147   @Override
setLocation(Location location)148   public void setLocation(Location location) {
149     LogUtil.i("LocationFragment.setLocation", String.valueOf(location));
150     isLocationSet = true;
151     this.location = location;
152 
153     if (location != null) {
154       latLongLine.setVisibility(View.VISIBLE);
155       latLongLine.setText(
156           getContext()
157               .getString(
158                   R.string.lat_long_format, location.getLatitude(), location.getLongitude()));
159 
160       Logger.get(getContext()).logImpression(DialerImpression.Type.EMERGENCY_GOT_LOCATION);
161     }
162     displayWhenReady();
163   }
164 
displayWhenReady()165   private void displayWhenReady() {
166     // Show the location if all data has loaded, otherwise prime the timeout
167     if (isMapSet && isAddressSet && isLocationSet) {
168       showLocationNow();
169     } else if (!hasTimeoutStarted) {
170       handler.postDelayed(dataTimeoutRunnable, TIMEOUT_MILLIS);
171       hasTimeoutStarted = true;
172     }
173   }
174 
showLocationNow()175   private void showLocationNow() {
176     handler.removeCallbacks(dataTimeoutRunnable);
177     if (viewAnimator.getDisplayedChild() != LOCATION_VIEW_INDEX) {
178       viewAnimator.setDisplayedChild(LOCATION_VIEW_INDEX);
179       viewAnimator.setOnClickListener(v -> launchMap());
180     }
181   }
182 
183   @Override
getContext()184   public Context getContext() {
185     return getActivity();
186   }
187 
launchMap()188   private void launchMap() {
189     if (location != null) {
190       startActivity(
191           LocationUrlBuilder.getShowMapIntent(
192               location, addressLine1.getText(), addressLine2.getText()));
193 
194       Logger.get(getContext()).logImpression(DialerImpression.Type.EMERGENCY_LAUNCHED_MAP);
195     }
196   }
197 
updateText(TextView view, String text)198   private static void updateText(TextView view, String text) {
199     if (!Objects.equals(text, view.getText())) {
200       view.setText(text);
201     }
202   }
203 }
204