diff --git a/java/com/android/dialer/callrecord/impl/CallRecorderServiceV2.java b/java/com/android/dialer/callrecord/impl/CallRecorderServiceV2.java index 0af8c6747..71dc86b25 100644 --- a/java/com/android/dialer/callrecord/impl/CallRecorderServiceV2.java +++ b/java/com/android/dialer/callrecord/impl/CallRecorderServiceV2.java @@ -6,13 +6,18 @@ import static com.android.dialer.callrecord.impl.CallRecorderService.KEY_CALL_RE import static java.lang.Integer.parseInt; import android.Manifest; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Service; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.net.Uri; +import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.provider.MediaStore; @@ -27,6 +32,9 @@ import java.util.Date; public class CallRecorderServiceV2 extends Service { private static final String TAG = "CallRecorderServiceV2"; + // Helpdesk patch (B5): microphone foreground service while recording. + private static final String FGS_CHANNEL_ID = "helpdesk_call_recording"; + private static final int FGS_NOTIF_ID = 0xCA11; private BaseCallRecorder mCallRecorder; @@ -60,6 +68,21 @@ public class CallRecorderServiceV2 extends Service { return mBinder; } + // Helpdesk patch: the recorder is started via Context.startForegroundService() (in addition to the + // existing bind used for the ICallRecorderService IPC) so the while-in-use reason is recorded in + // ServiceRecord.mAllowWiu_noBinding — the field the targetSdk<=34 legacy capability calculation + // actually reads. A bound-only service promoted with startForeground() stores its reason in + // mAllowWiu_byBindings, which that calculation ignores, so the mic-FGS never got + // PROCESS_CAPABILITY_FOREGROUND_MICROPHONE and capture was silenced whenever the InCall UI left + // TOP (screen blanked by the proximity sensor). Going foreground here immediately also satisfies + // the mandatory startForeground() deadline even if the recording start later fails. + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG, "onStartCommand " + intent); + goForeground(); + return START_NOT_STICKY; + } + private static SharedPreferences getPrefs(Context context) { // This replicates PreferenceManager.getDefaultSharedPreferences, except // that we need multi process preferences, as the pref is written in a separate @@ -74,13 +97,12 @@ public class CallRecorderServiceV2 extends Service { } private int getAudioSource() { - String def = getString(R.string.call_recording_audio_source_default); - return parseInt(getPrefs().getString(KEY_CALL_RECORDING_AUDIO_SOURCE, def)); + return 4; // Helpdesk patch: always MediaRecorder.AudioSource.VOICE_CALL (both call legs) } private static final String KEY_CALL_RECORDING_OUTPUT_FORMAT = "call_recording_output_format_v2"; public static boolean isV2Enabled(Context context) { - return getPrefs(context).getBoolean("call_recording_use_v2", false); + return true; // Helpdesk patch: always use callrecV2 (framework VOICE_CALL capture) } private OutputFormat getOutputFormat() { @@ -115,6 +137,7 @@ public class CallRecorderServiceV2 extends Service { if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { Log.e(TAG, "Record audio permission not granted, can't record call"); + leaveForeground(); // Helpdesk patch: don't linger as a started mic FGS with nothing recording return false; } @@ -129,6 +152,7 @@ public class CallRecorderServiceV2 extends Service { if (uri == null) { Log.e(TAG, "failed to get uri from MediaStore"); + leaveForeground(); // Helpdesk patch: don't linger as a started mic FGS with nothing recording mCallRecorder.close(); return false; } @@ -143,6 +167,7 @@ public class CallRecorderServiceV2 extends Service { mCallRecorder = new WavLPCMRecorder(this, audioSource, uri, outputFormat); break; } + goForeground(); // become a microphone FGS BEFORE capturing (mic held regardless of UI state) mCallRecorder.startRecording(); long mediaId = Long.parseLong(uri.getLastPathSegment()); @@ -181,7 +206,46 @@ public class CallRecorderServiceV2 extends Service { } } finally { mCallRecorder = null; + // Helpdesk patch: also clear the STARTED state (see onStartCommand) so the service doesn't + // keep running after recording ends; it stays alive while clients remain bound and is + // destroyed on the last unbind. + leaveForeground(); + } + } + + // Helpdesk patch: undo goForeground() — drop the mic-FGS promotion and the started state. + private void leaveForeground() { + stopForeground(STOP_FOREGROUND_REMOVE); // no-op if not foreground; API 24+ + stopSelf(); + } + + // Helpdesk patch (B5): run as a microphone foreground service while recording. + private void goForeground() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationManager nm = getSystemService(NotificationManager.class); + if (nm != null && nm.getNotificationChannel(FGS_CHANNEL_ID) == null) { + NotificationChannel ch = new NotificationChannel( + FGS_CHANNEL_ID, "Call recording", NotificationManager.IMPORTANCE_LOW); + ch.setShowBadge(false); + nm.createNotificationChannel(ch); + } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + startForeground(FGS_NOTIF_ID, buildNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE); + } else { + startForeground(FGS_NOTIF_ID, buildNotification()); + } + } + + @SuppressWarnings("deprecation") // Notification.Builder(Context) below API 26 (minSdk 24) + private Notification buildNotification() { + Notification.Builder b = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + ? new Notification.Builder(this, FGS_CHANNEL_ID) + : new Notification.Builder(this); + return b.setContentTitle("Recording call") + .setSmallIcon(android.R.drawable.ic_btn_speak_now) + .setOngoing(true) + .build(); } @Override