Dialer: Request permissions and launch activities the modern way

We are supposed to use ActivityResultLaunchers nowadays

Change-Id: I3888e53e3f1d57c1dab8d62989623b6081d75a82
diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java
index 04adc6c..9aa1af6 100644
--- a/java/com/android/dialer/app/calllog/CallLogFragment.java
+++ b/java/com/android/dialer/app/calllog/CallLogFragment.java
@@ -39,6 +39,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -93,8 +95,6 @@
   // No date-based filtering.
   private static final int NO_DATE_LIMIT = 0;
 
-  private static final int PHONE_PERMISSIONS_REQUEST_CODE = 1;
-
   private static final int EVENT_UPDATE_DISPLAY = 1;
 
   private static final long MILLIS_IN_MINUTE = 60 * 1000;
@@ -155,6 +155,15 @@
   protected CallLogModalAlertManager modalAlertManager;
   private ViewGroup modalAlertView;
 
+  private final ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(
+          new ActivityResultContracts.RequestMultiplePermissions(),
+          grantResults -> {
+            if (grantResults.size() >= 1 && grantResults.values().iterator().next()) {
+              // Force a refresh of the data since we were missing the permission before this.
+              refreshDataRequired = true;
+            }
+          });
+
   public CallLogFragment() {
     this(CallLogQueryHandler.CALL_TYPE_ALL, NO_LOG_LIMIT);
   }
@@ -561,25 +570,13 @@
       LogUtil.i(
           "CallLogFragment.onEmptyViewActionButtonClicked",
           "Requesting permissions: " + Arrays.toString(deniedPermissions));
-      requestPermissions(deniedPermissions, PHONE_PERMISSIONS_REQUEST_CODE);
+      permissionLauncher.launch(deniedPermissions);
     } else if (!isCallLogActivity) {
       LogUtil.i("CallLogFragment.onEmptyViewActionButtonClicked", "showing dialpad");
       // Show dialpad if we are not in the call log activity.
       FragmentUtils.getParentUnsafe(this, HostInterface.class).showDialpad();
     }
   }
-  //TODO: BadDaemon: Reimplement this
-/*
-  @Override
-  public void onRequestPermissionsResult(
-      int requestCode, String[] permissions, int[] grantResults) {
-    if (requestCode == PHONE_PERMISSIONS_REQUEST_CODE) {
-      if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
-        // Force a refresh of the data since we were missing the permission before this.
-        refreshDataRequired = true;
-      }
-    }
-  }*/
 
   /** Schedules an update to the relative call times (X mins ago). */
   private void rescheduleDisplayUpdate() {
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index 08c5b30..76622ca 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -72,7 +72,6 @@
 import com.android.dialer.clipboard.ClipboardUtils;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.AsyncTaskExecutors;
-import com.android.dialer.constants.ActivityRequestCodes;
 import com.android.dialer.contactphoto.ContactPhotoManager;
 import com.android.dialer.dialercontact.DialerContact;
 import com.android.dialer.dialercontact.SimDetails;
@@ -898,8 +897,7 @@
       return;
     }
     if (OldCallDetailsActivity.isLaunchIntent(intent)) {
-      ((Activity) context)
-          .startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_CALL_DETAILS);
+      ((Activity) context).startActivity(intent);
     } else {
       if (Intent.ACTION_CALL.equals(intent.getAction())
           && intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, -1)
diff --git a/java/com/android/dialer/callstats/CallStatsFragment.java b/java/com/android/dialer/callstats/CallStatsFragment.java
index 95f7df4..2941b0f 100644
--- a/java/com/android/dialer/callstats/CallStatsFragment.java
+++ b/java/com/android/dialer/callstats/CallStatsFragment.java
@@ -22,7 +22,6 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.os.Bundle;
 import android.os.Handler;
@@ -39,6 +38,8 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -60,8 +61,6 @@
     DoubleDatePickerDialog.OnDateSetListener {
   private static final String TAG = "CallStatsFragment";
 
-  private static final int READ_CALL_LOG_PERMISSION_REQUEST_CODE = 1;
-
   private PhoneAccountHandle mAccountFilter = null;
   private int mCallTypeFilter = -1;
   private long mFilterFrom = -1;
@@ -90,6 +89,16 @@
     }
   };
 
+  private final ActivityResultLauncher<String> permissionLauncher = registerForActivityResult(
+          new ActivityResultContracts.RequestPermission(),
+          granted -> {
+            if (granted) {
+              // Force a refresh of the data since we were missing the permission before this.
+              mRefreshDataRequired = true;
+              requireActivity().invalidateOptionsMenu();
+            }
+          });
+
   @Override
   public void onCreate(Bundle state) {
     super.onCreate(state);
@@ -201,20 +210,7 @@
   @Override
   public void onEmptyViewActionButtonClicked() {
     if (!PermissionsUtil.hasPermission(getActivity(), READ_CALL_LOG)) {
-      requestPermissions(new String[] { READ_CALL_LOG },
-          READ_CALL_LOG_PERMISSION_REQUEST_CODE);
-    }
-  }
-
-  @Override
-  public void onRequestPermissionsResult(int requestCode, String[] permissions,
-      int[] grantResults) {
-    if (requestCode == READ_CALL_LOG_PERMISSION_REQUEST_CODE) {
-      if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
-        // Force a refresh of the data since we were missing the permission before this.
-        mRefreshDataRequired = true;
-        getActivity().invalidateOptionsMenu();
-      }
+      permissionLauncher.launch(READ_CALL_LOG);
     }
   }
 
