[DO NOT MERGE] Fast Pair Half Sheet UI implementation

Refactor doc: go/fp-halfsheet-aosp

Test: Built band verified flow manually
Change-Id: I2496d111410e9bc715547b8fdbe708730304df8c
diff --git a/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml b/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml
index cc514e5..7d61d1c 100644
--- a/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml
+++ b/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml
@@ -17,7 +17,8 @@
     android:width="24dp"
     android:height="24dp"
     android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
+    android:viewportHeight="24.0"
+    android:tint="@color/fast_pair_half_sheet_subtitle_color">
     <path
         android:fillColor="@color/fast_pair_half_sheet_subtitle_color"
         android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
diff --git a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
index 24fcd83..7fbe229 100644
--- a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
+++ b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
@@ -73,50 +73,67 @@
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="horizontal"
         app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent">
+
       <ImageView
           android:id="@+id/info_icon"
+          android:layout_width="24dp"
+          android:layout_height="24dp"
+          app:srcCompat="@drawable/fast_pair_ic_info"
           android:layout_centerInParent="true"
           android:contentDescription="@null"
           android:layout_marginEnd="10dp"
           android:layout_toStartOf="@id/connect_btn"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"/>
-      <Button
+          android:visibility="invisible" />
+
+      <com.google.android.material.button.MaterialButton
           android:id="@+id/connect_btn"
-          android:text="@string/common_connect"
-          android:layout_height="wrap_content"
           android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+          android:layout_height="wrap_content"
+          android:text="@string/paring_action_connect"
           android:layout_centerInParent="true"
-          android:background="@color/fast_pair_half_sheet_button_color"
           style="@style/HalfSheetButton" />
+
     </RelativeLayout>
 
-    <Button
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settings_btn"
+        android:text="@string/paring_action_settings"
+        android:layout_height="wrap_content"
+        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+        app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        android:visibility="invisible"
+        style="@style/HalfSheetButton" />
+
+    <com.google.android.material.button.MaterialButton
         android:id="@+id/cancel_btn"
-        android:text="@string/common_done"
-        android:visibility="gone"
+        android:text="@string/paring_action_done"
+        android:visibility="invisible"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         android:gravity="start|center_vertical"
         android:layout_marginTop="6dp"
-        android:layout_marginBottom="16dp"
         style="@style/HalfSheetButtonBorderless"/>
 
-    <Button
+    <com.google.android.material.button.MaterialButton
         android:id="@+id/setup_btn"
+        android:text="@string/paring_action_launch"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         android:layout_marginTop="6dp"
         android:layout_marginBottom="16dp"
         android:background="@color/fast_pair_half_sheet_button_color"
-        android:visibility="gone"
+        android:visibility="invisible"
         android:layout_height="@dimen/fast_pair_half_sheet_bottom_button_height"
         android:layout_width="wrap_content"
         style="@style/HalfSheetButton" />
+
   </androidx.constraintlayout.widget.ConstraintLayout>
 
 </LinearLayout>
diff --git a/nearby/halfsheet/res/values/strings.xml b/nearby/halfsheet/res/values/strings.xml
index 12e3c23..01a82e4 100644
--- a/nearby/halfsheet/res/values/strings.xml
+++ b/nearby/halfsheet/res/values/strings.xml
@@ -16,9 +16,57 @@
 
 <resources>
 
-    <string name="common_done" description="After pairing process finish button text to dismiss halfsheet">Done</string>
-    <string name="common_save">Save</string>
-    <string name="common_connect" description="Button text to start connecting process">Connect</string>
-    <string name="fast_pair_app_launch_button" description="String on app launch half sheet button.">Set up</string>
+    <!--
+      ============================================================
+      PAIRING FRAGMENT
+      ============================================================
+    -->
 
+    <!--
+      A button shown to remind user setup is in progress. [CHAR LIMIT=30]
+    -->
+    <string name="fast_pair_setup_in_progress">Starting Setup&#x2026;</string>
+    <!--
+      Title text shown to remind user to setup a device through companion app. [CHAR LIMIT=40]
+    -->
+    <string name="fast_pair_title_setup">Set up device</string>
+    <!--
+      Title after we successfully pair with the audio device
+      [CHAR LIMIT=30]
+    -->
+    <string name="fast_pair_device_ready">Device connected</string>
+    <!-- Title text shown when peripheral device fail to connect to phone. [CHAR_LIMIT=30] -->
+    <string name="fast_pair_title_fail">Couldn\'t connect</string>
+
+    <!--
+      ============================================================
+      MISCELLANEOUS
+      ============================================================
+    -->
+
+    <!--
+      A button shown after paring process to dismiss the current activity.
+      [CHAR LIMIT=30]
+    -->
+    <string name="paring_action_done">Done</string>
+    <!--
+      A button shown for retroactive paring.
+      [CHAR LIMIT=30]
+     -->
+    <string name="paring_action_save">Save</string>
+    <!--
+      A button to start connecting process.
+      [CHAR LIMIT=30]
+     -->
+    <string name="paring_action_connect">Connect</string>
+    <!--
+      A button to launch a companion app.
+      [CHAR LIMIT=30]
+    -->
+    <string name="paring_action_launch">Set up</string>
+    <!--
+      A button to launch a bluetooth Settings page.
+      [CHAR LIMIT=20]
+    -->
+    <string name="paring_action_settings">Settings</string>
 </resources>
\ No newline at end of file
diff --git a/nearby/halfsheet/res/values/styles.xml b/nearby/halfsheet/res/values/styles.xml
index b48da70..917bb63 100644
--- a/nearby/halfsheet/res/values/styles.xml
+++ b/nearby/halfsheet/res/values/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-  <style name="HalfSheetStyle" parent="Theme.MaterialComponents.DayNight.NoActionBar">
+  <style name="HalfSheetStyle" parent="Theme.Material3.DayNight.NoActionBar">
     <item name="android:windowFrame">@null</item>
     <item name="android:windowBackground">@android:color/transparent</item>
     <item name="android:windowEnterAnimation">@anim/fast_pair_half_sheet_slide_in</item>
