Allow to show device name as toolbar title.

Fixes: 143956787
Test: manually
Change-Id: I43f1f4e7bcc8228a351df5fc3be399d65a078295
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e409c95..a7801ab 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -42,7 +42,7 @@
 
         <activity android:name=".ui.TelecomActivity"
                   android:launchMode="singleTask"
-                  android:theme="@style/Theme.Dialer"
+                  android:theme="@style/Theme.Dialer.Telecom"
                   android:label="@string/phone_app_name"
                   android:exported="true"
                   android:resizeableActivity="true">
@@ -83,7 +83,7 @@
 
         <activity android:name=".ui.activecall.InCallActivity"
                   android:launchMode="singleInstance"
-                  android:theme="@style/Theme.Dialer"
+                  android:theme="@style/Theme.Dialer.InCall"
                   android:label="@string/phone_app_name">
             <meta-data android:name="distractionOptimized" android:value="true"/>
         </activity>
diff --git a/res/values-port/strings.xml b/res/values-port/configs.xml
similarity index 77%
rename from res/values-port/strings.xml
rename to res/values-port/configs.xml
index 572bb37..b0af8bd 100644
--- a/res/values-port/strings.xml
+++ b/res/values-port/configs.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version='1.0' encoding='UTF-8'?>
 <!--
   Copyright (C) 2019 The Android Open Source Project
 
@@ -14,9 +14,7 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-
 <resources>
-    <!-- Titles -->
-    <!-- Toolbar title for tabbed pages -->
-    <string name="default_toolbar_title">@string/phone_app_name</string>
-</resources>
\ No newline at end of file
+    <!-- Use app name as toolbar title. Supported value see @attr/toolbarTitleMode -->
+    <integer name="config_toolbar_title_mode">0</integer>
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 7b966de..f085c19 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -29,6 +29,13 @@
     <declare-styleable name="Theme">
         <!-- Tab bar height that only applies to portrait layout where tabs are under action bar.-->
         <attr name="tabBarSize" format="dimension"/>
+
+        <!-- Toolbar title mode -->
+        <attr name="toolbarTitleMode" format="enum">
+            <enum name="app_name" value="0"/>
+            <enum name="none" value="1"/>
+            <enum name="device_name" value="2"/>
+        </attr>
     </declare-styleable>
 
     <declare-styleable name="LoadingFrameLayout">
diff --git a/res/values/configs.xml b/res/values/configs.xml
index 531b0fd..42ba9ab 100644
--- a/res/values/configs.xml
+++ b/res/values/configs.xml
@@ -33,4 +33,7 @@
 
     <!-- A config determines if to show the action bar view in the contact details page.-->
     <bool name="config_show_contact_details_action_bar_view">false</bool>
+
+    <!-- No toolbar title. Supported value see @attr/toolbarTitleMode -->
+    <integer name="config_toolbar_title_mode">1</integer>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 862794a..579c363 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -70,8 +70,6 @@
     <string name="contacts_title">Contacts</string>
     <!-- Title for the dialpad [CHAR LIMIT=30] -->
     <string name="dialpad_title">Dialpad</string>
-    <!-- Toolbar title for tabbed pages -->
-    <string name="default_toolbar_title" translatable="false"></string>
 
     <!-- Headers for call history -->
     <!-- Header in call log to group calls from the current day. [CHAR LIMIT=30] -->
diff --git a/res/values/themes.xml b/res/values/themes.xml
index b64b934..e3b054c 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -14,7 +14,7 @@
 limitations under the License.
 -->
 <resources>
-    <!-- The theme for the Dialer app. -->
+    <!-- The base theme for the Dialer app. -->
     <style name="Theme.Dialer" parent="android:Theme.DeviceDefault.NoActionBar">
         <item name="android:actionBarSize">@dimen/action_bar_height</item>
         <item name="tabBarSize">@dimen/tab_bar_height</item>
@@ -29,6 +29,12 @@
         <item name="android:searchViewStyle">@style/Widget.Dialer.SearchView</item>
     </style>
 
+    <style name="Theme.Dialer.Telecom" parent="Theme.Dialer">
+        <item name="toolbarTitleMode">@integer/config_toolbar_title_mode</item>
+    </style>
+
+    <style name="Theme.Dialer.InCall" parent="Theme.Dialer"/>
+
     <style name="Theme.Dialer.Setting" parent="Theme.Dialer">
         <item name="preferenceTheme">@style/PreferenceTheme</item>
     </style>