diff --git a/java/com/android/dialer/constants/ActivityRequestCodes.java b/java/com/android/dialer/constants/ActivityRequestCodes.java
deleted file mode 100644
index 12b7263f..0000000
--- a/java/com/android/dialer/constants/ActivityRequestCodes.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 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.dialer.constants;
-
-/**
- * Class containing {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
- * request codes.
- */
-public final class ActivityRequestCodes {
-
-  private ActivityRequestCodes() {}
-
-  /** Request code for {@link android.speech.RecognizerIntent#ACTION_RECOGNIZE_SPEECH} intent. */
-  public static final int DIALTACTS_VOICE_SEARCH = 1;
-
-  /** Request code for {@link com.android.dialer.calldetails.OldCallDetailsActivity} intent. */
-  public static final int DIALTACTS_CALL_DETAILS = 4;
-
-  /**
-   * Request code for {@link com.android.dialer.speeddial.SpeedDialFragment} contact picker intent.
-   */
-  public static final int SPEED_DIAL_ADD_FAVORITE = 5;
-}
diff --git a/java/com/android/dialer/contactsfragment/ContactsFragment.java b/java/com/android/dialer/contactsfragment/ContactsFragment.java
index 46d4287..6dbbbad 100644
--- a/java/com/android/dialer/contactsfragment/ContactsFragment.java
+++ b/java/com/android/dialer/contactsfragment/ContactsFragment.java
@@ -22,7 +22,6 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -33,6 +32,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
@@ -70,8 +71,6 @@
     int ADD_CONTACT = 1;
   }
 
-  public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1;
-
   private static final String EXTRA_HEADER = "extra_header";
   private static final String EXTRA_HAS_PHONE_NUMBERS = "extra_has_phone_numbers";
 
@@ -87,6 +86,16 @@
         }
       };
 
+  private final ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(
+          new ActivityResultContracts.RequestMultiplePermissions(),
+          grantResults -> {
+            if (grantResults.size() >= 1  && grantResults.values().iterator().next()) {
+              String key = grantResults.keySet().iterator().next();
+              // Force a refresh of the data since we were missing the permission before this.
+              PermissionsUtil.notifyPermissionGranted(getContext(), key);
+            }
+          });
+
   private FastScroller fastScroller;
   private TextView anchoredHeader;
   private RecyclerView recyclerView;
@@ -303,7 +312,7 @@
         LogUtil.i(
             "ContactsFragment.onEmptyViewActionButtonClicked",
             "Requesting permissions: " + Arrays.toString(deniedPermissions));
-        requestPermissions(deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE);
+        permissionLauncher.launch(deniedPermissions);
       }
 
     } else if (emptyContentView.getActionLabel()
@@ -317,17 +326,6 @@
   }
 
   @Override