@@ -14,7 +14,7 @@
     <item name="android:windowTranslucentNavigation">true</item>
   </style>
 
-  <style name="HalfSheetButton" parent="@style/Widget.MaterialComponents.Button.TextButton">
+  <style name="HalfSheetButton" parent="@style/Widget.Material3.Button.TonalButton">
     <item name="android:textColor">@color/fast_pair_half_sheet_button_accent_text</item>
     <item name="android:backgroundTint">@color/fast_pair_half_sheet_button_color</item>
     <item name="android:textSize">@dimen/fast_pair_notification_text_size</item>
@@ -23,8 +23,7 @@
     <item name="android:textAllCaps">false</item>
   </style>
 
-  <style name="HalfSheetButtonBorderless"
-      parent="@style/Widget.MaterialComponents.Button.OutlinedButton">
+  <style name="HalfSheetButtonBorderless" parent="@style/Widget.Material3.Button.OutlinedButton">
     <item name="android:textColor">@color/fast_pair_half_sheet_button_text</item>
     <item name="android:strokeColor">@color/fast_pair_half_sheet_button_color</item>
     <item name="android:textAllCaps">false</item>
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
index c495c35..9507b9b 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
@@ -19,22 +19,18 @@
 import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE;
 import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
 import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
-import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET;
 import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL;
 import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
 import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
 import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
 
-import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 import androidx.fragment.app.FragmentActivity;
 
 import com.android.nearby.halfsheet.fragment.DevicePairingFragment;
@@ -48,14 +44,12 @@
 import service.proto.Cache;
 
 /**
- * Half sheet activity to show pairing ux.
+ * A class show Fast Pair related information in Half sheet format.
  */
 public class HalfSheetActivity extends FragmentActivity {
 
-    public static final String EXTRA_HALF_SHEET_PENDING_INTENT_CALL_BACK =
-            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PENDING_INTENT_CALL_BACK";
-    public static final String EXTRA_HALF_SHEET_PACKAGE_NAME =
-            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PACKAGE_NAME";
+    public static final String TAG = "HalfSheetActivity";
+
     public static final String EXTRA_HALF_SHEET_CONTENT =
             "com.android.nearby.halfsheet.HALF_SHEET_CONTENT";
     public static final String EXTRA_TITLE =
@@ -72,38 +66,11 @@
             "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PAIRING_RESURFACE";
     public static final String ACTION_HALF_SHEET_FOREGROUND_STATE =
             "com.android.nearby.halfsheet.ACTION_HALF_SHEET_FOREGROUND_STATE";
-    public static final String ACTION_HALF_SHEET_BAN_ALL_ITEM =
-            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_BAN_ALL_ITEM";
-    public static final String ACTION_HALF_SHEET_APP_LAUNCH_CLICKED =
-            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_APP_LAUNCH_CLICKED";
-    public static final String ACTION_HALF_SHEET_WEAR_OS_CLICKED =
-            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_WEAR_OS_CLICKED";
-    public static final String ACTION_HALF_SHEET_USER_COMPLETE_CONFIRMATION =
-            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_USER_COMPLETE_CONFIRMATION";
-    public static final String ACTION_FAST_PAIR_HANDLE_CHIP_DEVICE =
-            "com.android.nearby.halfsheet.ACTION_FAST_PAIR_HANDLE_CHIP_DEVICE";
-    // Intent extra contains another intent that will trigger DiscoveryChimeraService to upload
-    // device
-    // information to the cloud.
-    public static final String EXTRA_HALF_SHEET_CLOUD_SYNC_INTENT =
-            "com.android.nearby.halfsheet.HALF_SHEET_CLOUD_SYNC_INTENT";
     // Intent extra contains the user gmail name eg. testaccount@gmail.com.
     public static final String EXTRA_HALF_SHEET_ACCOUNT_NAME =
             "com.android.nearby.halfsheet.HALF_SHEET_ACCOUNT_NAME";
     public static final String EXTRA_HALF_SHEET_FOREGROUND =
             "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_FOREGROUND";
-    public static final String EXTRA_USER_CONSENT_SYNC_CONTACTS =
-            "com.android.nearby.halfsheet.EXTRA_USER_CONSENT_SYNC_CONTACTS";
-    public static final String EXTRA_USER_CONSENT_SYNC_SMS =
-            "com.android.nearby.halfsheet.EXTRA_USER_CONSENT_SYNC_SMS";
-    public static final String EXTRA_USER_CONFIRM_PASSKEY =
-            "com.android.nearby.halfsheet.EXTRA_USER_CONFIRM_PASSKEY";
-    public static final String CLASS_NAME =
-            "com.android.nearby.halfsheet.HalfSheetActivity";
-    public static final String ACTION_HALF_SHEET_STATUS_CHANGE =
-            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_STATUS_CHANGE";
-    public static final String FINISHED_STATE = "FINISHED_STATE";
-    public static final String EXTRA_CLASSIC_MAC_ADDRESS = "EXTRA_CLASSIC_MAC_ADDRESS";
     public static final String ARG_FRAGMENT_STATE = "ARG_FRAGMENT_STATE";
     @Nullable
     private HalfSheetModuleFragment mHalfSheetModuleFragment;
@@ -128,7 +95,7 @@
                 mHalfSheetModuleFragment = DevicePairingFragment.newInstance(getIntent(),
                         savedInstanceState);
                 if (mHalfSheetModuleFragment == null) {
-                    Log.d("HalfSheetActivity", "device pairing fragment has error.");
+                    Log.d(TAG, "device pairing fragment has error.");
                     finish();
                     return;
                 }
@@ -136,13 +103,13 @@
             case APP_LAUNCH_FRAGMENT_TYPE:
                 // currentFragment = AppLaunchFragment.newInstance(getIntent());
                 if (mHalfSheetModuleFragment == null) {
-                    Log.v("HalfSheetActivity", "app launch fragment has error.");
+                    Log.v(TAG, "app launch fragment has error.");
                     finish();
                     return;
                 }
                 break;
             default:
-                Log.w("HalfSheetActivity", "there is no valid type for half sheet");
+                Log.w(TAG, "there is no valid type for half sheet");
                 finish();
                 return;
         }
