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