-  public void onRequestPermissionsResult(
-      int requestCode, String[] permissions, int[] grantResults) {
-    if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) {
-      if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
-        // Force a refresh of the data since we were missing the permission before this.
-        PermissionsUtil.notifyPermissionGranted(getContext(), permissions[0]);
-      }
-    }
-  }
-
-  @Override
   public void onHiddenChanged(boolean hidden) {
     super.onHiddenChanged(hidden);
     OnContactsFragmentHiddenChangedListener listener =
diff --git a/java/com/android/dialer/interactions/PhoneNumberInteraction.java b/java/com/android/dialer/interactions/PhoneNumberInteraction.java
index 98c6864..4644801 100644
--- a/java/com/android/dialer/interactions/PhoneNumberInteraction.java
+++ b/java/com/android/dialer/interactions/PhoneNumberInteraction.java
@@ -42,8 +42,8 @@
 import android.widget.ListAdapter;
 import android.widget.TextView;
 
+import androidx.activity.result.ActivityResultLauncher;
 import androidx.annotation.IntDef;
-import androidx.core.app.ActivityCompat;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
@@ -84,10 +84,6 @@
 public class PhoneNumberInteraction implements Loader.OnLoadCompleteListener<Cursor> {
 
   static final String TAG = PhoneNumberInteraction.class.getSimpleName();
-  /** The identifier for a permissions request if one is generated. */
-  public static final int REQUEST_READ_CONTACTS = 1;
-
-  public static final int REQUEST_CALL_PHONE = 2;
 
   private static final String[] PHONE_NUMBER_PROJECTION =
       new String[] {
@@ -168,7 +164,6 @@
 
     Assert.checkArgument(context instanceof InteractionErrorListener);
     Assert.checkArgument(context instanceof DisambigDialogDismissedListener);
-    Assert.checkArgument(context instanceof ActivityCompat.OnRequestPermissionsResultCallback);
   }
 
   private static void performAction(
@@ -202,13 +197,14 @@
    * @param isVideoCall {@code true} if the call is a video call, {@code false} otherwise.
    */
   public static void startInteractionForPhoneCall(
-      TransactionSafeActivity activity,
-      Uri uri,
-      boolean isVideoCall,
-      CallSpecificAppData callSpecificAppData) {
+          TransactionSafeActivity activity,
+          Uri uri,
+          boolean isVideoCall,
+          CallSpecificAppData callSpecificAppData,
+          ActivityResultLauncher<String[]> permissionLauncher) {
     new PhoneNumberInteraction(
             activity, ContactDisplayUtils.INTERACTION_CALL, isVideoCall, callSpecificAppData)
-        .startInteraction(uri);
+        .startInteraction(uri, permissionLauncher);
   }
 
   private void performAction(String phoneNumber) {
@@ -221,14 +217,13 @@
    *
    * @param uri Contact Uri
    */
-  private void startInteraction(Uri uri) {
+  private void startInteraction(Uri uri, ActivityResultLauncher<String[]> permissionLauncher) {
     // It's possible for a shortcut to have been created, and then permissions revoked. To avoid a
     // crash when the user tries to use such a shortcut, check for this condition and ask the user
     // for the permission.
     if (!PermissionsUtil.hasPhonePermissions(context)) {
       LogUtil.i("PhoneNumberInteraction.startInteraction", "Need phone permission: CALL_PHONE");
-      ActivityCompat.requestPermissions(
-          (Activity) context, new String[] {permission.CALL_PHONE}, REQUEST_CALL_PHONE);
+      permissionLauncher.launch(new String[] {permission.CALL_PHONE});
       return;
     }
 
@@ -239,8 +234,7 @@
       LogUtil.i(
           "PhoneNumberInteraction.startInteraction",
           "Need contact permissions: " + Arrays.toString(deniedContactsPermissions));
-      ActivityCompat.requestPermissions(
-          (Activity) context, deniedContactsPermissions, REQUEST_READ_CONTACTS);
+      permissionLauncher.launch(deniedContactsPermissions);
       return;
     }
 
diff --git a/java/com/android/dialer/main/MainActivityPeer.java b/java/com/android/dialer/main/MainActivityPeer.java
index 31af9ad..fe8d922 100644
--- a/java/com/android/dialer/main/MainActivityPeer.java
+++ b/java/com/android/dialer/main/MainActivityPeer.java
@@ -36,8 +36,6 @@
 
   void onNewIntent(Intent intent);
 
-  void onActivityResult(int requestCode, int resultCode, Intent data);
-
   void onSaveInstanceState(Bundle bundle);
 
   boolean onBackPressed();
diff --git a/java/com/android/dialer/main/impl/DefaultDialerActivity.java b/java/com/android/dialer/main/impl/DefaultDialerActivity.java
index aa7d47c..3a1382c 100644
--- a/java/com/android/dialer/main/impl/DefaultDialerActivity.java
+++ b/java/com/android/dialer/main/impl/DefaultDialerActivity.java
@@ -4,7 +4,6 @@
  */
 package com.android.dialer.main.impl;
 
-import android.app.Activity;
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.Intent;
@@ -12,6 +11,8 @@
 import android.telecom.TelecomManager;
 import android.view.View;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.appcompat.app.AppCompatActivity;
 
 import com.android.dialer.R;
@@ -20,10 +21,15 @@
 public class DefaultDialerActivity extends AppCompatActivity implements
         EmptyContentView.OnEmptyViewActionButtonClickedListener {
 
-    private static final int REQUEST_DEFAULT_DIALER = 1;
-
     private RoleManager mRoleManager;
 
+    private final ActivityResultLauncher<Intent> mDefaultDialerLauncher = registerForActivityResult(
+            new ActivityResultContracts.StartActivityForResult(), result -> {
+                if (result.getResultCode() == RESULT_OK) {
+                    finish();
+                }
+            });
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         setTheme(R.style.MainActivityTheme);
@@ -53,17 +59,6 @@
         Intent roleRequest = mRoleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
         roleRequest.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,
                 getPackageName());
-        startActivityForResult(roleRequest, REQUEST_DEFAULT_DIALER);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-
-        if (requestCode == REQUEST_DEFAULT_DIALER) {
-            if (resultCode == Activity.RESULT_OK) {
-                finish();
-            }
-        }
+        mDefaultDialerLauncher.launch(roleRequest);
     }
 }
diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java
index d8cd679..380995b 100644
--- a/java/com/android/dialer/main/impl/MainActivity.java
+++ b/java/com/android/dialer/main/impl/MainActivity.java
@@ -106,12 +106,6 @@
   }
 
   @Override
-  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-    super.onActivityResult(requestCode, resultCode, data);
-    activePeer.onActivityResult(requestCode, resultCode, data);
-  }
-
-  @Override
   public void onBackPressed() {
     if (activePeer.onBackPressed()) {
       return;
diff --git a/java/com/android/dialer/main/impl/MainSearchController.java b/java/com/android/dialer/main/impl/MainSearchController.java
index fe5cd41..1462368 100644
--- a/java/com/android/dialer/main/impl/MainSearchController.java
+++ b/java/com/android/dialer/main/impl/MainSearchController.java
@@ -28,6 +28,8 @@
 import android.view.animation.Animation.AnimationListener;
 import android.widget.Toast;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.AttrRes;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
@@ -40,7 +42,6 @@
 import com.android.dialer.app.settings.DialerSettingsActivity;
 import com.android.dialer.callintent.CallInitiationType;
 import com.android.dialer.common.LogUtil;
-import com.android.dialer.constants.ActivityRequestCodes;
 import com.android.dialer.dialpadview.DialpadFragment;
 import com.android.dialer.dialpadview.DialpadFragment.DialpadListener;
 import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener;
@@ -104,6 +105,8 @@
   private DialpadFragment dialpadFragment;
   private NewSearchFragment searchFragment;
 
+  private final ActivityResultLauncher<Intent> mVoiceSearchLauncher;
+
   public MainSearchController(
       TransactionSafeActivity activity,
       BottomNavBar bottomNav,
@@ -120,6 +123,11 @@
             .findFragmentByTag(DIALPAD_FRAGMENT_TAG);
     searchFragment = (NewSearchFragment) activity.getSupportFragmentManager()
             .findFragmentByTag(SEARCH_FRAGMENT_TAG);
+
+    mVoiceSearchLauncher = activity.registerForActivityResult(
+            new ActivityResultContracts.StartActivityForResult(), result -> {
+              onVoiceResults(result.getResultCode(), result.getData());
+            });
   }
 
   /** Should be called if we're showing the dialpad because of a new ACTION_DIAL intent. */
@@ -440,7 +448,7 @@
   public void onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback) {
     try {
       Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-      activity.startActivityForResult(voiceIntent, ActivityRequestCodes.DIALTACTS_VOICE_SEARCH);
+      mVoiceSearchLauncher.launch(voiceIntent);
     } catch (ActivityNotFoundException e) {
       Toast.makeText(activity, R.string.voice_search_not_available, Toast.LENGTH_SHORT).show();
     }
diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
index d27c4eb..c2c1b4a 100644
--- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java
+++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
@@ -60,7 +60,6 @@
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DialerExecutorComponent;
 import com.android.dialer.common.concurrent.SupportUiListener;
-import com.android.dialer.constants.ActivityRequestCodes;
 import com.android.dialer.contactsfragment.ContactsFragment;
 import com.android.dialer.contactsfragment.ContactsFragment.Header;
 import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener;
@@ -469,23 +468,6 @@
   }
 
   @Override
-  public void onActivityResult(int requestCode, int resultCode, Intent data) {
-    LogUtil.i(
-        "OldMainActivityPeer.onActivityResult",
-        "requestCode:%d, resultCode:%d",
-        requestCode,
-        resultCode);
-    if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) {
-      searchController.onVoiceResults(resultCode, data);
-    } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_DETAILS) {
-      // ignored
-
-    } else {
-      LogUtil.e("OldMainActivityPeer.onActivityResult", "Unknown request code: " + requestCode);
-    }
-  }
-
-  @Override
   public boolean onBackPressed() {
     LogUtil.enterBlock("OldMainActivityPeer.onBackPressed");
     if (searchController.onBackPressed()) {
diff --git a/java/com/android/dialer/postcall/PostCallActivity.java b/java/com/android/dialer/postcall/PostCallActivity.java
index 000ee54..6988ad0 100644
--- a/java/com/android/dialer/postcall/PostCallActivity.java
+++ b/java/com/android/dialer/postcall/PostCallActivity.java
@@ -20,12 +20,13 @@
 import android.Manifest.permission;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.telephony.SmsManager;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
@@ -43,7 +44,13 @@
   public static final String KEY_PHONE_NUMBER = "phone_number";
   public static final String KEY_MESSAGE = "message";
   public static final String KEY_RCS_POST_CALL = "rcs_post_call";
-  private static final int REQUEST_CODE_SEND_SMS = 1;
+
+  private final ActivityResultLauncher<String> smsPermissionLauncher =
+          registerForActivityResult(new ActivityResultContracts.RequestPermission(),
+                  grantResult -> {
+            PermissionsUtil.permissionRequested(this, permission.SEND_SMS);
+            onMessageFragmentSendMessage(getIntent().getStringExtra(KEY_MESSAGE));
+          });
 
   private boolean useRcs;
 
@@ -105,7 +112,7 @@
     } else if (PermissionsUtil.isFirstRequest(this, permission.SEND_SMS)
         || shouldShowRequestPermissionRationale(permission.SEND_SMS)) {
       LogUtil.i("PostCallActivity.sendMessage", "Requesting SMS_SEND permission.");
-      requestPermissions(new String[] {permission.SEND_SMS}, REQUEST_CODE_SEND_SMS);
+      smsPermissionLauncher.launch(permission.SEND_SMS);
     } else {
       LogUtil.i(
           "PostCallActivity.sendMessage", "Permission permanently denied, sending to settings.");
@@ -119,17 +126,4 @@
 
   @Override
   public void onMessageFragmentAfterTextChange(String message) {}
-
-  @Override
-  public void onRequestPermissionsResult(
-      int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-    if (permissions.length > 0 && permissions[0].equals(permission.SEND_SMS)) {
-      PermissionsUtil.permissionRequested(this, permissions[0]);
-    }
-    if (requestCode == REQUEST_CODE_SEND_SMS
-        && grantResults.length > 0
-        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-      onMessageFragmentSendMessage(getIntent().getStringExtra(KEY_MESSAGE));
-    }
-  }
 }
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index fca096b..8edf4e3 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -19,7 +19,6 @@
 
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.os.Bundle;
 import android.telephony.PhoneNumberUtils;
@@ -33,7 +32,8 @@
 import android.widget.FrameLayout;
 import android.widget.FrameLayout.LayoutParams;
 
-import androidx.annotation.NonNull;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 import androidx.loader.app.LoaderManager;
@@ -85,9 +85,6 @@
 
   private static final String KEY_LOCATION_PROMPT_DISMISSED = "search_location_prompt_dismissed";
 
-  private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1;
-  private static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
-
   private static final int CONTACTS_LOADER_ID = 0;
   private static final int NEARBY_PLACES_LOADER_ID = 1;
 
@@ -133,6 +130,26 @@
 
   private Runnable updatePositionRunnable;
 
+  private final ActivityResultLauncher<String[]> contactPermissionLauncher =
+          registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
+                  grantResults -> {
+            if (grantResults.size() >= 1 && grantResults.values().iterator().next()) {
+              // Force a refresh of the data since we were missing the permission before this.
+              emptyContentView.setVisibility(View.GONE);
+              initLoaders();
+            }
+          });
+
+  private final ActivityResultLauncher<String[]> locationPermissionLauncher =
+          registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
+                  grantResults -> {
+            if (grantResults.size() >= 1 && grantResults.values().iterator().next()) {
+              // Force a refresh of the data since we were missing the permission before this.
+              loadNearbyPlacesCursor();
+              adapter.hideLocationPermissionRequest();
+            }
+          });
+
   public static NewSearchFragment newInstance() {
     return new NewSearchFragment();
   }
