Loading android/app/Android.bp +2 −1 Original line number Diff line number Diff line Loading @@ -135,6 +135,7 @@ cc_library_shared { "libstatslog_bt", "libudrv-uipc", "libutils", "server_configurable_flags", ], shared_libs: [ "libaaudio", Loading @@ -144,7 +145,6 @@ cc_library_shared { "libnativehelper", "libstatssocket", "libvndksupport", "server_configurable_flags", ], sanitize: { scs: true, Loading Loading @@ -208,6 +208,7 @@ android_app { srcs: [ ":statslog-bluetooth-java-gen", ":statslog-bt-restricted-java-gen", ":system-messages-proto-src", "proto/keystore.proto", "src/**/*.java", ], Loading android/app/AndroidManifest.xml +10 −0 Original line number Diff line number Diff line Loading @@ -103,6 +103,16 @@ </intent-filter> </service> <service android:process="@string/process" android:name="com.android.bluetooth.airplane.NotificationHelperService" android:label="Airplane Notification Helper" android:exported="true" android:permission="android.permission.BLUETOOTH_PRIVILEGED"> <intent-filter> <action android:name="android.bluetooth.airplane.action.SEND_NOTIFICATION"/> </intent-filter> </service> <!-- Advanced Audio Distribution Profile (A2DP) source Profile Service --> <service android:process="@string/process" android:name="com.android.bluetooth.a2dp.A2dpService" Loading android/app/src/com/android/bluetooth/airplane/NotificationHelperService.java 0 → 100644 +142 −0 Original line number Diff line number Diff line /* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.airplane; import static java.util.Objects.requireNonNull; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.net.Uri; import android.os.IBinder; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.Pair; import com.android.bluetooth.R; import com.android.internal.messages.SystemMessageProto.SystemMessage; import java.util.Map; public class NotificationHelperService extends Service { private static final String TAG = NotificationHelperService.class.getSimpleName(); // Keeps track of whether wifi and bt remains on notification was shown private static final String APM_WIFI_BT_NOTIFICATION = "apm_wifi_bt_notification"; // Keeps track of whether bt remains on notification was shown private static final String APM_BT_NOTIFICATION = "apm_bt_notification"; // Keeps track of whether user enabling bt notification was shown private static final String APM_BT_ENABLED_NOTIFICATION = "apm_bt_enabled_notification"; private static final String NOTIFICATION_TAG = "com.android.bluetooth"; private static final String APM_NOTIFICATION_CHANNEL = "apm_notification_channel"; private static final String APM_NOTIFICATION_GROUP = "apm_notification_group"; private static final String HELP_PAGE_URL = "https://support.google.com/pixelphone/answer/12639358"; private static final Map<String, Pair<Integer /* titleId */, Integer /* messageId */>> NOTIFICATION_MAP = Map.of( APM_WIFI_BT_NOTIFICATION, Pair.create( R.string.bluetooth_and_wifi_stays_on_title, R.string.bluetooth_and_wifi_stays_on_message), APM_BT_NOTIFICATION, Pair.create( R.string.bluetooth_stays_on_title, R.string.bluetooth_stays_on_message), APM_BT_ENABLED_NOTIFICATION, Pair.create( R.string.bluetooth_enabled_apm_title, R.string.bluetooth_enabled_apm_message)); @Override public IBinder onBind(Intent intent) { return null; // This is not a bound service } @Override public int onStartCommand(Intent intent, int flags, int startId) { sendAirplaneModeNotification( intent.getStringExtra("android.bluetooth.airplane.extra.NOTIFICATION_STATE")); return Service.START_NOT_STICKY; } private void sendAirplaneModeNotification(String notificationState) { String logHeader = "sendAirplaneModeNotification(" + notificationState + "): "; Pair<Integer, Integer> notificationContent = NOTIFICATION_MAP.get(notificationState); if (notificationContent == null) { Log.e(TAG, logHeader + "unknown action"); return; } if (!isFirstTimeNotification(notificationState)) { Log.d(TAG, logHeader + "already displayed"); return; } Settings.Secure.putInt(getContentResolver(), notificationState, 1); Log.d(TAG, logHeader + "sending"); NotificationManager notificationManager = requireNonNull(getSystemService(NotificationManager.class)); for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { if (NOTIFICATION_TAG.equals(notification.getTag())) { notificationManager.cancel(NOTIFICATION_TAG, notification.getId()); } } notificationManager.createNotificationChannel( new NotificationChannel( APM_NOTIFICATION_CHANNEL, APM_NOTIFICATION_GROUP, NotificationManager.IMPORTANCE_HIGH)); String title = getString(notificationContent.first); String message = getString(notificationContent.second); notificationManager.notify( NOTIFICATION_TAG, SystemMessage.ID.NOTE_BT_APM_NOTIFICATION_VALUE, new Notification.Builder(this, APM_NOTIFICATION_CHANNEL) .setAutoCancel(true) .setLocalOnly(true) .setContentTitle(title) .setContentText(message) .setContentIntent( PendingIntent.getActivity( this, PendingIntent.FLAG_UPDATE_CURRENT, new Intent(Intent.ACTION_VIEW) .setData(Uri.parse(HELP_PAGE_URL)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_IMMUTABLE)) .setVisibility(Notification.VISIBILITY_PUBLIC) .setStyle(new Notification.BigTextStyle().bigText(message)) .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) .build()); } /** Return whether the notification has been shown */ private boolean isFirstTimeNotification(String name) { return Settings.Secure.getInt(getContentResolver(), name, 0) == 0; } } android/app/src/com/android/bluetooth/hfp/AtPhonebook.java +11 −5 Original line number Diff line number Diff line Loading @@ -555,11 +555,17 @@ public class AtPhonebook { // try caller id lookup // TODO: This code is horribly inefficient. I saw it // take 7 seconds to process 100 missed calls. Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mContentResolver, Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, number), new String[]{ PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE }, null, null, null); Cursor c = BluetoothMethodProxy.getInstance() .contentResolverQuery( mContentResolver, Uri.withAppendedPath( PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(number)), new String[] {PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE}, null, null, null); if (c != null) { if (c.moveToFirst()) { name = c.getString(0); Loading android/app/tests/unit/src/com/android/bluetooth/hfp/AtPhonebookTest.java +32 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -40,6 +41,7 @@ import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.util.DevicePolicyUtils; import com.android.internal.telephony.GsmAlphabet; import org.junit.After; Loading Loading @@ -284,6 +286,35 @@ public class AtPhonebookTest { verify(mNativeInterface).atResponseString(mTestDevice, expected); } @Test public void processCpbrCommand_doesNotCrashWithEncodingNeededNumber() { final String encodingNeededNumber = "###0102124"; Cursor mockCursorOne = mock(Cursor.class); when(mockCursorOne.getCount()).thenReturn(1); when(mockCursorOne.getColumnIndex(Phone.TYPE)).thenReturn(1); // TypeColumn when(mockCursorOne.getColumnIndex(Phone.NUMBER)).thenReturn(2); // numberColumn when(mockCursorOne.getColumnIndex(Phone.DISPLAY_NAME)).thenReturn(-1); // nameColumn when(mockCursorOne.getInt(1)).thenReturn(Phone.TYPE_WORK); when(mockCursorOne.getString(2)).thenReturn(encodingNeededNumber); when(mockCursorOne.moveToNext()).thenReturn(false); doReturn(mockCursorOne) .when(mHfpMethodProxy) .contentResolverQuery( any(), eq(DevicePolicyUtils.getEnterprisePhoneUri(mTargetContext)), any(), any(), any()); mAtPhonebook.mCurrentPhonebook = "ME"; mAtPhonebook.mCpbrIndex1 = 1; mAtPhonebook.mCpbrIndex2 = 2; // This call should not crash mAtPhonebook.processCpbrCommand(mTestDevice); } @Test public void setCpbrIndex() { int index = 1; Loading Loading
android/app/Android.bp +2 −1 Original line number Diff line number Diff line Loading @@ -135,6 +135,7 @@ cc_library_shared { "libstatslog_bt", "libudrv-uipc", "libutils", "server_configurable_flags", ], shared_libs: [ "libaaudio", Loading @@ -144,7 +145,6 @@ cc_library_shared { "libnativehelper", "libstatssocket", "libvndksupport", "server_configurable_flags", ], sanitize: { scs: true, Loading Loading @@ -208,6 +208,7 @@ android_app { srcs: [ ":statslog-bluetooth-java-gen", ":statslog-bt-restricted-java-gen", ":system-messages-proto-src", "proto/keystore.proto", "src/**/*.java", ], Loading
android/app/AndroidManifest.xml +10 −0 Original line number Diff line number Diff line Loading @@ -103,6 +103,16 @@ </intent-filter> </service> <service android:process="@string/process" android:name="com.android.bluetooth.airplane.NotificationHelperService" android:label="Airplane Notification Helper" android:exported="true" android:permission="android.permission.BLUETOOTH_PRIVILEGED"> <intent-filter> <action android:name="android.bluetooth.airplane.action.SEND_NOTIFICATION"/> </intent-filter> </service> <!-- Advanced Audio Distribution Profile (A2DP) source Profile Service --> <service android:process="@string/process" android:name="com.android.bluetooth.a2dp.A2dpService" Loading
android/app/src/com/android/bluetooth/airplane/NotificationHelperService.java 0 → 100644 +142 −0 Original line number Diff line number Diff line /* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.airplane; import static java.util.Objects.requireNonNull; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.net.Uri; import android.os.IBinder; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.Pair; import com.android.bluetooth.R; import com.android.internal.messages.SystemMessageProto.SystemMessage; import java.util.Map; public class NotificationHelperService extends Service { private static final String TAG = NotificationHelperService.class.getSimpleName(); // Keeps track of whether wifi and bt remains on notification was shown private static final String APM_WIFI_BT_NOTIFICATION = "apm_wifi_bt_notification"; // Keeps track of whether bt remains on notification was shown private static final String APM_BT_NOTIFICATION = "apm_bt_notification"; // Keeps track of whether user enabling bt notification was shown private static final String APM_BT_ENABLED_NOTIFICATION = "apm_bt_enabled_notification"; private static final String NOTIFICATION_TAG = "com.android.bluetooth"; private static final String APM_NOTIFICATION_CHANNEL = "apm_notification_channel"; private static final String APM_NOTIFICATION_GROUP = "apm_notification_group"; private static final String HELP_PAGE_URL = "https://support.google.com/pixelphone/answer/12639358"; private static final Map<String, Pair<Integer /* titleId */, Integer /* messageId */>> NOTIFICATION_MAP = Map.of( APM_WIFI_BT_NOTIFICATION, Pair.create( R.string.bluetooth_and_wifi_stays_on_title, R.string.bluetooth_and_wifi_stays_on_message), APM_BT_NOTIFICATION, Pair.create( R.string.bluetooth_stays_on_title, R.string.bluetooth_stays_on_message), APM_BT_ENABLED_NOTIFICATION, Pair.create( R.string.bluetooth_enabled_apm_title, R.string.bluetooth_enabled_apm_message)); @Override public IBinder onBind(Intent intent) { return null; // This is not a bound service } @Override public int onStartCommand(Intent intent, int flags, int startId) { sendAirplaneModeNotification( intent.getStringExtra("android.bluetooth.airplane.extra.NOTIFICATION_STATE")); return Service.START_NOT_STICKY; } private void sendAirplaneModeNotification(String notificationState) { String logHeader = "sendAirplaneModeNotification(" + notificationState + "): "; Pair<Integer, Integer> notificationContent = NOTIFICATION_MAP.get(notificationState); if (notificationContent == null) { Log.e(TAG, logHeader + "unknown action"); return; } if (!isFirstTimeNotification(notificationState)) { Log.d(TAG, logHeader + "already displayed"); return; } Settings.Secure.putInt(getContentResolver(), notificationState, 1); Log.d(TAG, logHeader + "sending"); NotificationManager notificationManager = requireNonNull(getSystemService(NotificationManager.class)); for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { if (NOTIFICATION_TAG.equals(notification.getTag())) { notificationManager.cancel(NOTIFICATION_TAG, notification.getId()); } } notificationManager.createNotificationChannel( new NotificationChannel( APM_NOTIFICATION_CHANNEL, APM_NOTIFICATION_GROUP, NotificationManager.IMPORTANCE_HIGH)); String title = getString(notificationContent.first); String message = getString(notificationContent.second); notificationManager.notify( NOTIFICATION_TAG, SystemMessage.ID.NOTE_BT_APM_NOTIFICATION_VALUE, new Notification.Builder(this, APM_NOTIFICATION_CHANNEL) .setAutoCancel(true) .setLocalOnly(true) .setContentTitle(title) .setContentText(message) .setContentIntent( PendingIntent.getActivity( this, PendingIntent.FLAG_UPDATE_CURRENT, new Intent(Intent.ACTION_VIEW) .setData(Uri.parse(HELP_PAGE_URL)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_IMMUTABLE)) .setVisibility(Notification.VISIBILITY_PUBLIC) .setStyle(new Notification.BigTextStyle().bigText(message)) .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) .build()); } /** Return whether the notification has been shown */ private boolean isFirstTimeNotification(String name) { return Settings.Secure.getInt(getContentResolver(), name, 0) == 0; } }
android/app/src/com/android/bluetooth/hfp/AtPhonebook.java +11 −5 Original line number Diff line number Diff line Loading @@ -555,11 +555,17 @@ public class AtPhonebook { // try caller id lookup // TODO: This code is horribly inefficient. I saw it // take 7 seconds to process 100 missed calls. Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mContentResolver, Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, number), new String[]{ PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE }, null, null, null); Cursor c = BluetoothMethodProxy.getInstance() .contentResolverQuery( mContentResolver, Uri.withAppendedPath( PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(number)), new String[] {PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE}, null, null, null); if (c != null) { if (c.moveToFirst()) { name = c.getString(0); Loading
android/app/tests/unit/src/com/android/bluetooth/hfp/AtPhonebookTest.java +32 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -40,6 +41,7 @@ import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.util.DevicePolicyUtils; import com.android.internal.telephony.GsmAlphabet; import org.junit.After; Loading Loading @@ -284,6 +286,35 @@ public class AtPhonebookTest { verify(mNativeInterface).atResponseString(mTestDevice, expected); } @Test public void processCpbrCommand_doesNotCrashWithEncodingNeededNumber() { final String encodingNeededNumber = "###0102124"; Cursor mockCursorOne = mock(Cursor.class); when(mockCursorOne.getCount()).thenReturn(1); when(mockCursorOne.getColumnIndex(Phone.TYPE)).thenReturn(1); // TypeColumn when(mockCursorOne.getColumnIndex(Phone.NUMBER)).thenReturn(2); // numberColumn when(mockCursorOne.getColumnIndex(Phone.DISPLAY_NAME)).thenReturn(-1); // nameColumn when(mockCursorOne.getInt(1)).thenReturn(Phone.TYPE_WORK); when(mockCursorOne.getString(2)).thenReturn(encodingNeededNumber); when(mockCursorOne.moveToNext()).thenReturn(false); doReturn(mockCursorOne) .when(mHfpMethodProxy) .contentResolverQuery( any(), eq(DevicePolicyUtils.getEnterprisePhoneUri(mTargetContext)), any(), any(), any()); mAtPhonebook.mCurrentPhonebook = "ME"; mAtPhonebook.mCpbrIndex1 = 1; mAtPhonebook.mCpbrIndex2 = 2; // This call should not crash mAtPhonebook.processCpbrCommand(mTestDevice); } @Test public void setCpbrIndex() { int index = 1; Loading