Merge "Introduce vendor header for selfManaged dialog" into tm-dev

This commit is contained in:
Evan Chen
2022-03-02 23:12:12 +00:00
committed by Android (Google) Code Review
9 changed files with 472 additions and 17 deletions

View File

@@ -0,0 +1,22 @@
<!--
~ Copyright (C) 2022 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/system_accent1_100"/>
<corners android:topLeftRadius="16dp" android:topRightRadius="16dp"
android:bottomLeftRadius="16dp" android:bottomRightRadius="16dp"/>
</shape>

View File

@@ -15,14 +15,10 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_confirmation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/dialog_background"
android:elevation="16dp"
android:maxHeight="400dp"
android:orientation="vertical"
android:padding="18dp"
android:layout_gravity="center">
style="@style/ContainerLayout">
<!-- A header for selfManaged devices only. -->
<include layout="@layout/vendor_header" />
<!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
@@ -51,9 +47,9 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/device_list"
style="@android:style/Widget.Material.ListView"
android:layout_width="match_parent"
android:layout_height="200dp" />
android:layout_width="match_parent"
android:scrollbars="vertical"
android:layout_height="200dp" />
</RelativeLayout>

View File

@@ -0,0 +1,63 @@
<!--
~ Copyright (C) 2022 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/helper_confirmation"
android:theme="@style/ChooserActivity"
style="@style/ContainerLayout">
<ImageView
android:id="@+id/app_icon"
android:layout_width="match_parent"
android:layout_height="32dp"
android:gravity="center"
android:layout_marginBottom="12dp"
android:layout_marginTop="1dp"/>
<TextView
android:id="@+id/helper_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingHorizontal="12dp"
style="@*android:style/TextAppearance.Widget.Toolbar.Title"
android:textSize="20sp" />
<TextView
android:id="@+id/helper_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginLeft="20dp"
android:layout_marginBottom="24dp"
android:gravity="start"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
<Button
android:id="@+id/btn_ok"
style="@style/VendorHelperOkButton"
android:text="@string/consent_ok" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,48 @@
<!--
~ Copyright (C) 2022 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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/vendor_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center"
android:layout_marginBottom="16dp"
android:visibility="gone" >
<ImageView
android:id="@+id/vendor_header_image"
android:layout_width="31dp"
android:layout_height="32dp" />
<TextView
android:id="@+id/vendor_header_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="5dp"
android:layout_toRightOf="@+id/header_image" />
<ImageButton
android:id="@+id/vendor_header_button"
style="?android:attr/actionOverflowButtonStyle"
android:layout_width="31dp"
android:layout_height="32dp"
android:layout_marginLeft="100dp"
android:layout_alignParentRight="true" />
</RelativeLayout>

View File

@@ -39,7 +39,7 @@
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
<string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream applications?</string>
<string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to access this information for your phone</string>
<!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_app_streaming" product="default">Let &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to provide &lt;strong&gt;<xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this phone when connected.</string>
@@ -50,6 +50,12 @@
<!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_app_streaming" product="device">Let &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to provide &lt;strong&gt;<xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this device when connected.</string>
<!-- Title of the helper dialog for APP_STREAMING profile [CHAR LIMIT=30]. -->
<string name="helper_title_app_streaming">Cross-device services</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_app_streaming">This service is used to stream apps between your devices</string>
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
<!-- Confirmation for associating an application with a companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
@@ -66,6 +72,15 @@
<!-- Description of the privileges the application will get if associated with the companion device of COMPUTER profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_computer"></string>
<!-- Title of the helper dialog for COMPUTER profile [CHAR LIMIT=30]. -->
<string name="helper_title_computer">Google Play services</string>
<!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_computer" product="default">This service shares photos, media, and notifications form your phone to other devices</string>
<!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_computer" product="tablet">This service shares photos, media, and notifications form your phone to other devices</string>
<!-- ================= null profile ================= -->
<!-- A noun for a companion device with unspecified profile (type) [CHAR LIMIT=30] -->
@@ -82,6 +97,9 @@
<!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
<string name="consent_no">Don\u2019t allow</string>
<!-- Ok button for the helper consent dialog [CHAR LIMIT=30] -->
<string name="consent_ok">OK</string>
<!-- ================== System data transfer ==================== -->
<!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=60] -->
<string name="permission_sync_confirmation_title">Transfer app permissions to your

