1 /*
2  * Copyright (C) 2018 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.phone;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.ResolveInfo;
25 import android.graphics.Bitmap;
26 import android.graphics.drawable.Drawable;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.telephony.TelephonyManager;
30 import android.text.TextUtils;
31 import android.util.AttributeSet;
32 import android.view.MotionEvent;
33 import android.view.View;
34 import android.view.ViewAnimationUtils;
35 import android.view.accessibility.AccessibilityManager;
36 import android.widget.FrameLayout;
37 import android.widget.ImageView;
38 import android.widget.TextView;
39 
40 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
41 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
42 
43 import com.android.internal.util.UserIcons;
44 
45 import java.util.List;
46 
47 /**
48  * EmergencyInfoGroup display user icon and user name. And it is an entry point to
49  * Emergency Information.
50  */
51 public class EmergencyInfoGroup extends FrameLayout implements View.OnClickListener {
52     // Time to hide view of confirmation.
53     private static final long HIDE_DELAY_MS = 3000;
54     private static final int[] ICON_VIEWS =
55             {R.id.emergency_info_image, R.id.confirmed_emergency_info_image};
56 
57     private TextView mEmergencyInfoName;
58     private View mEmergencyInfoButton;
59     private View mEmergencyInfoConfirmButton;
60 
61     private MotionEvent mPendingTouchEvent;
62     private OnConfirmClickListener mOnConfirmClickListener;
63 
64     private boolean mConfirmViewHiding;
65 
EmergencyInfoGroup(Context context, @Nullable AttributeSet attrs)66     public EmergencyInfoGroup(Context context, @Nullable AttributeSet attrs) {
67         super(context, attrs);
68     }
69 
70     /**
71      * Interface definition for a callback to be invoked when the view of confirmation on emergency
72      * info button is clicked.
73      */
74     public interface OnConfirmClickListener {
75         /**
76          * Called when the view of confirmation on emergency info button has been clicked.
77          *
78          * @param button The shortcut button that was clicked.
79          */
onConfirmClick(EmergencyInfoGroup button)80         void onConfirmClick(EmergencyInfoGroup button);
81     }
82 
83     /**
84      * Register a callback {@link OnConfirmClickListener} to be invoked when view of confirmation
85      * is clicked.
86      *
87      * @param onConfirmClickListener The callback that will run.
88      */
setOnConfirmClickListener(OnConfirmClickListener onConfirmClickListener)89     public void setOnConfirmClickListener(OnConfirmClickListener onConfirmClickListener) {
90         mOnConfirmClickListener = onConfirmClickListener;
91     }
92 
93     @Override
onFinishInflate()94     protected void onFinishInflate() {
95         super.onFinishInflate();
96         mEmergencyInfoButton = findViewById(R.id.emergency_info_view);
97         mEmergencyInfoName = (TextView) findViewById(R.id.emergency_info_name);
98 
99         mEmergencyInfoConfirmButton = findViewById(R.id.emergency_info_confirm_view);
100 
101         mEmergencyInfoButton.setOnClickListener(this);
102         mEmergencyInfoConfirmButton.setOnClickListener(this);
103 
104         mConfirmViewHiding = true;
105     }
106 
107     @Override
onWindowVisibilityChanged(int visibility)108     protected void onWindowVisibilityChanged(int visibility) {
109         super.onWindowVisibilityChanged(visibility);
110         if (visibility == View.VISIBLE) {
111             setupButtonInfo();
112         }
113     }
114 
setupButtonInfo()115     private void setupButtonInfo() {
116         List<ResolveInfo> infos;
117 
118         if (TelephonyManager.EMERGENCY_ASSISTANCE_ENABLED) {
119             infos = EmergencyAssistanceHelper.resolveAssistPackageAndQueryActivities(getContext());
120         } else {
121             infos = null;
122         }
123 
124         boolean visible = false;
125 
126         if (infos != null && infos.size() > 0) {
127             final String packageName = infos.get(0).activityInfo.packageName;
128             final Intent intent = new Intent(
129                     EmergencyAssistanceHelper.getIntentAction())
130                     .setPackage(packageName);
131             setTag(R.id.tag_intent, intent);
132             setUserIcon();
133 
134             visible = true;
135         }
136         mEmergencyInfoName.setText(getUserName());
137 
138         setVisibility(visible ? View.VISIBLE : View.GONE);
139     }
140 
setUserIcon()141     private void setUserIcon() {
142         for (int iconView : ICON_VIEWS) {
143             ImageView userIcon = findViewById(iconView);
144             userIcon.setImageDrawable(getCircularUserIcon());
145         }
146     }
147 
148     /**
149      * Get user icon.
150      *
151      * @return user icon, or default user icon if user do not set photo.
152      */
getCircularUserIcon()153     private Drawable getCircularUserIcon() {
154         final UserManager userManager = (UserManager) getContext().getSystemService(
155                 Context.USER_SERVICE);
156         Bitmap bitmapUserIcon = userManager.getUserIcon();
157 
158         if (bitmapUserIcon == null) {
159             // get default user icon.
160             final Drawable defaultUserIcon = UserIcons.getDefaultUserIcon(
161                     getContext().getResources(), UserHandle.myUserId(), false);
162             bitmapUserIcon = UserIcons.convertToBitmap(defaultUserIcon);
163         }
164         RoundedBitmapDrawable drawableUserIcon = RoundedBitmapDrawableFactory.create(
165                 getContext().getResources(), bitmapUserIcon);
166         drawableUserIcon.setCircular(true);
167 
168         return drawableUserIcon;
169     }
170 
getUserName()171     private CharSequence getUserName() {
172         final UserManager userManager = (UserManager) getContext().getSystemService(
173                 Context.USER_SERVICE);
174         final String userName = userManager.getUserName();
175 
176         return TextUtils.isEmpty(userName) ? getContext().getText(
177                 R.string.emergency_information_owner_hint) : userName;
178     }
179 
180     /**
181      * Called by the activity before a touch event is dispatched to the view hierarchy.
182      */
onPreTouchEvent(MotionEvent event)183     public void onPreTouchEvent(MotionEvent event) {
184         mPendingTouchEvent = event;
185     }
186 
187     /**
188      * Called by the activity after a touch event is dispatched to the view hierarchy.
189      */
onPostTouchEvent(MotionEvent event)190     public void onPostTouchEvent(MotionEvent event) {
191         // Hide the confirmation button if a touch event was delivered to the activity but not to
192         // this view.
193         if (mPendingTouchEvent != null) {
194             hideSelectedButton();
195         }
196         mPendingTouchEvent = null;
197     }
198 
199     @Override
dispatchTouchEvent(MotionEvent event)200     public boolean dispatchTouchEvent(MotionEvent event) {
201         boolean handled = super.dispatchTouchEvent(event);
202         if (mPendingTouchEvent == event && handled) {
203             mPendingTouchEvent = null;
204         }
205         return handled;
206     }
207 
208     @Override
onClick(View view)209     public void onClick(View view) {
210         if (view.getId() == R.id.emergency_info_view) {
211             AccessibilityManager accessibilityMgr =
212                     (AccessibilityManager) getContext().getSystemService(
213                             Context.ACCESSIBILITY_SERVICE);
214             if (accessibilityMgr.isTouchExplorationEnabled()) {
215                 if (mOnConfirmClickListener != null) {
216                     mOnConfirmClickListener.onConfirmClick(this);
217                 }
218             } else {
219                 revealSelectedButton();
220             }
221         } else if (view.getId() == R.id.emergency_info_confirm_view) {
222             if (mOnConfirmClickListener != null) {
223                 mOnConfirmClickListener.onConfirmClick(this);
224             }
225         }
226     }
227 
revealSelectedButton()228     private void revealSelectedButton() {
229         mConfirmViewHiding = false;
230 
231         mEmergencyInfoConfirmButton.setVisibility(View.VISIBLE);
232         int centerX = mEmergencyInfoButton.getLeft() + mEmergencyInfoButton.getWidth() / 2;
233         int centerY = mEmergencyInfoButton.getTop() + mEmergencyInfoButton.getHeight() / 2;
234         Animator reveal = ViewAnimationUtils.createCircularReveal(
235                 mEmergencyInfoConfirmButton,
236                 centerX,
237                 centerY,
238                 0,
239                 Math.max(centerX, mEmergencyInfoConfirmButton.getWidth() - centerX)
240                         + Math.max(centerY, mEmergencyInfoConfirmButton.getHeight() - centerY));
241         reveal.start();
242 
243         postDelayed(mCancelSelectedButtonRunnable, HIDE_DELAY_MS);
244         mEmergencyInfoConfirmButton.requestFocus();
245     }
246 
hideSelectedButton()247     private void hideSelectedButton() {
248         if (mConfirmViewHiding || mEmergencyInfoConfirmButton.getVisibility() != VISIBLE) {
249             return;
250         }
251 
252         mConfirmViewHiding = true;
253 
254         removeCallbacks(mCancelSelectedButtonRunnable);
255         int centerX =
256                 mEmergencyInfoConfirmButton.getLeft() + mEmergencyInfoConfirmButton.getWidth() / 2;
257         int centerY =
258                 mEmergencyInfoConfirmButton.getTop() + mEmergencyInfoConfirmButton.getHeight() / 2;
259         Animator reveal = ViewAnimationUtils.createCircularReveal(
260                 mEmergencyInfoConfirmButton,
261                 centerX,
262                 centerY,
263                 Math.max(centerX, mEmergencyInfoButton.getWidth() - centerX)
264                         + Math.max(centerY, mEmergencyInfoButton.getHeight() - centerY),
265                 0);
266         reveal.addListener(new AnimatorListenerAdapter() {
267             @Override
268             public void onAnimationEnd(Animator animation) {
269                 mEmergencyInfoConfirmButton.setVisibility(INVISIBLE);
270             }
271         });
272         reveal.start();
273 
274         mEmergencyInfoButton.requestFocus();
275     }
276 
277     private final Runnable mCancelSelectedButtonRunnable = new Runnable() {
278         @Override
279         public void run() {
280             if (!isAttachedToWindow()) return;
281             hideSelectedButton();
282         }
283     };
284 }
285