@@ -159,23 +126,19 @@
         findViewById(R.id.background).setOnClickListener(v -> onCancelClicked());
         findViewById(R.id.card)
                 .setOnClickListener(
-                        v -> Log.v("HalfSheetActivity", "card view is clicked noop"));
+                        v -> Log.v(TAG, "card view is clicked noop"));
         try {
             mScanFastPairStoreItem =
                     Cache.ScanFastPairStoreItem.parseFrom(infoArray);
         } catch (InvalidProtocolBufferException e) {
             Log.w(
-                    "HalfSheetActivity", "error happens when pass info to half sheet");
+                    TAG, "error happens when pass info to half sheet");
         }
     }
 
     @Override
     protected void onStart() {
         super.onStart();
-        BroadcastUtils.sendBroadcast(
-                this,
-                new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
-                        .putExtra(EXTRA_HALF_SHEET_FOREGROUND, true));
     }
 
     @Override
@@ -217,17 +180,18 @@
                         mScanFastPairStoreItem.getAddress())
                         && testScanFastPairStoreItem.getModelId().equals(
                         mScanFastPairStoreItem.getModelId())) {
-                    Log.d("HalfSheetActivity", "possible factory reset happens");
+                    Log.d(TAG, "possible factory reset happens");
                     halfSheetStateChange();
                 }
             } catch (InvalidProtocolBufferException | NullPointerException e) {
-                Log.w("HalfSheetActivity", "error happens when pass info to half sheet");
+                Log.w(TAG, "error happens when pass info to half sheet");
             }
         }
     }
 
     /** This function should be called when user click empty area and cancel button. */
     public void onCancelClicked() {
+        Log.d(TAG, "Cancels the half sheet and paring.");
         sendHalfSheetCancelBroadcast();
         finish();
     }
@@ -241,20 +205,6 @@
         finish();
     }
 
-    /**
-     * Change the half sheet ban state to active sometimes users leave half sheet to go to fast pair
-     * info page we do not want the behavior to be counted as dismiss.
-     */
-    public void sendBanStateResetBroadcast() {
-        if (mScanFastPairStoreItem != null) {
-            BroadcastUtils.sendBroadcast(
-                    this,
-                    new Intent(ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET)
-                            .putExtra(EXTRA_MODEL_ID,
-                                    mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT)));
-        }
-    }
-
     private void sendHalfSheetCancelBroadcast() {
         BroadcastUtils.sendBroadcast(
                 this,
@@ -280,31 +230,10 @@
         }
     }
 
-    @Nullable
-    @VisibleForTesting
-    public HalfSheetModuleFragment getFragmentModel() {
-        return mHalfSheetModuleFragment;
-    }
-
     @Override
     public void setTitle(CharSequence title) {
         super.setTitle(title);
         TextView toolbarTitle = findViewById(R.id.toolbar_title);
         toolbarTitle.setText(title);
     }
-
-
-    /**
-     * This method converts dp unit to equivalent pixels, depending on device density.
-     *
-     * @param dp      A value in dp (density independent pixels) unit, which we need to convert into
-     *                pixels
-     * @param context Context to get resources and device specific display metrics
-     * @return A float value to represent px equivalent to dp depending on device density
-     */
-    private float convertDpToPixel(float dp, Context context) {
-        return dp
-                * ((float) context.getResources().getDisplayMetrics().densityDpi
-                / DisplayMetrics.DENSITY_DEFAULT);
-    }
 }
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
index 74530de..a62c8cc 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
@@ -17,44 +17,35 @@
 
 import static android.text.TextUtils.isEmpty;
 
-import static com.android.nearby.halfsheet.HalfSheetActivity.ACTION_HALF_SHEET_STATUS_CHANGE;
 import static com.android.nearby.halfsheet.HalfSheetActivity.ARG_FRAGMENT_STATE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_CLASSIC_MAC_ADDRESS;
 import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_DESCRIPTION;
 import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ACCOUNT_NAME;
 import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_CONTENT;
 import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ID;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_IS_RETROACTIVE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_PAIRING_RESURFACE;
 import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_TITLE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.FINISHED_STATE;
+import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FAILED;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FOUND_DEVICE;
 import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.RESULT_FAILURE;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
-import static com.android.server.nearby.fastpair.Constant.DISMISS;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_LAUNCHABLE;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_UNLAUNCHABLE;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRING;
 import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
 import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
 import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
-import static com.android.server.nearby.fastpair.Constant.FAIL_STATE;
-import static com.android.server.nearby.fastpair.Constant.SUCCESS_STATE;
-import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
-import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_PRIVATE_BLE_ADDRESS;
 
-import android.animation.AnimatorSet;
-import android.app.Activity;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.nearby.IFastPairHalfSheetCallback;
+import android.graphics.Bitmap;
+import android.nearby.FastPairClient;
+import android.nearby.FastPairDevice;
+import android.nearby.FastPairStatusCallback;
+import android.nearby.NearbyDevice;
+import android.nearby.PairStatusMetadata;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -69,12 +60,13 @@
 
 import com.android.nearby.halfsheet.HalfSheetActivity;
 import com.android.nearby.halfsheet.R;
-import com.android.nearby.halfsheet.utils.BroadcastUtils;
 import com.android.nearby.halfsheet.utils.FastPairUtils;
-import com.android.server.nearby.fastpair.UserActionHandler;
+import com.android.nearby.halfsheet.utils.IconUtils;
 
 import com.google.protobuf.InvalidProtocolBufferException;
 