View File

@@ -0,0 +1,38 @@
<!--
~ Copyright (C) 2022 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.
-->
<resources>
<style name="ContainerLayout">
<item name="android:padding">18dp</item>
<item name="android:elevation">16dp</item>
<item name="android:maxHeight">400dp</item>
<item name="android:orientation">vertical</item>
<item name="android:layout_gravity">center</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@drawable/dialog_background</item>
</style>
<style name="VendorHelperOkButton"
parent="@android:style/Widget.Material.Button.Borderless.Colored">
<item name="android:layout_width">50dp</item>
<item name="android:layout_height">35dp</item>
<item name="android:layout_marginTop">20dp</item>
<item name="android:textColor">@android:color/system_neutral1_900</item>
<item name="android:background">@drawable/helper_ok_button</item>
</style>
</resources>

View File

@@ -26,6 +26,8 @@ import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService
import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import static com.android.companiondevicemanager.Utils.getVendorHeaderIcon;
import static com.android.companiondevicemanager.Utils.getVendorHeaderName;
import static com.android.companiondevicemanager.Utils.prepareResultReceiverForIpc;
import static java.util.Objects.requireNonNull;
@@ -38,6 +40,7 @@ import android.companion.CompanionDeviceManager;
import android.companion.IAssociationRequestCallback;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.MacAddress;
import android.os.Bundle;
import android.os.Handler;
@@ -47,9 +50,14 @@ import android.text.Spanned;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -59,7 +67,8 @@ import java.util.List;
* A CompanionDevice activity response for showing the available
* nearby devices to be associated with.
*/
public class CompanionDeviceActivity extends FragmentActivity {
public class CompanionDeviceActivity extends FragmentActivity implements
CompanionVendorHelperDialogFragment.CompanionVendorHelperDialogListener {
private static final boolean DEBUG = false;
private static final String TAG = CompanionDeviceActivity.class.getSimpleName();
@@ -72,6 +81,8 @@ public class CompanionDeviceActivity extends FragmentActivity {
private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
private static final String FRAGMENT_DIALOG_TAG = "fragment_dialog";
// Activity result: Internal Error.
private static final int RESULT_INTERNAL_ERROR = 2;
@@ -91,6 +102,11 @@ public class CompanionDeviceActivity extends FragmentActivity {
private TextView mTitle;
private TextView mSummary;
// Only present for selfManaged devices.
private ImageView mVendorHeaderImage;
private TextView mVendorHeaderName;
private ImageButton mVendorHeaderButton;
// Progress indicator is only shown while we are looking for the first suitable device for a
// "regular" (ie. not self-managed) association.
private View mProgressIndicator;
@@ -98,6 +114,11 @@ public class CompanionDeviceActivity extends FragmentActivity {
// Present for self-managed association requests and "single-device" regular association
// regular.
private Button mButtonAllow;
// Present for all associations.
private Button mButtonNotAllow;
private LinearLayout mAssociationConfirmationDialog;
private RelativeLayout mVendorHeader;
// The recycler view is only shown for multiple-device regular association request, after
// at least one matching device is found.
@@ -211,15 +232,25 @@ public class CompanionDeviceActivity extends FragmentActivity {
setContentView(R.layout.activity_confirmation);
mAssociationConfirmationDialog = findViewById(R.id.activity_confirmation);
mVendorHeader = findViewById(R.id.vendor_header);
mTitle = findViewById(R.id.title);
mSummary = findViewById(R.id.summary);
mVendorHeaderImage = findViewById(R.id.vendor_header_image);
mVendorHeaderName = findViewById(R.id.vendor_header_name);
mVendorHeaderButton = findViewById(R.id.vendor_header_button);
mRecyclerView = findViewById(R.id.device_list);
mAdapter = new DeviceListAdapter(this, this::onListItemClick);
mButtonAllow = findViewById(R.id.btn_positive);
mButtonNotAllow = findViewById(R.id.btn_negative);
mButtonAllow.setOnClickListener(this::onPositiveButtonClick);
findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick);
mButtonNotAllow.setOnClickListener(this::onNegativeButtonClick);
mVendorHeaderButton.setOnClickListener(this::onShowHelperDialog);
if (mRequest.isSelfManaged()) {
initUiForSelfManagedAssociation(appLabel);
@@ -321,11 +352,24 @@ public class CompanionDeviceActivity extends FragmentActivity {
private void initUiForSelfManagedAssociation(CharSequence appLabel) {
if (DEBUG) Log.i(TAG, "initUiFor_SelfManaged_Association()");
final CharSequence deviceName = mRequest.getDisplayName(); // "<device>";
final String deviceProfile = mRequest.getDeviceProfile(); // DEVICE_PROFILE_APP_STREAMING;
final CharSequence deviceName = mRequest.getDisplayName();
final String deviceProfile = mRequest.getDeviceProfile();
final String packageName = mRequest.getPackageName();
final int userId = mRequest.getUserId();
final Drawable vendorIcon;
final CharSequence vendorName;
final Spanned title;
final Spanned summary;
try {
vendorIcon = getVendorHeaderIcon(this, packageName, userId);
vendorName = getVendorHeaderName(this, packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
setResultAndFinish(null, RESULT_INTERNAL_ERROR);
return;
}
switch (deviceProfile) {
case DEVICE_PROFILE_APP_STREAMING:
title = getHtmlFromResources(this, R.string.title_app_streaming, appLabel);
@@ -348,10 +392,14 @@ public class CompanionDeviceActivity extends FragmentActivity {
default:
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
mTitle.setText(title);
mSummary.setText(summary);
mVendorHeaderImage.setImageDrawable(vendorIcon);
mVendorHeaderName.setText(vendorName);
mRecyclerView.setVisibility(View.GONE);
mVendorHeader.setVisibility(View.VISIBLE);
}
private void initUiForSingleDevice(CharSequence appLabel) {
@@ -370,6 +418,7 @@ public class CompanionDeviceActivity extends FragmentActivity {
String deviceProfile, CharSequence appLabel) {
// Ignore "empty" scan reports.
if (deviceFilterPairs.isEmpty()) return;
mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
final String deviceName = mSelectedDevice.getDisplayName();
@@ -464,6 +513,17 @@ public class CompanionDeviceActivity extends FragmentActivity {
cancel(false);
}
private void onShowHelperDialog(View view) {
FragmentManager fragmentManager = getSupportFragmentManager();
CompanionVendorHelperDialogFragment fragmentDialog =
CompanionVendorHelperDialogFragment.newInstance(mRequest.getPackageName(),
mRequest.getUserId(), mRequest.getDeviceProfile());
mAssociationConfirmationDialog.setVisibility(View.GONE);
fragmentDialog.show(fragmentManager, /* Tag */ FRAGMENT_DIALOG_TAG);
}
private boolean isDone() {
return mApproved || mCancelled;
}
@@ -482,4 +542,14 @@ public class CompanionDeviceActivity extends FragmentActivity {
onAssociationCreated(association);
}
};
@Override
public void onShowHelperDialogFailed() {
setResultAndFinish(null, RESULT_INTERNAL_ERROR);
}
@Override
public void onHelperDialogDismissed() {
mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2022 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.companiondevicemanager;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
import static com.android.companiondevicemanager.Utils.getApplicationIcon;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import android.annotation.Nullable;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Spanned;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
/**
* A fragmentDialog shows additional information about selfManaged devices
*/
public class CompanionVendorHelperDialogFragment extends DialogFragment {
private static final String TAG = CompanionVendorHelperDialogFragment.class.getSimpleName();
private static final String PACKAGE_NAME_EXTRA = "packageName";
private static final String DEVICE_PROFILE_EXTRA = "deviceProfile";
private static final String USER_ID_EXTRA = "userId";
private CompanionVendorHelperDialogListener mListener;
// Only present for selfManaged devices.
private TextView mTitle;
private TextView mSummary;
private ImageView mAppIcon;
private Button mButton;
interface CompanionVendorHelperDialogListener {
void onShowHelperDialogFailed();
void onHelperDialogDismissed();
}
private CompanionVendorHelperDialogFragment() {}
static CompanionVendorHelperDialogFragment newInstance(String packageName,
int userId, String deviceProfile) {
CompanionVendorHelperDialogFragment fragmentDialog =
new CompanionVendorHelperDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(PACKAGE_NAME_EXTRA, packageName);
bundle.putInt(USER_ID_EXTRA, userId);
bundle.putString(DEVICE_PROFILE_EXTRA, deviceProfile);
fragmentDialog.setArguments(bundle);
return fragmentDialog;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListener = (CompanionVendorHelperDialogListener) getActivity();
// Hide the title bar in the dialog.
setStyle(STYLE_NO_TITLE, /* Theme */0);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.helper_confirmation, container);
}
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
mListener.onHelperDialogDismissed();
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Drawable applicationIcon;
String packageName = getArguments().getString(PACKAGE_NAME_EXTRA);
String deviceProfile = getArguments().getString(DEVICE_PROFILE_EXTRA);
int userId = getArguments().getInt(USER_ID_EXTRA);
try {
applicationIcon = getApplicationIcon(getContext(), packageName);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
mListener.onShowHelperDialogFailed();
return;
}
mTitle = view.findViewById(R.id.helper_title);
mSummary = view.findViewById(R.id.helper_summary);
mAppIcon = view.findViewById(R.id.app_icon);
mButton = view.findViewById(R.id.btn_ok);
final Spanned title;
final Spanned summary;
switch (deviceProfile) {
case DEVICE_PROFILE_APP_STREAMING:
title = getHtmlFromResources(getContext(), R.string.helper_title_app_streaming);
summary = getHtmlFromResources(getContext(), R.string.helper_summary_app_streaming);
break;
case DEVICE_PROFILE_COMPUTER:
title = getHtmlFromResources(getContext(), R.string.helper_title_computer);
summary = getHtmlFromResources(getContext(), R.string.helper_summary_computer);
break;
default:
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
mTitle.setText(title);
mSummary.setText(summary);
mAppIcon.setImageDrawable(applicationIcon);
mButton.setOnClickListener(v -> {
dismiss();
mListener.onHelperDialogDismissed();
});
}
}

View File

@@ -21,6 +21,9 @@ import android.annotation.StringRes;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
@@ -32,6 +35,10 @@ import android.text.Spanned;
* Utilities.
*/
class Utils {
private static final String COMPANION_DEVICE_ACTIVITY_VENDOR_ICON =
"android.companion.vendor_icon";
private static final String COMPANION_DEVICE_ACTIVITY_VENDOR_NAME =
"android.companion.vendor_name";
/**
* Convert an instance of a "locally-defined" ResultReceiver to an instance of
@@ -60,6 +67,12 @@ class Utils {
return packageManager.getApplicationLabel(appInfo);
}
static @NonNull Drawable getApplicationIcon(@NonNull Context context,
@NonNull String packageName) throws PackageManager.NameNotFoundException {
final PackageManager packageManager = context.getPackageManager();
return packageManager.getApplicationIcon(packageName);
}
static Spanned getHtmlFromResources(
@NonNull Context context, @StringRes int resId, CharSequence... formatArgs) {
final String[] escapedArgs = new String[formatArgs.length];
@@ -70,6 +83,44 @@ class Utils {
return Html.fromHtml(plain, 0);
}
static @NonNull Drawable getVendorHeaderIcon(@NonNull Context context,
@NonNull String packageName, int userId) throws PackageManager.NameNotFoundException {
final ApplicationInfo appInfo = getApplicationInfo(context, packageName, userId);
final Bundle bundle = appInfo.metaData;
int resId = bundle == null ? 0 : bundle.getInt(COMPANION_DEVICE_ACTIVITY_VENDOR_ICON, 0);
if (bundle == null || resId == 0) {
return getApplicationIcon(context, packageName);
}
return context.createPackageContext(packageName, /* flags= */ 0).getDrawable(resId);
}
static CharSequence getVendorHeaderName(@NonNull Context context,
@NonNull String packageName, int userId) throws PackageManager.NameNotFoundException {
final ApplicationInfo appInfo = getApplicationInfo(context, packageName, userId);
final Bundle bundle = appInfo.metaData;
if (bundle == null) {
return "";
}
return appInfo.metaData.getCharSequence(COMPANION_DEVICE_ACTIVITY_VENDOR_NAME, "");
}
/**
* Getting ApplicationInfo from meta-data.
*/
private static @NonNull ApplicationInfo getApplicationInfo(@NonNull Context context,
@NonNull String packageName, int userId) throws PackageManager.NameNotFoundException {
final PackageManager packageManager = context.getPackageManager();
final ApplicationInfoFlags flags = ApplicationInfoFlags.of(PackageManager.GET_META_DATA);
final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
packageName, flags, userId);
return appInfo;
}
static void runOnMainThread(Runnable runnable) {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
runnable.run();