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