@@ -330,24 +347,6 @@
   }
 
   @Override
-  public void onRequestPermissionsResult(
-          int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-    if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) {
-      if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
-        // Force a refresh of the data since we were missing the permission before this.
-        emptyContentView.setVisibility(View.GONE);
-        initLoaders();
-      }
-    } else if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
-      if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
-        // Force a refresh of the data since we were missing the permission before this.
-        loadNearbyPlacesCursor();
-        adapter.hideLocationPermissionRequest();
-      }
-    }
-  }
-
-  @Override
   public void onEmptyViewActionButtonClicked() {
     String[] deniedPermissions =
         PermissionsUtil.getPermissionsCurrentlyDenied(
@@ -357,7 +356,7 @@
           "NewSearchFragment.onEmptyViewActionButtonClicked",
           "Requesting permissions: " + Arrays.toString(deniedPermissions));
       FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class).requestingPermission();
-      requestPermissions(deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE);
+      contactPermissionLauncher.launch(deniedPermissions);
     }
   }
 
@@ -420,7 +419,7 @@
         PermissionsUtil.getPermissionsCurrentlyDenied(
             getContext(), PermissionsUtil.allLocationGroupPermissionsUsedInDialer);
     FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class).requestingPermission();
-    requestPermissions(deniedPermissions, LOCATION_PERMISSION_REQUEST_CODE);
+    locationPermissionLauncher.launch(deniedPermissions);
   }
 
   public void dismissLocationPermission() {
diff --git a/java/com/android/dialer/shortcuts/CallContactActivity.java b/java/com/android/dialer/shortcuts/CallContactActivity.java
index 87fe5e6..585ab0c 100644
--- a/java/com/android/dialer/shortcuts/CallContactActivity.java
+++ b/java/com/android/dialer/shortcuts/CallContactActivity.java
@@ -17,14 +17,15 @@
 
 package com.android.dialer.shortcuts;
 
-import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.widget.Toast;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
-import androidx.core.app.ActivityCompat;
 
+import android.widget.Toast;
 import com.android.dialer.callintent.CallInitiationType;
 import com.android.dialer.callintent.CallSpecificAppData;
 import com.android.dialer.common.LogUtil;
@@ -37,11 +38,20 @@
  */
 public class CallContactActivity extends TransactionSafeActivity
     implements PhoneNumberInteraction.DisambigDialogDismissedListener,
-        PhoneNumberInteraction.InteractionErrorListener,
-        ActivityCompat.OnRequestPermissionsResultCallback {
+        PhoneNumberInteraction.InteractionErrorListener {
 
   private static final String CONTACT_URI_KEY = "uri_key";
 
+  private final ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(
+          new ActivityResultContracts.RequestMultiplePermissions(), grantResults -> {
+            if (grantResults.values().iterator().next()) {
+              makeCall();
+            } else {
+              Toast.makeText(this, R.string.dialer_shortcut_no_permissions, Toast.LENGTH_SHORT).show();
+              finish();
+            }
+          });
+
   private Uri contactUri;
 
   @Override
@@ -73,7 +83,7 @@
             .setCallInitiationType(CallInitiationType.Type.LAUNCHER_SHORTCUT)
             .build();
     PhoneNumberInteraction.startInteractionForPhoneCall(
-        this, contactUri, false /* isVideoCall */, callSpecificAppData);
+        this, contactUri, false /* isVideoCall */, callSpecificAppData, permissionLauncher);
   }
 
   @Override
@@ -122,26 +132,4 @@
     }
     contactUri = savedInstanceState.getParcelable(CONTACT_URI_KEY, Uri.class);
   }