diff --git a/src/com/android/car/dialer/livedata/HfpDeviceListLiveData.java b/src/com/android/car/dialer/livedata/HfpDeviceListLiveData.java
new file mode 100644
index 0000000..54794d5
--- /dev/null
+++ b/src/com/android/car/dialer/livedata/HfpDeviceListLiveData.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 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.car.dialer.livedata;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import java.util.List;
+
+/** {@link LiveData} that monitors the hfp connected devices. */
+public class HfpDeviceListLiveData extends MutableLiveData<List<BluetoothDevice>> {
+    private final Context mContext;
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final IntentFilter mIntentFilter;
+
+    private BluetoothHeadsetClient mBluetoothHeadsetClient;
+
+    private BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            update();
+        }
+    };
+
+    /** Creates a new {@link HfpDeviceListLiveData}. Call on main thread. */
+    public HfpDeviceListLiveData(Context context) {
+        mContext = context;
+
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mBluetoothAdapter != null) {
+            mBluetoothAdapter.getProfileProxy(mContext, new BluetoothProfile.ServiceListener() {
+                @Override
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    if (profile == BluetoothProfile.HEADSET_CLIENT) {
+                        mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
+                        update();
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(int profile) {
+                }
+            }, BluetoothProfile.HEADSET_CLIENT);
+        }
+
+        mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+    }
+
+    @Override
+    protected void onActive() {
+        if (mBluetoothAdapter != null) {
+            update();
+            mContext.registerReceiver(mBluetoothStateReceiver, mIntentFilter);
+        }
+    }
+
+    @Override
+    protected void onInactive() {
+        if (mBluetoothAdapter != null) {
+            mContext.unregisterReceiver(mBluetoothStateReceiver);
+        }
+    }
+
+    private void update() {
+        if (mBluetoothHeadsetClient != null) {
+            setValue(mBluetoothHeadsetClient.getConnectedDevices());
+        }
+    }
+}
diff --git a/src/com/android/car/dialer/ui/TelecomActivity.java b/src/com/android/car/dialer/ui/TelecomActivity.java
index 26b5cf9..9a2160d 100644
--- a/src/com/android/car/dialer/ui/TelecomActivity.java
+++ b/src/com/android/car/dialer/ui/TelecomActivity.java
@@ -36,6 +36,7 @@
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
 import androidx.lifecycle.ViewModelProviders;
 import androidx.preference.PreferenceManager;
 
