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