-
-  @Override
-  public void onRequestPermissionsResult(
-      int requestCode, String[] permissions, int[] grantResults) {
-    switch (requestCode) {
-      case PhoneNumberInteraction.REQUEST_READ_CONTACTS:
-      case PhoneNumberInteraction.REQUEST_CALL_PHONE:
-        {
-          // If request is cancelled, the result arrays are empty.
-          if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-            makeCall();
-          } else {
-            Toast.makeText(this, R.string.dialer_shortcut_no_permissions, Toast.LENGTH_SHORT)
-                .show();
-            finish();
-          }
-          break;
-        }
-      default:
-        throw new IllegalStateException("Unsupported request code: " + requestCode);
-    }
-  }
 }
diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java
index 99ae3a4..462f817 100644
--- a/java/com/android/dialer/speeddial/SpeedDialFragment.java
+++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java
@@ -17,11 +17,12 @@
 
 package com.android.dialer.speeddial;
 
+import static android.app.Activity.RESULT_OK;
+
 import android.Manifest;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Bundle;
@@ -32,9 +33,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.annotation.NonNull;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
@@ -51,7 +52,6 @@
 import com.android.dialer.common.concurrent.DialerExecutorComponent;
 import com.android.dialer.common.concurrent.SupportUiListener;
 import com.android.dialer.common.concurrent.ThreadUtil;