+import java.util.Objects;
+
 import service.proto.Cache.ScanFastPairStoreItem;
 
 /**
@@ -83,36 +75,38 @@
  * <p>This fragment will handle initial pairing subsequent pairing and retroactive pairing.
  */
 @SuppressWarnings("nullness")
-public class DevicePairingFragment extends HalfSheetModuleFragment {
+public class DevicePairingFragment extends HalfSheetModuleFragment implements
+        FastPairStatusCallback {
+    private TextView mTitleView;
+    private TextView mSubTitleView;
+    private ImageView mImage;
+
     private Button mConnectButton;
     private Button mSetupButton;
     private Button mCancelButton;
+    // Opens Bluetooth Settings.
+    private Button mSettingsButton;
     private ImageView mInfoIconButton;
     private ProgressBar mConnectProgressBar;
-    private View mRootView;
-    private TextView mSubTitle;
-    private TextView mTitle;
-    private ImageView mImage;
-    private ScanFastPairStoreItem mScanFastPairStoreItem;
-    // This open companion app intent will be triggered after user finish Fast Pair.
-    private Intent mOpenCompanionAppIntent;
-    // Indicates that the setup button is clicked before.
-    private boolean mSetupButtonClicked = false;
-    private boolean mIsSubsequentPair = false;
-    private boolean mIsPairingResurface = false;
-    private String mBluetoothMacAddress = "";
-    private HalfSheetFragmentState mFragmentState = NOT_STARTED;
-    private AnimatorSet mAnimatorSet = new AnimatorSet();
-    // True means pairing was successful and false means failed.
-    private Boolean mPairingResult = false;
+
     private Bundle mBundle;
 
-    public static final String APP_LAUNCH_FRAGMENT_TYPE = "APP_LAUNCH";
-    public static final String FAST_PAIR_CONSENT_FRAGMENT_TYPE = "FAST_PAIR_CONSENT";
-    private static final String ARG_SETUP_BUTTON_CLICKED = "SETUP_BUTTON_CLICKED";
-    public static final String RESULT_FAIL = "RESULT_FAIL";
-    private static final String ARG_PAIRING_RESULT = "PAIRING_RESULT";
+    private ScanFastPairStoreItem mScanFastPairStoreItem;
+    private FastPairClient mFastPairClient;
 
+    private @PairStatusMetadata.Status int mPairStatus = PairStatusMetadata.Status.UNKNOWN;
+    // True when there is a companion app to open.
+    private boolean mIsLaunchable;
+    private boolean mIsConnecting;
+    // Indicates that the setup button is clicked before.
+    private boolean mSetupButtonClicked = false;
+
+    // Holds the new text while we transition between the two.
+    private static final int TAG_PENDING_TEXT = R.id.toolbar_title;
+    public static final String APP_LAUNCH_FRAGMENT_TYPE = "APP_LAUNCH";
+
+    private static final String ARG_SETUP_BUTTON_CLICKED = "SETUP_BUTTON_CLICKED";
+    private static final String ARG_PAIRING_RESULT = "PAIRING_RESULT";
 
     /**
      * Create certain fragment according to the intent.
@@ -122,23 +116,15 @@
             Intent intent, @Nullable Bundle saveInstanceStates) {
         Bundle args = new Bundle();
         byte[] infoArray = intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO);
-        boolean isRetroactive = intent.getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE, false);
-        boolean isSubsequentPair = intent.getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
-                false);
-        boolean isPairingResurface = intent.getBooleanExtra(EXTRA_HALF_SHEET_PAIRING_RESURFACE,
-                false);
+
         Bundle bundle = intent.getBundleExtra(EXTRA_BUNDLE);
         String title = intent.getStringExtra(EXTRA_TITLE);
         String description = intent.getStringExtra(EXTRA_DESCRIPTION);
         String accountName = intent.getStringExtra(EXTRA_HALF_SHEET_ACCOUNT_NAME);
         String result = intent.getStringExtra(EXTRA_HALF_SHEET_CONTENT);
-        String publicAddress = intent.getStringExtra(EXTRA_CLASSIC_MAC_ADDRESS);
         int halfSheetId = intent.getIntExtra(EXTRA_HALF_SHEET_ID, 0);
 
         args.putByteArray(EXTRA_HALF_SHEET_INFO, infoArray);
-        args.putBoolean(EXTRA_HALF_SHEET_IS_RETROACTIVE, isRetroactive);
-        args.putBoolean(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR, isSubsequentPair);
-        args.putBoolean(EXTRA_HALF_SHEET_PAIRING_RESURFACE, isPairingResurface);
         args.putString(EXTRA_HALF_SHEET_ACCOUNT_NAME, accountName);
         args.putString(EXTRA_TITLE, title);
         args.putString(EXTRA_DESCRIPTION, description);
@@ -180,21 +166,22 @@
     public View onCreateView(
             LayoutInflater inflater, @Nullable ViewGroup container,
             @Nullable Bundle savedInstanceState) {
-        mRootView =
-                inflater.inflate(
-                        R.layout.fast_pair_device_pairing_fragment, container, /* attachToRoot= */
-                        false);
+        /* attachToRoot= */
+        View rootView = inflater.inflate(
+                R.layout.fast_pair_device_pairing_fragment, container, /* attachToRoot= */
+                false);
         if (getContext() == null) {
-            Log.d("DevicePairingFragment", "can't find the attached activity");
-            return mRootView;
+            Log.d(TAG, "can't find the attached activity");
+            return rootView;
         }
+
         Bundle args = getArguments();
         byte[] storeFastPairItemBytesArray = args.getByteArray(EXTRA_HALF_SHEET_INFO);
-        boolean isRetroactive = args.getBoolean(EXTRA_HALF_SHEET_IS_RETROACTIVE);
-        mIsSubsequentPair = args.getBoolean(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR);
-        mIsPairingResurface = args.getBoolean(EXTRA_HALF_SHEET_PAIRING_RESURFACE);
-        String accountName = args.getString(EXTRA_HALF_SHEET_ACCOUNT_NAME);
         mBundle = args.getBundle(EXTRA_BUNDLE);
+        if (mBundle != null) {
+            mFastPairClient = new FastPairClient(getContext(), mBundle.getBinder(EXTRA_BINDER));
+            mFastPairClient.registerHalfSheet(this);
+        }
         if (args.containsKey(ARG_FRAGMENT_STATE)) {
             mFragmentState = (HalfSheetFragmentState) args.getSerializable(ARG_FRAGMENT_STATE);
         }
@@ -202,86 +189,53 @@
             mSetupButtonClicked = args.getBoolean(ARG_SETUP_BUTTON_CLICKED);
         }
         if (args.containsKey(ARG_PAIRING_RESULT)) {
-            mPairingResult = args.getBoolean(ARG_PAIRING_RESULT);
-        } else {
-            mPairingResult = false;
+            mPairStatus = args.getInt(ARG_PAIRING_RESULT);
         }
 
