1 /*
2  * Copyright (C) 2008 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.keyguard;
18 
19 import static com.android.systemui.DejankUtils.whitelistIpcs;
20 
21 import android.app.ActivityOptions;
22 import android.app.ActivityTaskManager;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.res.Configuration;
26 import android.os.PowerManager;
27 import android.os.RemoteException;
28 import android.os.SystemClock;
29 import android.os.UserHandle;
30 import android.telecom.TelecomManager;
31 import android.telephony.TelephonyManager;
32 import android.util.AttributeSet;
33 import android.util.Log;
34 import android.util.Slog;
35 import android.view.MotionEvent;
36 import android.view.View;
37 import android.view.ViewConfiguration;
38 import android.widget.Button;
39 
40 import com.android.internal.logging.MetricsLogger;
41 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
42 import com.android.internal.util.EmergencyAffordanceManager;
43 import com.android.internal.widget.LockPatternUtils;
44 import com.android.systemui.Dependency;
45 import com.android.systemui.util.EmergencyDialerConstants;
46 
47 /**
48  * This class implements a smart emergency button that updates itself based
49  * on telephony state.  When the phone is idle, it is an emergency call button.
50  * When there's a call in progress, it presents an appropriate message and
51  * allows the user to return to the call.
52  */
53 public class EmergencyButton extends Button {
54 
55     private static final String LOG_TAG = "EmergencyButton";
56     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
57 
58     private int mDownX;
59     private int mDownY;
60     KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
61 
62         @Override
63         public void onSimStateChanged(int subId, int slotId, int simState) {
64             updateEmergencyCallButton();
65         }
66 
67         @Override
68         public void onPhoneStateChanged(int phoneState) {
69             updateEmergencyCallButton();
70         }
71     };
72     private boolean mLongPressWasDragged;
73 
74     public interface EmergencyButtonCallback {
onEmergencyButtonClickedWhenInCall()75         public void onEmergencyButtonClickedWhenInCall();
76     }
77 
78     private LockPatternUtils mLockPatternUtils;
79     private PowerManager mPowerManager;
80     private EmergencyButtonCallback mEmergencyButtonCallback;
81 
82     private final boolean mIsVoiceCapable;
83     private final boolean mEnableEmergencyCallWhileSimLocked;
84 
EmergencyButton(Context context)85     public EmergencyButton(Context context) {
86         this(context, null);
87     }
88 
EmergencyButton(Context context, AttributeSet attrs)89     public EmergencyButton(Context context, AttributeSet attrs) {
90         super(context, attrs);
91         mIsVoiceCapable = getTelephonyManager().isVoiceCapable();
92         mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
93                 com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
94         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
95     }
96 
getTelephonyManager()97     private TelephonyManager getTelephonyManager() {
98         return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
99     }
100 
101     @Override
onAttachedToWindow()102     protected void onAttachedToWindow() {
103         super.onAttachedToWindow();
104         Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
105     }
106 
107     @Override
onDetachedFromWindow()108     protected void onDetachedFromWindow() {
109         super.onDetachedFromWindow();
110         Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
111     }
112 
113     @Override
onFinishInflate()114     protected void onFinishInflate() {
115         super.onFinishInflate();
116         mLockPatternUtils = new LockPatternUtils(mContext);
117         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
118         setOnClickListener(v -> takeEmergencyCallAction());
119         if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
120             setOnLongClickListener(v -> {
121                 if (!mLongPressWasDragged
122                         && mEmergencyAffordanceManager.needsEmergencyAffordance()) {
123                     mEmergencyAffordanceManager.performEmergencyCall();
124                     return true;
125                 }
126                 return false;
127             });
128         }
129         whitelistIpcs(this::updateEmergencyCallButton);
130     }
131 
132     @Override
onTouchEvent(MotionEvent event)133     public boolean onTouchEvent(MotionEvent event) {
134         final int x = (int) event.getX();
135         final int y = (int) event.getY();
136         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
137             mDownX = x;
138             mDownY = y;
139             mLongPressWasDragged = false;
140         } else {
141             final int xDiff = Math.abs(x - mDownX);
142             final int yDiff = Math.abs(y - mDownY);
143             int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
144             if (Math.abs(yDiff) > touchSlop || Math.abs(xDiff) > touchSlop) {
145                 mLongPressWasDragged = true;
146             }
147         }
148         return super.onTouchEvent(event);
149     }
150 
151     @Override
performLongClick()152     public boolean performLongClick() {
153         return super.performLongClick();
154     }
155 
156     @Override
onConfigurationChanged(Configuration newConfig)157     protected void onConfigurationChanged(Configuration newConfig) {
158         super.onConfigurationChanged(newConfig);
159         updateEmergencyCallButton();
160     }
161 
162     /**
163      * Shows the emergency dialer or returns the user to the existing call.
164      */
takeEmergencyCallAction()165     public void takeEmergencyCallAction() {
166         MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
167         if (mPowerManager != null) {
168             mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
169         }
170         try {
171             ActivityTaskManager.getService().stopSystemLockTaskMode();
172         } catch (RemoteException e) {
173             Slog.w(LOG_TAG, "Failed to stop app pinning");
174         }
175         if (isInCall()) {
176             resumeCall();
177             if (mEmergencyButtonCallback != null) {
178                 mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
179             }
180         } else {
181             KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
182             if (updateMonitor != null) {
183                 updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
184             } else {
185                 Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
186             }
187             TelecomManager telecomManager = getTelecommManager();
188             if (telecomManager == null) {
189                 Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
190                 return;
191             }
192             Intent emergencyDialIntent =
193                     telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
194                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
195                                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
196                                     | Intent.FLAG_ACTIVITY_CLEAR_TOP)
197                             .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
198                                     EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
199 
200             getContext().startActivityAsUser(emergencyDialIntent,
201                     ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
202                     new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
203         }
204     }
205 
updateEmergencyCallButton()206     private void updateEmergencyCallButton() {
207         boolean visible = false;
208         if (mIsVoiceCapable) {
209             // Emergency calling requires voice capability.
210             if (isInCall()) {
211                 visible = true; // always show "return to call" if phone is off-hook
212             } else {
213                 final boolean simLocked = Dependency.get(KeyguardUpdateMonitor.class)
214                         .isSimPinVoiceSecure();
215                 if (simLocked) {
216                     // Some countries can't handle emergency calls while SIM is locked.
217                     visible = mEnableEmergencyCallWhileSimLocked;
218                 } else {
219                     // Only show if there is a secure screen (pin/pattern/SIM pin/SIM puk);
220                     visible = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
221                 }
222             }
223         }
224         if (visible) {
225             setVisibility(View.VISIBLE);
226 
227             int textId;
228             if (isInCall()) {
229                 textId = com.android.internal.R.string.lockscreen_return_to_call;
230             } else {
231                 textId = com.android.internal.R.string.lockscreen_emergency_call;
232             }
233             setText(textId);
234         } else {
235             setVisibility(View.GONE);
236         }
237     }
238 
setCallback(EmergencyButtonCallback callback)239     public void setCallback(EmergencyButtonCallback callback) {
240         mEmergencyButtonCallback = callback;
241     }
242 
243     /**
244      * Resumes a call in progress.
245      */
resumeCall()246     private void resumeCall() {
247         getTelecommManager().showInCallScreen(false);
248     }
249 
250     /**
251      * @return {@code true} if there is a call currently in progress.
252      */
isInCall()253     private boolean isInCall() {
254         return getTelecommManager().isInCall();
255     }
256 
getTelecommManager()257     private TelecomManager getTelecommManager() {
258         return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
259     }
260 }
261