-import com.android.dialer.constants.ActivityRequestCodes;
 import com.android.dialer.historyitemactions.DividerModule;
 import com.android.dialer.historyitemactions.HistoryItemActionBottomSheet;
 import com.android.dialer.historyitemactions.HistoryItemActionModule;
@@ -91,8 +91,6 @@
  */
 public class SpeedDialFragment extends Fragment {
 
-  private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1;
-
   /**
    * Listen to broadcast events about permissions in order to be notified if the READ_CONTACTS
    * permission is granted via the UI in another fragment.
@@ -130,6 +128,33 @@
    */
   private boolean updateSpeedDialItemsOnResume = true;
 
+  private final ActivityResultLauncher<String[]> contactPermissionLauncher =
+          registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
+                  grantResults -> {
+                    if (grantResults.size() >= 1 && grantResults.values().iterator().next()) {
+                      PermissionsUtil.notifyPermissionGranted(getContext(),
+                              Manifest.permission.READ_CONTACTS);
+                      loadContacts();
+                    }
+                  });
+
+  private final ActivityResultLauncher<Intent> addFavoriteLauncher = registerForActivityResult(
+          new ActivityResultContracts.StartActivityForResult(), result -> {
+            Intent data = result.getData();
+            if (result.getResultCode() == RESULT_OK && data != null && data.getData() != null) {
+              updateSpeedDialItemsOnResume = false;
+              speedDialLoaderListener.listen(
+                      getContext(),
+                      UiItemLoaderComponent.get(requireContext())
+                              .speedDialUiItemMutator()
+                              .starContact(data.getData()),
+                      this::onSpeedDialUiItemListLoaded,
+                      throwable -> {
+                        throw new RuntimeException(throwable);
+                      });
+            }
+          });
+
   public static SpeedDialFragment newInstance() {
     return new SpeedDialFragment();
   }
@@ -223,17 +248,6 @@
         ShortcutRefresher.speedDialUiItemsToContactEntries(adapter.getSpeedDialUiItems()));
   }
 
-  @Override
-  public void onRequestPermissionsResult(
-          int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-    if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE
-        && grantResults.length > 0
-        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-      PermissionsUtil.notifyPermissionGranted(getContext(), Manifest.permission.READ_CONTACTS);
-      loadContacts();
-    }
-  }
-
   private void loadContacts() {
     if (!updateSpeedDialItemsOnResume) {
       updateSpeedDialItemsOnResume = true;
@@ -253,24 +267,6 @@
         });
   }
 
-  @Override
-  public void onActivityResult(int requestCode, int resultCode, Intent data) {
-    if (requestCode == ActivityRequestCodes.SPEED_DIAL_ADD_FAVORITE) {
-      if (resultCode == AppCompatActivity.RESULT_OK && data.getData() != null) {
-        updateSpeedDialItemsOnResume = false;
-        speedDialLoaderListener.listen(
-            getContext(),
-            UiItemLoaderComponent.get(getContext())
-                .speedDialUiItemMutator()
-                .starContact(data.getData()),
-            this::onSpeedDialUiItemListLoaded,
-            throwable -> {
-              throw new RuntimeException(throwable);
-            });
-      }
-    }
-  }
-
   private void onSpeedDialUiItemListLoaded(ImmutableList<SpeedDialUiItem> speedDialUiItems) {
     LogUtil.enterBlock("SpeedDialFragment.onSpeedDialUiItemListLoaded");
     // TODO(calderwoodra): Use DiffUtil to properly update and animate the change
@@ -295,7 +291,8 @@
     emptyContentView.setActionLabel(R.string.speed_dial_turn_on_contacts_permission);
     emptyContentView.setDescription(R.string.speed_dial_contacts_permission_description);
     emptyContentView.setActionClickedListener(
-        new SpeedDialContactPermissionEmptyViewListener(getContext(), this));
+        new SpeedDialContactPermissionEmptyViewListener(getContext(), this,
+                contactPermissionLauncher));
     return true;
   }
 
@@ -308,7 +305,8 @@
     emptyContentView.setVisibility(View.VISIBLE);
     emptyContentView.setActionLabel(R.string.speed_dial_no_contacts_action_text);
     emptyContentView.setDescription(R.string.speed_dial_no_contacts_description);
-    emptyContentView.setActionClickedListener(new SpeedDialNoContactsEmptyViewListener(this));
+    emptyContentView.setActionClickedListener(
+            new SpeedDialNoContactsEmptyViewListener(addFavoriteLauncher));
   }
 
   @Override
@@ -336,7 +334,7 @@
     @Override
     public void onAddFavoriteClicked() {
       Intent intent = new Intent(Intent.ACTION_PICK, Phone.CONTENT_URI);
-      startActivityForResult(intent, ActivityRequestCodes.SPEED_DIAL_ADD_FAVORITE);
+      addFavoriteLauncher.launch(intent);
     }
   }
 
@@ -584,9 +582,14 @@
     private final Context context;
     private final Fragment fragment;
 