-        // title = ((FragmentActivity) getContext()).findViewById(R.id.toolbar_title);
-        mConnectButton = mRootView.findViewById(R.id.connect_btn);
-        mImage = mRootView.findViewById(R.id.pairing_pic);
+        // Initiate views.
+        mTitleView = Objects.requireNonNull(getActivity()).findViewById(R.id.toolbar_title);
+        mSubTitleView = rootView.findViewById(R.id.header_subtitle);
+        mImage = rootView.findViewById(R.id.pairing_pic);
+        mConnectProgressBar = rootView.findViewById(R.id.connect_progressbar);
+        mConnectButton = rootView.findViewById(R.id.connect_btn);
+        mCancelButton = rootView.findViewById(R.id.cancel_btn);
+        mSettingsButton = rootView.findViewById(R.id.settings_btn);
+        mSetupButton = rootView.findViewById(R.id.setup_btn);
+        mInfoIconButton = rootView.findViewById(R.id.info_icon);
+        mInfoIconButton.setImageResource(R.drawable.fast_pair_ic_info);
 
-        mConnectProgressBar = mRootView.findViewById(R.id.connect_progressbar);
-        mConnectProgressBar.setVisibility(View.INVISIBLE);
+        try {
+            setScanFastPairStoreItem(ScanFastPairStoreItem.parseFrom(storeFastPairItemBytesArray));
+        } catch (InvalidProtocolBufferException e) {
+            Log.w(TAG,
+                    "DevicePairingFragment: error happens when pass info to half sheet");
+            return rootView;
+        }
 
+        // Config for landscape mode
         DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
         if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            mRootView.getLayoutParams().height = displayMetrics.heightPixels * 4 / 5;
-            mRootView.getLayoutParams().width = displayMetrics.heightPixels * 4 / 5;
+            rootView.getLayoutParams().height = displayMetrics.heightPixels * 4 / 5;
+            rootView.getLayoutParams().width = displayMetrics.heightPixels * 4 / 5;
             mImage.getLayoutParams().height = displayMetrics.heightPixels / 2;
             mImage.getLayoutParams().width = displayMetrics.heightPixels / 2;
             mConnectProgressBar.getLayoutParams().width = displayMetrics.heightPixels / 2;
             mConnectButton.getLayoutParams().width = displayMetrics.heightPixels / 2;
+            //TODO(b/213373051): Add cancel button
         }
 
-        mCancelButton = mRootView.findViewById(R.id.cancel_btn);
-        mSetupButton = mRootView.findViewById(R.id.setup_btn);
-        mInfoIconButton = mRootView.findViewById(R.id.info_icon);
-        mSubTitle = mRootView.findViewById(R.id.header_subtitle);
-        mSetupButton.setVisibility(View.GONE);
-        mInfoIconButton.setVisibility(View.GONE);
-
-        try {
-            if (storeFastPairItemBytesArray != null) {
-                mScanFastPairStoreItem =
-                        ScanFastPairStoreItem.parseFrom(storeFastPairItemBytesArray);
-            }
-
-            // If the fragmentState is not NOT_STARTED, it is because the fragment was just
-            // resumed from
-            // configuration change (e.g. rotating the screen or half-sheet resurface). Let's
-            // recover the
-            // UI directly.
-            if (mFragmentState != NOT_STARTED) {
-                switch (mFragmentState) {
-                    case PAIRING:
-                        Log.d("DevicePairingFragment", "redraw for PAIRING state.");
-                        return mRootView;
-                    case RESULT_SUCCESS:
-                        Log.d("DevicePairingFragment", "redraw for RESULT_SUCCESS state.");
-                        return mRootView;
-                    case RESULT_FAILURE:
-                        Log.d("DevicePairingFragment", "redraw for RESULT_FAILURE state.");
-                        return mRootView;
-                    default:
-                        // fall-out
-                        Log.d("DevicePairingFragment",
-                                "DevicePairingFragment: not supported state");
-                }
-            }
-            if (mIsPairingResurface) {
-                // Since the Settings contextual card has sent the pairing intent, we don't send the
-                // pairing intent here.
-                onConnectClick(/* sendPairingIntent= */ false);
-            } else {
-                mSubTitle.setText(this.getArguments().getString(EXTRA_DESCRIPTION));
-                mSubTitle.setText("");
-                mConnectButton.setOnClickListener(
-                        v -> onConnectClick(/* sendPairingIntent= */ true));
-                // Pairing fail half sheet resurface
-                if (this.getArguments().getString(EXTRA_HALF_SHEET_CONTENT).equals(RESULT_FAIL)) {
-                    mFragmentState = RESULT_FAILURE;
-                    showFailInfo();
-                } else {
-                    mConnectButton.setOnClickListener(
-                            v -> onConnectClick(/* sendPairingIntent= */ true));
-                }
-            }
-        } catch (InvalidProtocolBufferException e) {
-            Log.w("DevicePairingFragment",
-                    "DevicePairingFragment: error happens when pass info to half sheet");
+        Bitmap icon = IconUtils.getIcon(mScanFastPairStoreItem.getIconPng().toByteArray(),
+                mScanFastPairStoreItem.getIconPng().size());
+        if (icon != null) {
+            mImage.setImageBitmap(icon);
         }
-        return mRootView;
+        mConnectButton.setOnClickListener(v -> onConnectClick());
+        mCancelButton.setOnClickListener(v ->
+                ((HalfSheetActivity) getActivity()).onCancelClicked());
+        mSettingsButton.setOnClickListener(v -> onSettingsClicked());
+        mSetupButton.setOnClickListener(v -> onSetupClick());
+
+        return rootView;
     }
 
     @Override