@@ -73,11 +74,9 @@
 public class TelecomActivity extends FragmentActivity implements
         DialerBaseFragment.DialerFragmentParent, FragmentManager.OnBackStackChangedListener {
     private static final String TAG = "CD.TelecomActivity";
-
     private LiveData<String> mBluetoothErrorMsgLiveData;
     private LiveData<Integer> mDialerAppStateLiveData;
     private LiveData<List<Call>> mOngoingCallListLiveData;
-
     // View objects for this activity.
     private CarTabLayout<TelecomPageTab> mTabLayout;
     private TelecomPageTab.Factory mTabFactory;
@@ -87,6 +86,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
         L.d(TAG, "onCreate");
         setContentView(R.layout.telecom_activity);
 
@@ -104,6 +104,8 @@
         mDialerAppStateLiveData = viewModel.getDialerAppState();
         mDialerAppStateLiveData.observe(this,
                 dialerAppState -> updateCurrentFragment(dialerAppState));
+        MutableLiveData<Integer> toolbarTitleMode = viewModel.getToolbarTitleMode();
+        toolbarTitleMode.setValue(Themes.getAttrInteger(this, R.attr.toolbarTitleMode));
 
         InCallViewModel inCallViewModel = ViewModelProviders.of(this).get(InCallViewModel.class);
         mOngoingCallListLiveData = inCallViewModel.getOngoingCallList();
diff --git a/src/com/android/car/dialer/ui/TelecomActivityViewModel.java b/src/com/android/car/dialer/ui/TelecomActivityViewModel.java
index c9125c1..e28c4ce 100644
--- a/src/com/android/car/dialer/ui/TelecomActivityViewModel.java
+++ b/src/com/android/car/dialer/ui/TelecomActivityViewModel.java
@@ -44,13 +44,18 @@
  */
 public class TelecomActivityViewModel extends AndroidViewModel {
     private static final String TAG = "CD.TelecomActivityViewModel";
-    /** A constant which indicates that there's no Bluetooth error. */
+    /**
+     * A constant which indicates that there's no Bluetooth error.
+     */
     public static final String NO_BT_ERROR = "NO_ERROR";
 
     private final Context mApplicationContext;
     private final LiveData<String> mErrorStringLiveData;
     private final MutableLiveData<Integer> mDialerAppStateLiveData;
 
+    private final ToolbarTitleLiveData mToolbarTitleLiveData;
+    private final MutableLiveData<Integer> mToolbarTitleMode;
+
     /**
      * App state indicates if bluetooth is connected or it should just show the content fragments.
      */
@@ -67,6 +72,9 @@
         super(application);
         mApplicationContext = application.getApplicationContext();
 
+        mToolbarTitleMode = new MediatorLiveData<>();
+        mToolbarTitleLiveData = new ToolbarTitleLiveData(mApplicationContext, mToolbarTitleMode);
+
         if (BluetoothAdapter.getDefaultAdapter() == null) {
             MutableLiveData<String> bluetoothUnavailableLiveData = new MutableLiveData<>();
             bluetoothUnavailableLiveData.setValue(
@@ -84,6 +92,22 @@
         mDialerAppStateLiveData = new DialerAppStateLiveData(mErrorStringLiveData);
     }
 
+    /**
+     * Returns the {@link LiveData} for the toolbar title, which provides the toolbar title
+     * depending on the {@link R.attr#toolbarTitleMode}.
+     */
+    public LiveData<String> getToolbarTitle() {
+        return mToolbarTitleLiveData;
+    }
+
+    /**
+     * Returns the {@link MutableLiveData} of the toolbar title mode. The value should be set by the
+     * {@link TelecomActivity}.
+     */
+    public MutableLiveData<Integer> getToolbarTitleMode() {
+        return mToolbarTitleMode;
+    }
+
     public MutableLiveData<Integer> getDialerAppState() {
         return mDialerAppStateLiveData;
     }
diff --git a/src/com/android/car/dialer/ui/ToolbarTitleLiveData.java b/src/com/android/car/dialer/ui/ToolbarTitleLiveData.java
new file mode 100644
index 0000000..11863dd
--- /dev/null
+++ b/src/com/android/car/dialer/ui/ToolbarTitleLiveData.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 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.car.dialer.ui;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+import androidx.annotation.IntDef;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+
+import com.android.car.dialer.R;
+import com.android.car.dialer.livedata.HfpDeviceListLiveData;
+
+import java.util.List;
+
+/**
+ * LiveData for the toolbar title of the  {@link com.android.car.dialer.ui.TelecomActivity}. The
+ * attribute {@link R.attr#toolbarTitleMode} is activity scope attribute. Supported values are:
+ * <ul>
+ *     <li> app_name: 0
+ *     <li> none: 1
+ *     <li> device_name: 2
+ */
+class ToolbarTitleLiveData extends MediatorLiveData<String> {
+
+    @IntDef({
+            ToolbarTitleMode.APP_NAME,
+            ToolbarTitleMode.NONE,
+            ToolbarTitleMode.DEVICE_NAME
+    })
+    private @interface ToolbarTitleMode {
+        int APP_NAME = 0;
+        int NONE = 1;
+        int DEVICE_NAME = 2;
+    }
+
+    private final HfpDeviceListLiveData mHfpDeviceListLiveData;
+    private final LiveData<Integer> mToolbarTitleModeLiveData;
+    private final Context mContext;
+
+    ToolbarTitleLiveData(Context context, LiveData<Integer> toolbarTitleModeLiveData) {
+        mContext = context;
+        mToolbarTitleModeLiveData = toolbarTitleModeLiveData;
+        mHfpDeviceListLiveData = new HfpDeviceListLiveData(context);
+
+        addSource(mToolbarTitleModeLiveData, this::updateToolbarTitle);
+    }
+
+    private void updateToolbarTitle(int toolbarTitleMode) {
+        switch (toolbarTitleMode) {
+            case ToolbarTitleMode.NONE:
+                removeSource(mHfpDeviceListLiveData);
+                setValue(null);
+                return;
+            case ToolbarTitleMode.DEVICE_NAME:
+                addSource(mHfpDeviceListLiveData, this::updateDeviceName);
+                return;
+            case ToolbarTitleMode.APP_NAME:
+            default:
+                removeSource(mHfpDeviceListLiveData);
+                setValue(mContext.getString(R.string.phone_app_name));
+                return;
+        }
+    }
+
+    private void updateDeviceName(List<BluetoothDevice> hfpDeviceList) {
+        // When there is no hfp device connected, use the app name as title.
+        if (hfpDeviceList == null || hfpDeviceList.isEmpty()) {
+            setValue(mContext.getString(R.string.phone_app_name));
+            return;
+        }
+
+        // TODO: handle multi-HFP cases. Right now return the first connected device in list.
+        BluetoothDevice bluetoothDevice = hfpDeviceList.get(0);
+        setValue(bluetoothDevice.getName());
+    }
+}
diff --git a/src/com/android/car/dialer/ui/common/DialerBaseFragment.java b/src/com/android/car/dialer/ui/common/DialerBaseFragment.java
index 87b5240..9fc862b 100644
--- a/src/com/android/car/dialer/ui/common/DialerBaseFragment.java
+++ b/src/com/android/car/dialer/ui/common/DialerBaseFragment.java
@@ -24,10 +24,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.ViewModelProviders;
 
 import com.android.car.apps.common.util.Themes;
 import com.android.car.dialer.R;
 import com.android.car.dialer.ui.TelecomActivity;
+import com.android.car.dialer.ui.TelecomActivityViewModel;
 
 /** The base class for top level dialer content {@link Fragment}s. */
 public abstract class DialerBaseFragment extends Fragment {
@@ -46,7 +49,11 @@
 
     /** Customizes the action bar. Can be overridden in subclasses. */
     public void setupActionBar(@NonNull ActionBar actionBar) {
-        actionBar.setTitle(getActionBarTitle());
+        TelecomActivityViewModel viewModel = ViewModelProviders.of(getActivity()).get(
+                TelecomActivityViewModel.class);
+        LiveData<String> toolbarTitleLiveData = viewModel.getToolbarTitle();
+        toolbarTitleLiveData.observe(this,
+                toolbarTitle -> setActionBarTitle(actionBar, toolbarTitle));
         actionBar.setCustomView(null);
         setActionBarBackground(getContext().getDrawable(R.color.app_bar_background_color));
     }
@@ -59,11 +66,6 @@
         }
     }
 
-    /** Return the action bar title. */
-    protected CharSequence getActionBarTitle() {
-        return getString(R.string.default_toolbar_title);
-    }
-
     protected int getTopBarHeight() {
         View toolbar = getActivity().findViewById(R.id.car_toolbar);
 
@@ -79,6 +81,11 @@
         return topBarHeight;
     }
 
+    protected final void setActionBarTitle(@NonNull ActionBar actionBar,
+            @Nullable CharSequence title) {
+        actionBar.setTitle(title);
+    }
+
     protected final void setActionBarBackground(@Nullable Drawable drawable) {
         Activity activity = getActivity();
         if (activity instanceof TelecomActivity) {
diff --git a/src/com/android/car/dialer/ui/search/ContactResultsFragment.java b/src/com/android/car/dialer/ui/search/ContactResultsFragment.java
index 3888179..63a5d0f 100644
--- a/src/com/android/car/dialer/ui/search/ContactResultsFragment.java
+++ b/src/com/android/car/dialer/ui/search/ContactResultsFragment.java
@@ -127,7 +127,8 @@
 
     @Override
     public void setupActionBar(@NonNull ActionBar actionBar) {
-        super.setupActionBar(actionBar);
+        setActionBarTitle(actionBar, null);
+        setActionBarBackground(getContext().getDrawable(R.color.app_bar_background_color));
 
         // We have to use the setCustomView that accepts a LayoutParams to get the SearchView
         // to take up the full height and width of the action bar.
@@ -192,11 +193,6 @@
     }
 
     @Override
-    protected CharSequence getActionBarTitle() {
-        return null;
-    }
-
-    @Override
     public void onShowContactDetail(Contact contact) {
         Fragment contactDetailsFragment = ContactDetailsFragment.newInstance(contact);
         pushContentFragment(contactDetailsFragment, ContactDetailsFragment.FRAGMENT_TAG);