-    private SpeedDialContactPermissionEmptyViewListener(Context context, Fragment fragment) {
+    private final ActivityResultLauncher<String[]> contactPermissionLauncher;
+
+    private SpeedDialContactPermissionEmptyViewListener(Context context, Fragment fragment,
+                                                        ActivityResultLauncher<String[]>
+                                                                contactPermissionLauncher) {
       this.context = context;
       this.fragment = fragment;
+      this.contactPermissionLauncher = contactPermissionLauncher;
     }
 
     @Override
@@ -598,23 +601,23 @@
       LogUtil.i(
           "OldSpeedDialFragment.onEmptyViewActionButtonClicked",
           "Requesting permissions: " + Arrays.toString(deniedPermissions));
-      fragment.requestPermissions(deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE);
+      contactPermissionLauncher.launch(deniedPermissions);
     }
   }
 
   private static final class SpeedDialNoContactsEmptyViewListener
       implements OnEmptyViewActionButtonClickedListener {
 
-    private final Fragment fragment;
+    private final ActivityResultLauncher<Intent> addFavoriteLauncher;
 
-    SpeedDialNoContactsEmptyViewListener(Fragment fragment) {
-      this.fragment = fragment;
+    SpeedDialNoContactsEmptyViewListener(ActivityResultLauncher<Intent> addFavoriteLauncher) {
+      this.addFavoriteLauncher = addFavoriteLauncher;
     }
 
     @Override
     public void onEmptyViewActionButtonClicked() {
       Intent intent = new Intent(Intent.ACTION_PICK, Phone.CONTENT_URI);
-      fragment.startActivityForResult(intent, ActivityRequestCodes.SPEED_DIAL_ADD_FAVORITE);
+      addFavoriteLauncher.launch(intent);
     }
   }
 
diff --git a/java/com/android/dialer/voicemail/settings/CurrentVoicemailGreetingActivity.java b/java/com/android/dialer/voicemail/settings/CurrentVoicemailGreetingActivity.java
index 457e5a4..d112f63 100644
--- a/java/com/android/dialer/voicemail/settings/CurrentVoicemailGreetingActivity.java
+++ b/java/com/android/dialer/voicemail/settings/CurrentVoicemailGreetingActivity.java
@@ -20,16 +20,16 @@
 import android.Manifest;
 import android.app.Activity;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.media.MediaPlayer;
 import android.os.Bundle;
-import androidx.core.app.ActivityCompat;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.ImageButton;
 import android.widget.TextView;
 
-import androidx.annotation.NonNull;
 import androidx.appcompat.app.AppCompatActivity;
 
 import com.android.dialer.R;
@@ -43,9 +43,14 @@
 public class CurrentVoicemailGreetingActivity extends AppCompatActivity {
   public static final String VOICEMAIL_GREETING_FILEPATH_KEY = "canonVoicemailGreetingFilePathKey";
 
-  private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
-
-  private boolean permissionToRecordAccepted = false;
+  private final ActivityResultLauncher<String> audioPermissionLauncher = registerForActivityResult(
+          new ActivityResultContracts.RequestPermission(), granted -> {
+            if (!granted) {
+              LogUtil.w(
+                      "CurrentVoicemailGreetingActivity.onRequestPermissionsResult",
+                      "permissionToRecordAccepted = false.");
+            }
+          });
 
   private ImageButton changeGreetingButton;
   private ImageButton playButton;
@@ -96,8 +101,7 @@
 
   @Override
   public void onStart() {
-    ActivityCompat.requestPermissions(
-        this, new String[] {Manifest.permission.RECORD_AUDIO}, REQUEST_RECORD_AUDIO_PERMISSION);
+    audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO);
 
     if (isGreetingRecorded()) {
       mediaPlayer = new MediaPlayer();
@@ -122,22 +126,6 @@
     super.onPause();
   }
 
-  @Override
-  public void onRequestPermissionsResult(
-          int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-
-    if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
-      permissionToRecordAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
-    }
-    if (!permissionToRecordAccepted) {
-      LogUtil.w(
-          "CurrentVoicemailGreetingActivity.onRequestPermissionsResult",
-          "permissionToRecordAccepted = false.");
-      // TODO(sabowitz): Implement error dialog logic in a child CL.
-    }
-  }
-
   private boolean isGreetingRecorded() {
     Intent intent = getIntent();
     if (intent.hasExtra(VOICEMAIL_GREETING_FILEPATH_KEY)) {
diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java
index 9ffb934..4e1cabe 100644
--- a/java/com/android/incallui/incall/impl/InCallFragment.java
+++ b/java/com/android/incallui/incall/impl/InCallFragment.java
@@ -40,6 +40,8 @@
 import android.widget.ImageView;
 import android.widget.Toast;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
@@ -100,7 +102,14 @@
   private int phoneType;
   private boolean stateRestored;
 
-  private static final int REQUEST_CODE_CALL_RECORD_PERMISSION = 1000;
+  private final ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(
+          new ActivityResultContracts.RequestMultiplePermissions(),
+          grantResults -> {
+            boolean allGranted = grantResults.values().stream().allMatch(x -> x);
+            if (allGranted) {
+              inCallButtonUiDelegate.callRecordClicked(true);
+            }
+          });
 
   // Add animation to educate users. If a call has enriched calling attachments then we'll
   // initially show the attachment page. After a delay seconds we'll animate to the button grid.
@@ -469,23 +478,7 @@
 
   @Override
   public void requestCallRecordingPermissions(String[] permissions) {
-    requestPermissions(permissions, REQUEST_CODE_CALL_RECORD_PERMISSION);
-  }
-
-  @Override
-  public void onRequestPermissionsResult(int requestCode,
-      @NonNull String[] permissions, @NonNull int[] grantResults) {
-    if (requestCode == REQUEST_CODE_CALL_RECORD_PERMISSION) {
-      boolean allGranted = grantResults.length > 0;
-      for (int i = 0; i < grantResults.length; i++) {
-        allGranted &= grantResults[i] == PackageManager.PERMISSION_GRANTED;
-      }
-      if (allGranted) {
-        inCallButtonUiDelegate.callRecordClicked(true);
-      }
-    } else {
-      super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-    }
+    permissionLauncher.launch(permissions);
   }
 
   @Override
diff --git a/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java b/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java
index 0b12594..dd42c35 100644
--- a/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java
+++ b/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java
@@ -19,7 +19,6 @@
 
 import android.Manifest.permission;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.graphics.drawable.Animatable;
 import android.os.Bundle;
@@ -41,6 +40,8 @@
 import android.widget.ImageButton;
 import android.widget.TextView;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -92,7 +93,6 @@
 
   private static final String ARG_CALL_ID = "call_id";
 
-  private static final int CAMERA_PERMISSION_REQUEST_CODE = 1;
   private static final long CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS = 2000L;
   private static final long VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS = 2000L;
 
@@ -138,6 +138,19 @@
         }
       };
 
+  private final ActivityResultLauncher<String> cameraPermissionLauncher = registerForActivityResult(
+          new ActivityResultContracts.RequestPermission(),
+          grantResults -> {
+            if (grantResults) {
+              LogUtil.i("SurfaceViewVideoCallFragment.onRequestPermissionsResult",
+                      "Camera permission granted.");
+              videoCallScreenDelegate.onCameraPermissionGranted();
+            } else {
+              LogUtil.i("SurfaceViewVideoCallFragment.onRequestPermissionsResult",
+                      "Camera permission denied.");
+            }
+          });
+
   public static SurfaceViewVideoCallFragment newInstance(String callId) {
     Bundle bundle = new Bundle();
     bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId));