@@ -294,19 +248,8 @@
     @Override
     public void onStart() {
         super.onStart();
-        if (getContext() != null) {
-            IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(ACTION_HALF_SHEET_STATUS_CHANGE);
-            BroadcastUtils.registerReceiver(getContext(), mHalfSheetChangeReceiver, intentFilter);
-        }
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        if (getContext() != null) {
-            BroadcastUtils.unregisterReceiver(getContext(), mHalfSheetChangeReceiver);
-        }
+        Log.v(TAG, "onStart: invalidate states");
+        invalidateState();
     }
 
     @Override
@@ -315,118 +258,229 @@
 
         savedInstanceState.putSerializable(ARG_FRAGMENT_STATE, mFragmentState);
         savedInstanceState.putBoolean(ARG_SETUP_BUTTON_CLICKED, mSetupButtonClicked);
-        savedInstanceState.putBoolean(ARG_PAIRING_RESULT, mPairingResult);
-
-
+        savedInstanceState.putInt(ARG_PAIRING_RESULT, mPairStatus);
     }
 
-    @Nullable
-    private Intent createCompletionIntent(@Nullable String companionApp, @Nullable String address) {
-        if (isEmpty(companionApp)) {
-            return null;
-        } else if (FastPairUtils.isAppInstalled(getContext(), companionApp)
-                && isLaunchable(companionApp)) {
-            mOpenCompanionAppIntent = createCompanionAppIntent(companionApp, address);
-            return mOpenCompanionAppIntent;
-        } else {
-            return null;
-        }
-    }
-
-    @Nullable
-    private Intent createCompanionAppIntent(String packageName, @Nullable String address) {
-        return createCompanionAppIntent(getContext(), packageName, address);
-    }
-
-    @Nullable
-    private static Intent createCompanionAppIntent(
-            Context context, String packageName, @Nullable String address) {
-        Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
-        BluetoothManager manager = context.getSystemService(BluetoothManager.class);
-        if (address != null && manager != null) {
-            BluetoothAdapter adapter = manager.getAdapter();
-            if (intent != null && adapter != null) {
-                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address));
-            }
-        }
-        return intent;
-    }
-
-    private void onConnectClick(boolean sendPairingIntent) {
-        if (mScanFastPairStoreItem == null) {
-            Log.w("DevicePairingFragment", "No pairing related information in half sheet");
-            return;
-        }
-
-        Log.d("FastPairHalfSheet", "on connect click");
-        // Allow user to setup device before connection setup.
-        // showPairingLastPhase();
-        ((Activity) getContext())
-                .findViewById(R.id.background)
-                .setOnClickListener(
-                        v ->
-                                Log.d("DevicePairingFragment",
-                                        "DevicePairingFragment: tap empty area do not dismiss "
-                                                + "half sheet when pairing."));
-        if (sendPairingIntent) {
-            try {
-                Log.d("FastPairHalfSheet", "on connect click");
-                Intent intent =
-                        new Intent(ACTION_FAST_PAIR)
-                                // Using the DiscoveryChimeraService notification id for
-                                // backwards compat
-                                .putExtra(
-                                        UserActionHandler.EXTRA_DISCOVERY_ITEM,
-                                        FastPairUtils.convertFrom(
-                                                mScanFastPairStoreItem).toByteArray())
-                                .putExtra(EXTRA_MODEL_ID, mScanFastPairStoreItem.getModelId())
-                                .putExtra(EXTRA_PRIVATE_BLE_ADDRESS,
-                                        mScanFastPairStoreItem.getAddress());
-                IFastPairHalfSheetCallback.Stub.asInterface(mBundle.getBinder(EXTRA_BINDER))
-                        .onHalfSheetConnectionConfirm(intent);
-            } catch (RemoteException e) {
-                Log.d("FastPairHalfSheet", "invoke callback fall");
-            }
-        }
-    }
-
-    private void onFailConnectClick() {
+    private void onSettingsClicked() {
         startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
     }
 
-    private final BroadcastReceiver mHalfSheetChangeReceiver =
-            new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (!ACTION_HALF_SHEET_STATUS_CHANGE.equals(intent.getAction())) {
-                        return;
-                    }
-                    if (SUCCESS_STATE.equals(intent.getStringExtra(FINISHED_STATE))) {
-                        mBluetoothMacAddress = intent.getStringExtra(EXTRA_CLASSIC_MAC_ADDRESS);
-                        showSuccessInfo();
-                        if (mOpenCompanionAppIntent != null) {
-                            //((HalfSheetActivity) getContext()).halfSheetStateChange();
-                            String companionApp =
-                                    FastPairUtils.getCompanionAppFromActionUrl(
-                                            mScanFastPairStoreItem.getActionUrl());
-                            // Redirect user to companion app if user choose to setup the app.
-                            // Recreate the intent
-                            // since the correct mac address just populated.
-                            startActivity(
-                                    createCompletionIntent(companionApp, mBluetoothMacAddress));
-                        }
-                    } else if (FAIL_STATE.equals(intent.getStringExtra(FINISHED_STATE))) {
-                        showFailInfo();
-                    } else if (DISMISS.equals(intent.getStringExtra(FINISHED_STATE))) {
-                        if (getContext() != null) {
-                            HalfSheetActivity activity = (HalfSheetActivity) getContext();
-                            activity.finish();
-                        }
-                    }
-                }
-            };
+    private void onSetupClick() {
+        String companionApp =
+                FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
+        Intent intent =
+                FastPairUtils.createCompanionAppIntent(
+                        Objects.requireNonNull(getContext()),
+                        companionApp,
+                        mScanFastPairStoreItem.getAddress());
+        mSetupButtonClicked = true;
+        if (mFragmentState == PAIRED_LAUNCHABLE) {
+            if (intent != null) {
+                startActivity(intent);
+            }
+        } else {
+            Log.d(TAG, "onSetupClick: State is " + mFragmentState);
+        }
+    }
 
