1 /*
2  * Copyright (C) 2007 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.settings;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.Looper;
23 import android.os.UserHandle;
24 import android.provider.Settings;
25 import android.telephony.PhoneStateListener;
26 import android.telephony.SubscriptionInfo;
27 import android.telephony.TelephonyManager;
28 import android.util.Log;
29 
30 import androidx.annotation.VisibleForTesting;
31 
32 import com.android.internal.telephony.flags.Flags;
33 import com.android.settings.network.GlobalSettingsChangeListener;
34 import com.android.settings.network.ProxySubscriptionManager;
35 import com.android.settings.overlay.FeatureFactory;
36 import com.android.settingslib.WirelessUtils;
37 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
38 
39 import java.util.List;
40 
41 /**
42  * Monitor and update configuration of airplane mode settings
43  */
44 public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
45 
46     private static final String LOG_TAG = "AirplaneModeEnabler";
47     private static final boolean DEBUG = false;
48 
49     private final Context mContext;
50     private final MetricsFeatureProvider mMetricsFeatureProvider;
51 
52     private OnAirplaneModeChangedListener mOnAirplaneModeChangedListener;
53 
54     public interface OnAirplaneModeChangedListener {
55         /**
56          * Called when airplane mode status is changed.
57          *
58          * @param isAirplaneModeOn the airplane mode is on
59          */
onAirplaneModeChanged(boolean isAirplaneModeOn)60         void onAirplaneModeChanged(boolean isAirplaneModeOn);
61     }
62 
63     private TelephonyManager mTelephonyManager;
64     @VisibleForTesting
65     PhoneStateListener mPhoneStateListener;
66 
AirplaneModeEnabler(Context context, OnAirplaneModeChangedListener listener)67     public AirplaneModeEnabler(Context context, OnAirplaneModeChangedListener listener) {
68         super(context, Settings.Global.AIRPLANE_MODE_ON);
69 
70         mContext = context;
71         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
72         mOnAirplaneModeChangedListener = listener;
73 
74         mTelephonyManager = context.getSystemService(TelephonyManager.class);
75 
76         mPhoneStateListener = new PhoneStateListener(Looper.getMainLooper()) {
77             @Override
78             public void onRadioPowerStateChanged(int state) {
79                 if (DEBUG) {
80                     Log.d(LOG_TAG, "RadioPower: " + state);
81                 }
82                 onAirplaneModeChanged();
83             }
84         };
85     }
86 
87     /**
88      * Implementation of GlobalSettingsChangeListener.onChanged
89      */
90     @Override
onChanged(String field)91     public void onChanged(String field) {
92         if (DEBUG) {
93             Log.d(LOG_TAG, "Airplane mode configuration update");
94         }
95         onAirplaneModeChanged();
96     }
97 
98     /**
99      * Start listening to the phone state change
100      */
start()101     public void start() {
102         mTelephonyManager.listen(mPhoneStateListener,
103                 PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED);
104     }
105 
106     /**
107      * Stop listening to the phone state change
108      */
stop()109     public void stop() {
110         mTelephonyManager.listen(mPhoneStateListener,
111                 PhoneStateListener.LISTEN_NONE);
112     }
113 
setAirplaneModeOn(boolean enabling)114     private void setAirplaneModeOn(boolean enabling) {
115         // Change the system setting
116         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON,
117                 enabling ? 1 : 0);
118 
119         // Notify listener the system setting is changed.
120         if (mOnAirplaneModeChangedListener != null) {
121             mOnAirplaneModeChangedListener.onAirplaneModeChanged(enabling);
122         }
123 
124         // Post the intent
125         final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
126         intent.putExtra("state", enabling);
127         mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
128     }
129 
130     /**
131      * Called when we've received confirmation that the airplane mode was set.
132      * TODO: We update the checkbox summary when we get notified
133      * that mobile radio is powered up/down. We should not have dependency
134      * on one radio alone. We need to do the following:
135      * - handle the case of wifi/bluetooth failures
136      * - mobile does not send failure notification, fail on timeout.
137      */
onAirplaneModeChanged()138     private void onAirplaneModeChanged() {
139         if (mOnAirplaneModeChangedListener != null) {
140             mOnAirplaneModeChangedListener.onAirplaneModeChanged(isAirplaneModeOn());
141         }
142     }
143 
144     /**
145      * Check the status of ECM mode
146      *
147      * @return any subscription within device is under ECM mode
148      */
isInEcmMode()149     public boolean isInEcmMode() {
150         if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
151             try {
152                 if (mTelephonyManager.getEmergencyCallbackMode()) {
153                     return true;
154                 }
155             } catch (UnsupportedOperationException e) {
156                 // Device doesn't support FEATURE_TELEPHONY_CALLING
157                 // Ignore exception, device is not in ECM mode.
158             }
159         } else {
160             if (mTelephonyManager.getEmergencyCallbackMode()) {
161                 return true;
162             }
163         }
164         final List<SubscriptionInfo> subInfoList =
165                 ProxySubscriptionManager.getInstance(mContext).getActiveSubscriptionsInfo();
166         if (subInfoList == null) {
167             return false;
168         }
169         for (SubscriptionInfo subInfo : subInfoList) {
170             final TelephonyManager telephonyManager =
171                     mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
172             if (telephonyManager != null) {
173                 if (!Flags.enforceTelephonyFeatureMappingForPublicApis()) {
174                     if (telephonyManager.getEmergencyCallbackMode()) {
175                         return true;
176                     }
177                 } else {
178                     try {
179                         if (telephonyManager.getEmergencyCallbackMode()) {
180                             return true;
181                         }
182                     } catch (UnsupportedOperationException e) {
183                         // Ignore exception, device is not in ECM mode.
184                     }
185                 }
186             }
187         }
188         return false;
189     }
190 
setAirplaneMode(boolean isAirplaneModeOn)191     public void setAirplaneMode(boolean isAirplaneModeOn) {
192         if (isInEcmMode()) {
193             // In ECM mode, do not update database at this point
194             Log.d(LOG_TAG, "ECM airplane mode=" + isAirplaneModeOn);
195         } else {
196             mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AIRPLANE_TOGGLE,
197                     isAirplaneModeOn);
198             setAirplaneModeOn(isAirplaneModeOn);
199         }
200     }
201 
setAirplaneModeInECM(boolean isECMExit, boolean isAirplaneModeOn)202     public void setAirplaneModeInECM(boolean isECMExit, boolean isAirplaneModeOn) {
203         Log.d(LOG_TAG, "Exist ECM=" + isECMExit + ", with airplane mode=" + isAirplaneModeOn);
204         if (isECMExit) {
205             // update database based on the current checkbox state
206             setAirplaneModeOn(isAirplaneModeOn);
207         } else {
208             // update summary
209             onAirplaneModeChanged();
210         }
211     }
212 
isAirplaneModeOn()213     public boolean isAirplaneModeOn() {
214         return WirelessUtils.isAirplaneModeOn(mContext);
215     }
216 }
217 
218