@@ -160,23 +173,6 @@
     }
   }
 
-  @Override
-  public void onRequestPermissionsResult(
-          int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-    if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
-      if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-        LogUtil.i(
-            "SurfaceViewVideoCallFragment.onRequestPermissionsResult",
-            "Camera permission granted.");
-        videoCallScreenDelegate.onCameraPermissionGranted();
-      } else {
-        LogUtil.i(
-            "SurfaceViewVideoCallFragment.onRequestPermissionsResult", "Camera permission denied.");
-      }
-    }
-    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-  }
-
   @Nullable
   @Override
   public View onCreateView(
@@ -1023,7 +1019,8 @@
     if (!VideoUtils.hasCameraPermissionAndShownPrivacyToast(getContext())) {
       videoCallScreenDelegate.onCameraPermissionDialogShown();
       if (!VideoUtils.hasCameraPermission(getContext())) {
-        requestPermissions(new String[] {permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
+        cameraPermissionLauncher.launch(permission.CAMERA);
+
       } else {
         PermissionsUtil.showCameraPermissionToast(getContext());
         videoCallScreenDelegate.onCameraPermissionGranted();
diff --git a/java/com/android/incallui/video/impl/VideoCallFragment.java b/java/com/android/incallui/video/impl/VideoCallFragment.java
index 1d8037f..cea7d33 100644
--- a/java/com/android/incallui/video/impl/VideoCallFragment.java
+++ b/java/com/android/incallui/video/impl/VideoCallFragment.java
@@ -19,7 +19,6 @@
 
 import android.Manifest.permission;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
@@ -53,6 +52,8 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -186,6 +187,19 @@
         }
       };
 
+  private final ActivityResultLauncher<String> cameraPermissionLauncher = registerForActivityResult(
+          new ActivityResultContracts.RequestPermission(),
+          grantResult -> {
+            if (grantResult) {
+              LogUtil.i("VideoCallFragment.onRequestPermissionsResult",
+                      "Camera permission granted.");
+              videoCallScreenDelegate.onCameraPermissionGranted();
+            } else {
+              LogUtil.i("VideoCallFragment.onRequestPermissionsResult",
+                      "Camera permission denied.");
+            }
+          });
+
   public static VideoCallFragment newInstance(String callId) {
     Bundle bundle = new Bundle();
     bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId));
@@ -208,20 +222,6 @@
     }
   }
 
-  @Override
-  public void onRequestPermissionsResult(
-          int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-    if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
-      if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-        LogUtil.i("VideoCallFragment.onRequestPermissionsResult", "Camera permission granted.");
-        videoCallScreenDelegate.onCameraPermissionGranted();
-      } else {
-        LogUtil.i("VideoCallFragment.onRequestPermissionsResult", "Camera permission denied.");
-      }
-    }
-    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-  }
-
   @Nullable
   @Override
   public View onCreateView(
@@ -1275,7 +1275,7 @@
     if (!VideoUtils.hasCameraPermissionAndShownPrivacyToast(getContext())) {
       videoCallScreenDelegate.onCameraPermissionDialogShown();
       if (!VideoUtils.hasCameraPermission(getContext())) {
-        requestPermissions(new String[] {permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
+        cameraPermissionLauncher.launch(permission.CAMERA);
       } else {
         PermissionsUtil.showCameraPermissionToast(getContext());
         videoCallScreenDelegate.onCameraPermissionGranted();