-    private boolean isLaunchable(String companionApp) {
-        return createCompanionAppIntent(companionApp, null) != null;
+    private void onConnectClick() {
+        if (mScanFastPairStoreItem == null) {
+            Log.w(TAG, "No pairing related information in half sheet");
+            return;
+        }
+        if (getFragmentState() == PAIRING) {
+            return;
+        }
+        mIsConnecting = true;
+        invalidateState();
+        mFastPairClient.connect(
+                new FastPairDevice.Builder()
+                        .addMedium(NearbyDevice.Medium.BLE)
+                        .setBluetoothAddress(mScanFastPairStoreItem.getAddress())
+                        .setData(FastPairUtils.convertFrom(mScanFastPairStoreItem)
+                                .toByteArray())
+                        .build());
+    }
+
+    // Receives callback from service.
+    @Override
+    public void onPairUpdate(FastPairDevice fastPairDevice, PairStatusMetadata pairStatusMetadata) {
+        @PairStatusMetadata.Status int status = pairStatusMetadata.getStatus();
+        if (status == PairStatusMetadata.Status.DISMISS && getActivity() != null) {
+            getActivity().finish();
+        }
+        mIsConnecting = false;
+        mPairStatus = status;
+        invalidateState();
+    }
+
+    @Override
+    public void invalidateState() {
+        HalfSheetFragmentState newState = NOT_STARTED;
+        if (mIsConnecting) {
+            newState = PAIRING;
+        } else {
+            switch (mPairStatus) {
+                case PairStatusMetadata.Status.SUCCESS:
+                    newState = mIsLaunchable ? PAIRED_LAUNCHABLE : PAIRED_UNLAUNCHABLE;
+                    break;
+                case PairStatusMetadata.Status.FAIL:
+                    newState = FAILED;
+                    break;
+                default:
+                    if (mScanFastPairStoreItem != null) {
+                        newState = FOUND_DEVICE;
+                    }
+            }
+        }
+        if (newState == mFragmentState) {
+            return;
+        }
+        setState(newState);
+    }
+
+    @Override
+    public void setState(HalfSheetFragmentState state) {
+        super.setState(state);
+        invalidateTitles();
+        invalidateButtons();
+    }
+
+    private void setScanFastPairStoreItem(ScanFastPairStoreItem item) {
+        mScanFastPairStoreItem = item;
+        invalidateLaunchable();
+    }
+
+    private void invalidateLaunchable() {
+        String companionApp =
+                FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
+        if (isEmpty(companionApp)) {
+            mIsLaunchable = false;
+            return;
+        }
+        mIsLaunchable =
+                FastPairUtils.isLaunchable(Objects.requireNonNull(getContext()), companionApp);
+    }
+
+    private void invalidateButtons() {
+        mConnectProgressBar.setVisibility(View.INVISIBLE);
+        mConnectButton.setVisibility(View.INVISIBLE);
+        mCancelButton.setVisibility(View.INVISIBLE);
+        mSetupButton.setVisibility(View.INVISIBLE);
+        mSettingsButton.setVisibility(View.INVISIBLE);
+        mInfoIconButton.setVisibility(View.INVISIBLE);
+
+        switch (mFragmentState) {
+            case FOUND_DEVICE:
+                mInfoIconButton.setVisibility(View.VISIBLE);
+                mConnectButton.setVisibility(View.VISIBLE);
+                break;
+            case PAIRING:
+                mConnectProgressBar.setVisibility(View.VISIBLE);
+                mCancelButton.setVisibility(View.VISIBLE);
+                setBackgroundClickable(false);
+                break;
+            case PAIRED_LAUNCHABLE:
+                mCancelButton.setVisibility(View.VISIBLE);
+                mSetupButton.setVisibility(View.VISIBLE);
+                setBackgroundClickable(true);
+                break;
+            case FAILED:
+                mSettingsButton.setVisibility(View.VISIBLE);
+                setBackgroundClickable(true);
+                break;
+            case NOT_STARTED:
+            case PAIRED_UNLAUNCHABLE:
+            default:
+                mCancelButton.setVisibility(View.VISIBLE);
+                setBackgroundClickable(true);
+        }
+    }
+
+    private void setBackgroundClickable(boolean isClickable) {
+        HalfSheetActivity activity = (HalfSheetActivity) getActivity();
+        if (activity == null) {
+            Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable
+                    + " because cannot get HalfSheetActivity.");
+            return;
+        }
+        View background = activity.findViewById(R.id.background);
+        if (background == null) {
+            Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable
+                    + " cannot find background at HalfSheetActivity.");
+            return;
+        }
+        Log.d(TAG, "setBackgroundClickable to " + isClickable);
+        background.setClickable(isClickable);
+    }
+
+    private void invalidateTitles() {
+        String newTitle = getTitle();
+        invalidateTextView(mTitleView, newTitle);
+        String newSubTitle = getSubTitle();
+        invalidateTextView(mSubTitleView, newSubTitle);
+    }
+
+    private void invalidateTextView(TextView textView, String newText) {
+        CharSequence oldText =
+                textView.getTag(TAG_PENDING_TEXT) != null
+                        ? (CharSequence) textView.getTag(TAG_PENDING_TEXT)
+                        : textView.getText();
+        if (TextUtils.equals(oldText, newText)) {
+            return;
+        }
+        if (TextUtils.isEmpty(oldText)) {
+            // First time run. Don't animate since there's nothing to animate from.
+            textView.setText(newText);
+        } else {
+            textView.setTag(TAG_PENDING_TEXT, newText);
+            textView
+                    .animate()
+                    .alpha(0f)
+                    .setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS)
+                    .withEndAction(
+                            () -> {
+                                textView.setText(newText);
+                                textView
+                                        .animate()
+                                        .alpha(1f)
+                                        .setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS);
+                            });
+        }
+    }
+
+    private String getTitle() {
+        switch (mFragmentState) {
+            case PAIRED_LAUNCHABLE:
+                return getString(R.string.fast_pair_title_setup);
+            case FAILED:
+                return getString(R.string.fast_pair_title_fail);
+            case FOUND_DEVICE:
+            case NOT_STARTED:
+            case PAIRED_UNLAUNCHABLE:
+            default:
+                return mScanFastPairStoreItem.getDeviceName();
+        }
+    }
+
+    private String getSubTitle() {
+        switch (mFragmentState) {
+            case PAIRED_LAUNCHABLE:
+                return String.format(
+                        mScanFastPairStoreItem
+                                .getFastPairStrings()
+                                .getPairingFinishedCompanionAppInstalled(),
+                        mScanFastPairStoreItem.getDeviceName());
+            case FAILED:
+                return mScanFastPairStoreItem.getFastPairStrings().getPairingFailDescription();
+            case PAIRED_UNLAUNCHABLE:
+                getString(R.string.fast_pair_device_ready);
+            // fall through
+            case FOUND_DEVICE:
+            case NOT_STARTED:
+                return mScanFastPairStoreItem.getFastPairStrings().getInitialPairingDescription();
+            default:
+                return "";
+        }
     }
 }
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
index 88caf95..f1db4d0 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
@@ -15,17 +15,23 @@
  */
 package com.android.nearby.halfsheet.fragment;
 
