1 /*
2  * Copyright (C) 2024 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.network.telephony;
18 
19 import android.content.Context;
20 import android.os.Build;
21 import android.safetycenter.SafetyCenterManager;
22 import android.telephony.TelephonyManager;
23 import android.util.Log;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.VisibleForTesting;
27 
28 import com.android.internal.telephony.flags.Flags;
29 
30 /**
31  * {@link TelephonyTogglePreferenceController} for accessing Cellular Security settings through
32  * Safety Center.
33  */
34 public class CellularSecurityNotificationsPreferenceController extends
35                 TelephonyTogglePreferenceController {
36 
37     private static final String LOG_TAG = "CellularSecurityNotificationsPreferenceController";
38 
39     private TelephonyManager mTelephonyManager;
40     @VisibleForTesting
41     protected SafetyCenterManager mSafetyCenterManager;
42 
43     /**
44      * Class constructor of "Cellular Security" preference.
45      *
46      * @param context of settings
47      * @param prefKey assigned within UI entry of XML file
48      */
CellularSecurityNotificationsPreferenceController( @onNull Context context, @NonNull String prefKey)49     public CellularSecurityNotificationsPreferenceController(
50             @NonNull Context context, @NonNull String prefKey) {
51         super(context, prefKey);
52         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
53         mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);
54     }
55 
56     /**
57      * Initialization based on a given subscription id.
58      *
59      * @param subId is the subscription id
60      * @return this instance after initialization
61      */
init(@onNull int subId)62     @NonNull public CellularSecurityNotificationsPreferenceController init(@NonNull int subId) {
63         mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
64                 .createForSubscriptionId(subId);
65         return this;
66     }
67 
68     @Override
getAvailabilityStatus(int subId)69     public int getAvailabilityStatus(int subId) {
70         if (!isSafetyCenterSupported()) {
71             return UNSUPPORTED_ON_DEVICE;
72         }
73 
74         if (!areFlagsEnabled()) {
75             return UNSUPPORTED_ON_DEVICE;
76         }
77 
78         // Checking for hardware support, i.e. IRadio AIDL version must be >= 2.2
79         try {
80             areNotificationsEnabled();
81         } catch (UnsupportedOperationException e) {
82             Log.i(LOG_TAG, "Cellular security notifications are unsupported: " + e.getMessage());
83             return UNSUPPORTED_ON_DEVICE;
84         }
85 
86         return AVAILABLE;
87     }
88 
89     /**
90      * Return {@code true} if cellular security notifications are on
91      *
92      * <p><b>NOTE:</b> This method returns the active state of the preference controller and is not
93      * the parameter passed into {@link #setChecked(boolean)}, which is instead the requested future
94      * state.
95      */
96     @Override
isChecked()97     public boolean isChecked() {
98         if (!areFlagsEnabled()) {
99             return false;
100         }
101 
102         try {
103             // Note: the default behavior for this toggle is disabled (as the underlying
104             // TelephonyManager APIs are disabled by default)
105             return areNotificationsEnabled();
106         } catch (Exception e) {
107             Log.e(LOG_TAG,
108                     "Failed isNullCipherNotificationsEnabled and "
109                             + "isCellularIdentifierDisclosureNotificationsEnabled."
110                             + "Defaulting toggle to checked = true. Exception: "
111                             + e.getMessage());
112             return false;
113         }
114     }
115 
116     /**
117      * Called when a user preference changes on the toggle. We pass this info on to the Telephony
118      * Framework so that the modem can be updated with the user's preference.
119      *
120      * <p>See {@link com.android.settings.core.TogglePreferenceController#setChecked(boolean)} for
121      * details.
122      *
123      * @param isChecked The toggle value that we're being requested to enforce. A value of {@code
124      *                  true} denotes that both (1) null cipher/integrity notifications, and
125      *                  (2) IMSI disclosure notifications will be enabled by the modem after this
126      *                  function completes, if they are not already.
127      */
128     @Override
setChecked(boolean isChecked)129     public boolean setChecked(boolean isChecked) {
130         if (isChecked) {
131             Log.i(LOG_TAG, "Enabling cellular security notifications.");
132         } else {
133             Log.i(LOG_TAG, "Disabling cellular security notifications.");
134         }
135 
136         // Check flag status
137         if (!areFlagsEnabled()) {
138             return false;
139         }
140 
141         try {
142             setNotifications(isChecked);
143         } catch (Exception e) {
144             Log.e(LOG_TAG,
145                     "Failed setCellularIdentifierDisclosureNotificationEnabled or "
146                             + " setNullCipherNotificationsEnabled. Setting not updated. Exception: "
147                             + e.getMessage());
148             // Reset to defaults so we don't end up in an inconsistent state
149             setNotifications(!isChecked);
150             return false;
151         }
152         return true;
153     }
154 
setNotifications(boolean isChecked)155     private void setNotifications(boolean isChecked) {
156         mTelephonyManager.setEnableCellularIdentifierDisclosureNotifications(isChecked);
157         mTelephonyManager.setNullCipherNotificationsEnabled(isChecked);
158     }
159 
areNotificationsEnabled()160     private boolean areNotificationsEnabled() {
161         if (mTelephonyManager == null) {
162             Log.w(LOG_TAG, "Telephony manager not yet initialized");
163             mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
164         }
165         return mTelephonyManager.isNullCipherNotificationsEnabled()
166             && mTelephonyManager.isCellularIdentifierDisclosureNotificationsEnabled();
167     }
168 
areFlagsEnabled()169     private boolean areFlagsEnabled() {
170         if (!Flags.enableIdentifierDisclosureTransparencyUnsolEvents()
171                 || !Flags.enableModemCipherTransparencyUnsolEvents()
172                 || !Flags.enableIdentifierDisclosureTransparency()
173                 || !Flags.enableModemCipherTransparency()) {
174             return false;
175         }
176         return true;
177     }
178 
isSafetyCenterSupported()179     protected boolean isSafetyCenterSupported() {
180         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
181             return false;
182         }
183         mSafetyCenterManager = mContext.getSystemService(
184                 SafetyCenterManager.class);
185         if (mSafetyCenterManager == null) {
186             return false;
187         }
188         return mSafetyCenterManager.isSafetyCenterEnabled();
189     }
190 }
191