+import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
+
 import android.os.Bundle;
+import android.util.Log;
 
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 
 
 /** Base class for all of the half sheet fragment. */
-// TODO(b/177675274): Resolve nullness suppression.
-@SuppressWarnings("nullness")
 public abstract class HalfSheetModuleFragment extends Fragment {
 
+    static final int TEXT_ANIMATION_DURATION_MILLISECONDS = 200;
+
+    HalfSheetFragmentState mFragmentState = NOT_STARTED;
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -38,26 +44,15 @@
 
     /** UI states of the half-sheet fragment. */
     public enum HalfSheetFragmentState {
-        NOT_STARTED,
-        SYNC_CONTACTS,
-        SYNC_SMS,
-        PROGRESSING,
-        CONFIRM_PASSKEY,
-        WRONG_PASSKEY,
-        PAIRING,
-        ADDITIONAL_SETUP_PROGRESS,
-        ADDITIONAL_SETUP_FINAL,
-        RESULT_SUCCESS,
-        RESULT_FAILURE,
-        FINISHED
-    }
-
-    /** Only used in {@link DevicePairingFragment} show pairing success info in half sheet. */
-    public void showSuccessInfo() {
-    }
-
-    /** Only used in {@link DevicePairingFragment} show pairing fail info in half sheet. */
-    public void showFailInfo() {
+        NOT_STARTED, // Initial status
+        FOUND_DEVICE, // When a device is found found from Nearby scan service
+        PAIRING, // When user taps 'Connect' and Fast Pair stars pairing process
+        PAIRED_LAUNCHABLE, // When pair successfully
+        // and we found a launchable companion app installed
+        PAIRED_UNLAUNCHABLE, // When pair successfully
+        // but we cannot find a companion app to launch it
+        FAILED, // When paring was failed
+        FINISHED // When the activity is about to end finished.
     }
 
     /**
@@ -67,6 +62,16 @@
      * activity.
      */
     public HalfSheetFragmentState getFragmentState() {
-        return HalfSheetFragmentState.NOT_STARTED;
+        return mFragmentState;
     }
+
+    void setState(HalfSheetFragmentState state) {
+        Log.v(TAG, "Settings state from " + mFragmentState + " to " + state);
+        mFragmentState = state;
+    }
+
+    /**
+     * Populate data to UI widgets according to the latest {@link HalfSheetFragmentState}.
+     */
+    abstract void invalidateState();
 }
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
index cae2d8e..467997c 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
@@ -16,30 +16,13 @@
 
 package com.android.nearby.halfsheet.utils;
 
-
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 
 /**
  * Broadcast util class
  */
 public class BroadcastUtils {
-    /**
-     * Registers a list of broadcast receiver.
-     */
-    public static void registerReceiver(
-            Context context, BroadcastReceiver receiver, IntentFilter intentFilter) {
-        context.registerReceiver(receiver, intentFilter);
-    }
-
-    /**
-     * Unregisters the already registered receiver.
-     */
-    public static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
-        context.unregisterReceiver(receiver);
-    }
 
     /**
      * Helps send broadcast.
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
index fffb9e1..903ea90 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
@@ -38,8 +38,6 @@
  */
 public class FastPairUtils {
 
-    public static final String TAG = "HalfSheetActivity";
-
     /** FastPair util method check certain app is install on the device or not. */
     public static boolean isAppInstalled(Context context, String packageName) {
         try {
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
index 0521b7b..218c756 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
@@ -16,7 +16,7 @@
 
 package com.android.nearby.halfsheet.utils;
 
-import static com.android.nearby.halfsheet.utils.FastPairUtils.TAG;
+import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;