Added transparency-metadata display.
This feature enables 'App Details' to support MBAs with metadata file and display in an offline UI. This change includes a new fragment and user view that sources data from within any MBAs apk file that contains application-metadata.xml. Test: make -j64 RunSettingsRoboTests , AVD manual test Change-Id: If7822100a90f5fb8d33ce5d85958391fc33ecbee Bug: 244215932
This commit is contained in:
@@ -4605,6 +4605,17 @@
|
|||||||
android:exported="false">
|
android:exported="false">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.android.settings.applications.mobilebundledapps.MobileBundledAppDetailsActivity"
|
||||||
|
android:label="@string/mobile_bundled_apps_details_title"
|
||||||
|
android:exported="true"
|
||||||
|
android:icon="@drawable/ic_homepage_connected_device">
|
||||||
|
<intent-filter android:priority="1">
|
||||||
|
<action android:name="android.settings.TRANSPARENCY_METADATA" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<!-- This is the longest AndroidManifest.xml ever. -->
|
<!-- This is the longest AndroidManifest.xml ever. -->
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
107
res/layout/mobile_bundled_apps_details_fragment.xml
Normal file
107
res/layout/mobile_bundled_apps_details_fragment.xml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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/app_details_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contains_ads_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/mobile_bundled_apps_details_contains_ad_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contains_ads"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/developer_info_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/mobile_bundled_apps_details_developer_info_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/developer_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contact_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/mobile_bundled_apps_details_contact_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contact_url"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contact_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/description_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/mobile_bundled_apps_details_description_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/privacy_policy_url_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/mobile_bundled_apps_details_privacy_policy_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/privacy_policy_url"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/category_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/mobile_bundled_apps_details_category_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/category"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
50
res/layout/mobile_bundled_apps_developer_fragment_row.xml
Normal file
50
res/layout/mobile_bundled_apps_developer_fragment_row.xml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="10dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mba_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItemSecondary"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/developer_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/developer_relationship"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/developer_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/developer_country"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
@@ -11907,4 +11907,26 @@
|
|||||||
<!-- [CHAR LIMIT=NONE] Title for Accessibility Software Cursor setting for keyboard shift. -->
|
<!-- [CHAR LIMIT=NONE] Title for Accessibility Software Cursor setting for keyboard shift. -->
|
||||||
<string name="software_cursor_trigger_keyboard_shift_enabled_title" translatable="false">Shift gesture detection region above keyboard</string>
|
<string name="software_cursor_trigger_keyboard_shift_enabled_title" translatable="false">Shift gesture detection region above keyboard</string>
|
||||||
|
|
||||||
|
<!-- Mobile Bundled Apps Transparency Metadata-->
|
||||||
|
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Label for mobile bundled apps screen -->
|
||||||
|
<string name="mobile_bundled_apps">Mobile bundled apps</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Title for mobile bundled apps screen -->
|
||||||
|
<string name="mobile_bundled_apps_title">Mobile bundled apps</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Title for mobile bundled apps transparency information details screen -->
|
||||||
|
<string name="mobile_bundled_apps_details_title">Mobile bundled apps transparency info</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
|
||||||
|
<string name="mobile_bundled_apps_details_contains_ad_title">Contains ads</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
|
||||||
|
<string name="mobile_bundled_apps_details_developer_info_title">Developer(s) Information</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
|
||||||
|
<string name="mobile_bundled_apps_details_contact_title">Contact Information</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
|
||||||
|
<string name="mobile_bundled_apps_details_description_title">Description</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
|
||||||
|
<string name="mobile_bundled_apps_details_privacy_policy_title">Privacy Policy</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
|
||||||
|
<string name="mobile_bundled_apps_details_category_title">Category</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Summary for App Details in App Info page if app is mobile bundled app -->
|
||||||
|
<string name="app_install_details_mba_summary">Mobile Bundled App</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
26
res/xml/mobile_bundled_apps_details_preference.xml
Normal file
26
res/xml/mobile_bundled_apps_details_preference.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<PreferenceScreen
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:title="">
|
||||||
|
<com.android.settingslib.widget.LayoutPreference
|
||||||
|
android:key="metadata"
|
||||||
|
android:selectable="false"
|
||||||
|
android:layout="@layout/mobile_bundled_apps_details_fragment" />
|
||||||
|
</PreferenceScreen>
|
@@ -56,6 +56,7 @@ import com.android.settings.R;
|
|||||||
import com.android.settings.SettingsActivity;
|
import com.android.settings.SettingsActivity;
|
||||||
import com.android.settings.SettingsPreferenceFragment;
|
import com.android.settings.SettingsPreferenceFragment;
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications;
|
import com.android.settings.applications.manageapplications.ManageApplications;
|
||||||
|
import com.android.settings.applications.mobilebundledapps.ApplicationMetadataUtils;
|
||||||
import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesDetailsPreferenceController;
|
import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesDetailsPreferenceController;
|
||||||
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetailPreferenceController;
|
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetailPreferenceController;
|
||||||
import com.android.settings.core.SubSettingLauncher;
|
import com.android.settings.core.SubSettingLauncher;
|
||||||
@@ -162,6 +163,8 @@ public class AppInfoDashboardFragment extends DashboardFragment
|
|||||||
use(AppInstallerInfoPreferenceController.class);
|
use(AppInstallerInfoPreferenceController.class);
|
||||||
installer.setPackageName(packageName);
|
installer.setPackageName(packageName);
|
||||||
installer.setParentFragment(this);
|
installer.setParentFragment(this);
|
||||||
|
installer.setMbaWithMetadataStatus(ApplicationMetadataUtils.getDefaultInstance(),
|
||||||
|
packageName);
|
||||||
use(AppInstallerPreferenceCategoryController.class).setChildren(Arrays.asList(installer));
|
use(AppInstallerPreferenceCategoryController.class).setChildren(Arrays.asList(installer));
|
||||||
use(AppNotificationPreferenceController.class).setParentFragment(this);
|
use(AppNotificationPreferenceController.class).setParentFragment(this);
|
||||||
|
|
||||||
|
@@ -16,42 +16,50 @@
|
|||||||
|
|
||||||
package com.android.settings.applications.appinfo;
|
package com.android.settings.applications.appinfo;
|
||||||
|
|
||||||
|
import static com.android.settings.applications.mobilebundledapps.MobileBundledAppDetailsActivity.ACTION_TRANSPARENCY_METADATA;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
|
import android.provider.DeviceConfig;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.Utils;
|
import com.android.settings.Utils;
|
||||||
import com.android.settings.applications.AppStoreUtil;
|
import com.android.settings.applications.AppStoreUtil;
|
||||||
|
import com.android.settings.applications.mobilebundledapps.ApplicationMetadataUtils;
|
||||||
import com.android.settingslib.applications.AppUtils;
|
import com.android.settingslib.applications.AppUtils;
|
||||||
|
|
||||||
public class AppInstallerInfoPreferenceController extends AppInfoPreferenceControllerBase {
|
public class AppInstallerInfoPreferenceController extends AppInfoPreferenceControllerBase {
|
||||||
|
|
||||||
|
private static final String KEY_ENABLE_PROMPT = "enable_prompt";
|
||||||
private String mPackageName;
|
private String mPackageName;
|
||||||
private String mInstallerPackage;
|
private String mInstallerPackage;
|
||||||
private CharSequence mInstallerLabel;
|
private CharSequence mInstallerLabel;
|
||||||
|
private Boolean mAppIsMbaWithMetadata;
|
||||||
|
private Boolean mEnableMbaUiFlag = false;
|
||||||
|
|
||||||
public AppInstallerInfoPreferenceController(Context context, String key) {
|
public AppInstallerInfoPreferenceController(Context context, String key) {
|
||||||
super(context, key);
|
super(context, key);
|
||||||
|
updateFromDeviceConfigFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAvailabilityStatus() {
|
public int getAvailabilityStatus() {
|
||||||
if (UserManager.get(mContext).isManagedProfile()) {
|
if (UserManager.get(mContext).isManagedProfile()
|
||||||
|
|| AppUtils.isMainlineModule(mContext.getPackageManager(), mPackageName)) {
|
||||||
return DISABLED_FOR_USER;
|
return DISABLED_FOR_USER;
|
||||||
}
|
}
|
||||||
|
if (mInstallerLabel != null || (mAppIsMbaWithMetadata && mEnableMbaUiFlag)) {
|
||||||
if (AppUtils.isMainlineModule(mContext.getPackageManager(), mPackageName)) {
|
return AVAILABLE;
|
||||||
return DISABLED_FOR_USER;
|
|
||||||
}
|
}
|
||||||
|
return DISABLED_FOR_USER;
|
||||||
return mInstallerLabel != null ? AVAILABLE : DISABLED_FOR_USER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateState(Preference preference) {
|
public void updateState(final Preference preference) {
|
||||||
final int detailsStringId = AppUtils.isInstant(mParent.getPackageInfo().applicationInfo)
|
final int detailsStringId = AppUtils.isInstant(mParent.getPackageInfo().applicationInfo)
|
||||||
? R.string.instant_app_details_summary
|
? R.string.instant_app_details_summary
|
||||||
: R.string.app_install_details_summary;
|
: R.string.app_install_details_summary;
|
||||||
@@ -60,14 +68,52 @@ public class AppInstallerInfoPreferenceController extends AppInfoPreferenceContr
|
|||||||
Intent intent = AppStoreUtil.getAppStoreLink(mContext, mInstallerPackage, mPackageName);
|
Intent intent = AppStoreUtil.getAppStoreLink(mContext, mInstallerPackage, mPackageName);
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
preference.setIntent(intent);
|
preference.setIntent(intent);
|
||||||
|
} else if (mAppIsMbaWithMetadata && mEnableMbaUiFlag) {
|
||||||
|
preference.setIntent(generateMetadataXmlViewerIntent());
|
||||||
|
preference.setSummary(mContext.getString(R.string.app_install_details_mba_summary));
|
||||||
} else {
|
} else {
|
||||||
preference.setEnabled(false);
|
preference.setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPackageName(String packageName) {
|
/**
|
||||||
|
* Sets the packageName in context for the controller.
|
||||||
|
*/
|
||||||
|
public void setPackageName(final String packageName) {
|
||||||
mPackageName = packageName;
|
mPackageName = packageName;
|
||||||
mInstallerPackage = AppStoreUtil.getInstallerPackageName(mContext, mPackageName);
|
mInstallerPackage = AppStoreUtil.getInstallerPackageName(mContext, mPackageName);
|
||||||
mInstallerLabel = Utils.getApplicationLabel(mContext, mInstallerPackage);
|
mInstallerLabel = Utils.getApplicationLabel(mContext, mInstallerPackage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setups and determines if the current package in context is an mobile-bundled-app with
|
||||||
|
* an application metadata file embedded within.
|
||||||
|
*/
|
||||||
|
public void setMbaWithMetadataStatus(final ApplicationMetadataUtils appMetadataUtils,
|
||||||
|
final String packageName) {
|
||||||
|
mAppIsMbaWithMetadata = appMetadataUtils.packageContainsXmlFile(
|
||||||
|
mContext.getPackageManager(), packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent generateMetadataXmlViewerIntent() {
|
||||||
|
final Intent metadataXmlIntent = new Intent(ACTION_TRANSPARENCY_METADATA)
|
||||||
|
.setPackage(mContext.getPackageName());
|
||||||
|
metadataXmlIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName);
|
||||||
|
return metadataXmlIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFromDeviceConfigFlags() {
|
||||||
|
String enablePromptFlag = DeviceConfig.getProperty(
|
||||||
|
DeviceConfig.NAMESPACE_TRANSPARENCY_METADATA,
|
||||||
|
KEY_ENABLE_PROMPT);
|
||||||
|
//No-op for empty field and relies on default value of false
|
||||||
|
if (!TextUtils.isEmpty(enablePromptFlag)) {
|
||||||
|
setEnableMbaFlag(Boolean.parseBoolean(enablePromptFlag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setEnableMbaFlag(final boolean flagValue) {
|
||||||
|
mEnableMbaUiFlag = flagValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.applications.mobilebundledapps;
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for parsing application-metadata.xml and return relevant fields
|
||||||
|
*/
|
||||||
|
public class ApplicationMetadataUtils {
|
||||||
|
private static final String TAG = ApplicationMetadataUtils.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final ApplicationMetadataUtils DEFAULT_INSTANCE = new ApplicationMetadataUtils();
|
||||||
|
private static final String TRANSPARENCY_XML_DIR = "APP-INF/application-metadata.xml";
|
||||||
|
private static final String DESCRIPTION_TAG = "description";
|
||||||
|
private static final String CONTAINS_ADS_TAG = "contains-ads";
|
||||||
|
private static final String PRIVACY_POLICY_TAG = "privacy-policy";
|
||||||
|
private static final String CONTACT_TAG = "contact";
|
||||||
|
private static final String CATEGORY_TAG = "category";
|
||||||
|
private static final String DEVELOPER_TAG = "developer";
|
||||||
|
private static final String URL_TAG = "url";
|
||||||
|
private static final String EMAIL_TAG = "email";
|
||||||
|
private static final String NAME_TAG = "name";
|
||||||
|
private static final String RELATIONSHIP_TAG = "relationship";
|
||||||
|
private static final String COUNTRY_TAG = "country";
|
||||||
|
|
||||||
|
private final PackageManager mPackageManager;
|
||||||
|
|
||||||
|
private Document mXmlDoc;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
ApplicationMetadataUtils() {
|
||||||
|
mPackageManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Need to create singleton factory as Android is unable to mock static for testing.
|
||||||
|
public static ApplicationMetadataUtils getDefaultInstance() {
|
||||||
|
return DEFAULT_INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new instance that also provisions and reads the XML file.
|
||||||
|
*/
|
||||||
|
public static ApplicationMetadataUtils newInstance(final PackageManager packageManager,
|
||||||
|
String packageName) {
|
||||||
|
return new ApplicationMetadataUtils(packageManager, packageName);
|
||||||
|
}
|
||||||
|
private ApplicationMetadataUtils(final PackageManager packageManager,
|
||||||
|
final String packageName) {
|
||||||
|
mPackageManager = packageManager;
|
||||||
|
try (ZipFile apk = new ZipFile(getApkDirectory(packageName, mPackageManager))) {
|
||||||
|
mXmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||||
|
.parse(apk.getInputStream(apk.getEntry(TRANSPARENCY_XML_DIR)));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setXmlDoc(final Document xmlDoc) {
|
||||||
|
mXmlDoc = xmlDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getApkDirectory(final String packageName,
|
||||||
|
final PackageManager packageManager)
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
|
return packageManager
|
||||||
|
.getApplicationInfo(packageName,
|
||||||
|
PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA))
|
||||||
|
.sourceDir;
|
||||||
|
}
|
||||||
|
public boolean getContainsAds() {
|
||||||
|
return mXmlDoc != null
|
||||||
|
&& mXmlDoc.getElementsByTagName(CONTAINS_ADS_TAG) != null
|
||||||
|
&& mXmlDoc.getElementsByTagName(CONTAINS_ADS_TAG).getLength() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrivacyPolicyUrl() {
|
||||||
|
return retrieveElementAttributeValue(PRIVACY_POLICY_TAG, URL_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String retrieveElementAttributeValue(final String elementTag, final String attribute) {
|
||||||
|
try {
|
||||||
|
return mXmlDoc.getElementsByTagName(elementTag).item(0)
|
||||||
|
.getAttributes().getNamedItem(attribute).getNodeValue();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return retrieveElementValue(DESCRIPTION_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String retrieveElementValue(final String elementTag) {
|
||||||
|
try {
|
||||||
|
return mXmlDoc.getElementsByTagName(elementTag).item(0).getTextContent();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryName() {
|
||||||
|
return retrieveElementAttributeValue(CATEGORY_TAG, NAME_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactUrl() {
|
||||||
|
return retrieveElementAttributeValue(CONTACT_TAG, URL_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactEmail() {
|
||||||
|
return retrieveElementAttributeValue(CONTACT_TAG, EMAIL_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPlayStoreUrl() {
|
||||||
|
return retrieveElementValue(DESCRIPTION_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the list of relevant major parties involved with this MBA
|
||||||
|
*/
|
||||||
|
public List<MbaDeveloper> getDevelopers() {
|
||||||
|
final List<MbaDeveloper> developersDetails = new ArrayList();
|
||||||
|
try {
|
||||||
|
final NodeList developers = mXmlDoc.getElementsByTagName(DEVELOPER_TAG);
|
||||||
|
if (developers == null) return developersDetails;
|
||||||
|
for (int i = 0; i < developers.getLength(); ++i) {
|
||||||
|
final NamedNodeMap developerAttributes = developers.item(i).getAttributes();
|
||||||
|
developersDetails.add(new MbaDeveloper(
|
||||||
|
developerAttributes.getNamedItem(NAME_TAG).getNodeValue(),
|
||||||
|
developerAttributes.getNamedItem(RELATIONSHIP_TAG).getNodeValue(),
|
||||||
|
developerAttributes.getNamedItem(EMAIL_TAG).getNodeValue(),
|
||||||
|
developerAttributes.getNamedItem(COUNTRY_TAG).getNodeValue()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
|
}
|
||||||
|
return developersDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the a package can be parsed and extrapolate metadata from.
|
||||||
|
*/
|
||||||
|
public boolean packageContainsXmlFile(final PackageManager packageManager,
|
||||||
|
final String packageName) {
|
||||||
|
try (ZipFile apk = new ZipFile(getApkDirectory(packageName, packageManager))) {
|
||||||
|
return apk.getEntry(TRANSPARENCY_XML_DIR) != null;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to return developer details
|
||||||
|
*/
|
||||||
|
public static class MbaDeveloper {
|
||||||
|
public final String name;
|
||||||
|
public final String relationship;
|
||||||
|
public final String email;
|
||||||
|
public final String country;
|
||||||
|
|
||||||
|
public MbaDeveloper(final String name,
|
||||||
|
final String relationship,
|
||||||
|
final String email,
|
||||||
|
final String country) {
|
||||||
|
this.name = name;
|
||||||
|
this.relationship = relationship;
|
||||||
|
this.email = email;
|
||||||
|
this.country = country;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.applications.mobilebundledapps;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.android.settings.SettingsActivity;
|
||||||
|
/**
|
||||||
|
* An activity that is used to parse and display mobile-bundled apps application metadata xml file.
|
||||||
|
*/
|
||||||
|
public class MobileBundledAppDetailsActivity extends SettingsActivity {
|
||||||
|
public static final String ACTION_TRANSPARENCY_METADATA =
|
||||||
|
"android.settings.TRANSPARENCY_METADATA";
|
||||||
|
|
||||||
|
public MobileBundledAppDetailsActivity() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getIntent() {
|
||||||
|
final Intent modIntent = new Intent(super.getIntent());
|
||||||
|
modIntent.setData(Uri.parse("package:"
|
||||||
|
+ super.getIntent().getExtra(Intent.EXTRA_PACKAGE_NAME).toString()));
|
||||||
|
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, MobileBundledAppsDetailsFragment.class.getName());
|
||||||
|
return modIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isValidFragment(final String fragmentName) {
|
||||||
|
return MobileBundledAppsDetailsFragment.class.getName().equals(fragmentName);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.applications.mobilebundledapps;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.applications.AppInfoWithHeader;
|
||||||
|
import com.android.settings.applications.mobilebundledapps.ApplicationMetadataUtils.MbaDeveloper;
|
||||||
|
import com.android.settingslib.applications.ApplicationsState;
|
||||||
|
import com.android.settingslib.widget.LayoutPreference;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fragment for retrieving the transparency metadata and PSL in the in-APK XML file and displaying
|
||||||
|
* them.
|
||||||
|
*/
|
||||||
|
public class MobileBundledAppsDetailsFragment extends AppInfoWithHeader {
|
||||||
|
private static final String METADATA_PREF_KEY = "metadata";
|
||||||
|
|
||||||
|
protected PackageManager mPackageManager;
|
||||||
|
private Context mContext;
|
||||||
|
private LayoutPreference mMetadataPreferenceView;
|
||||||
|
private ApplicationsState mApplicationState;
|
||||||
|
private boolean mCreated = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mContext = getActivity();
|
||||||
|
mPackageManager = mContext.getPackageManager();
|
||||||
|
addPreferencesFromResource(R.xml.mobile_bundled_apps_details_preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
if (mCreated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
final ApplicationMetadataUtils appUtil = ApplicationMetadataUtils.newInstance(
|
||||||
|
mPackageManager,
|
||||||
|
mPackageName);
|
||||||
|
if (mAppEntry == null) {
|
||||||
|
mApplicationState =
|
||||||
|
ApplicationsState.getInstance((Application) (mContext.getApplicationContext()));
|
||||||
|
mAppEntry = mApplicationState.getEntry(mPackageName, mContext.getUserId());
|
||||||
|
}
|
||||||
|
mMetadataPreferenceView = findPreference(METADATA_PREF_KEY);
|
||||||
|
createView(appUtil);
|
||||||
|
mCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createView(final ApplicationMetadataUtils appUtil) {
|
||||||
|
final LinearLayout devListLayout =
|
||||||
|
mMetadataPreferenceView.findViewById(R.id.developer_list);
|
||||||
|
populateDeveloperList(appUtil.getDevelopers(), devListLayout);
|
||||||
|
|
||||||
|
((TextView) mMetadataPreferenceView.findViewById(R.id.contains_ads))
|
||||||
|
.setText(Boolean.toString(appUtil.getContainsAds()));
|
||||||
|
|
||||||
|
((TextView) mMetadataPreferenceView.findViewById(R.id.contact_url))
|
||||||
|
.setText(appUtil.getContactUrl());
|
||||||
|
((TextView) mMetadataPreferenceView.findViewById(R.id.contact_email))
|
||||||
|
.setText(appUtil.getContactEmail());
|
||||||
|
|
||||||
|
((TextView) mMetadataPreferenceView.findViewById(R.id.privacy_policy_url))
|
||||||
|
.setText(appUtil.getPrivacyPolicyUrl());
|
||||||
|
|
||||||
|
((TextView) mMetadataPreferenceView.findViewById(R.id.description))
|
||||||
|
.setText(appUtil.getDescription());
|
||||||
|
|
||||||
|
((TextView) mMetadataPreferenceView.findViewById(R.id.category))
|
||||||
|
.setText(appUtil.getCategoryName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateDeveloperList(List<MbaDeveloper> developersDetails, ViewGroup parent) {
|
||||||
|
for (MbaDeveloper dev : developersDetails) {
|
||||||
|
View itemView = LayoutInflater.from(mContext)
|
||||||
|
.inflate(R.layout.mobile_bundled_apps_developer_fragment_row, parent, false);
|
||||||
|
|
||||||
|
((TextView) itemView.findViewById(R.id.developer_name)).setText(dev.name);
|
||||||
|
((TextView) itemView.findViewById(R.id.developer_relationship))
|
||||||
|
.setText(dev.relationship);
|
||||||
|
((TextView) itemView.findViewById(R.id.developer_email)).setText(dev.email);
|
||||||
|
((TextView) itemView.findViewById(R.id.developer_country)).setText(dev.country);
|
||||||
|
|
||||||
|
parent.addView(itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AlertDialog createDialog(int id, int errorCode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean refreshUi() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMetricsCategory() {
|
||||||
|
return SettingsEnums.TRANSPARENCY_METADATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.settings.applications.appinfo;
|
package com.android.settings.applications.appinfo;
|
||||||
|
|
||||||
|
import static com.android.settings.applications.mobilebundledapps.MobileBundledAppDetailsActivity.ACTION_TRANSPARENCY_METADATA;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
@@ -41,11 +43,14 @@ import android.os.UserManager;
|
|||||||
|
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
|
import com.android.settings.applications.mobilebundledapps.ApplicationMetadataUtils;
|
||||||
import com.android.settings.core.BasePreferenceController;
|
import com.android.settings.core.BasePreferenceController;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
@@ -53,7 +58,8 @@ import org.robolectric.RuntimeEnvironment;
|
|||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class AppInstallerInfoPreferenceControllerTest {
|
public class AppInstallerInfoPreferenceControllerTest {
|
||||||
|
private static final String TEST_PACKAGE_NAME = "Package1";
|
||||||
|
private static final String TEST_CONTEXT_KEY = "test_key";
|
||||||
@Mock
|
@Mock
|
||||||
private UserManager mUserManager;
|
private UserManager mUserManager;
|
||||||
@Mock
|
@Mock
|
||||||
@@ -67,11 +73,17 @@ public class AppInstallerInfoPreferenceControllerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private Preference mPreference;
|
private Preference mPreference;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ApplicationMetadataUtils mApplicationMetadataUtils;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
ArgumentCaptor<Intent> mIntentArgumentCaptor;
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private AppInstallerInfoPreferenceController mController;
|
private AppInstallerInfoPreferenceController mController;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws PackageManager.NameNotFoundException {
|
public void setup() throws PackageManager.NameNotFoundException {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mContext = spy(RuntimeEnvironment.application);
|
mContext = spy(RuntimeEnvironment.application);
|
||||||
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
|
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
|
||||||
@@ -81,9 +93,13 @@ public class AppInstallerInfoPreferenceControllerTest {
|
|||||||
when(mInstallSourceInfo.getInstallingPackageName()).thenReturn(installerPackage);
|
when(mInstallSourceInfo.getInstallingPackageName()).thenReturn(installerPackage);
|
||||||
when(mPackageManager.getApplicationInfo(eq(installerPackage), anyInt()))
|
when(mPackageManager.getApplicationInfo(eq(installerPackage), anyInt()))
|
||||||
.thenReturn(mAppInfo);
|
.thenReturn(mAppInfo);
|
||||||
mController = new AppInstallerInfoPreferenceController(mContext, "test_key");
|
mController = new AppInstallerInfoPreferenceController(mContext, TEST_CONTEXT_KEY);
|
||||||
mController.setPackageName("Package1");
|
|
||||||
mController.setParentFragment(mFragment);
|
mController.setParentFragment(mFragment);
|
||||||
|
mController.setPackageName(TEST_PACKAGE_NAME);
|
||||||
|
when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
|
||||||
|
.thenReturn(false);
|
||||||
|
mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
|
||||||
|
mController.setEnableMbaFlag(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -95,8 +111,42 @@ public class AppInstallerInfoPreferenceControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAvailabilityStatus_noAppLabel_shouldReturnDisabled() {
|
public void getAvailabilityStatus_noAppLabel_andNotMbaWithMetadata_shouldReturnDisabled()
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
when(mUserManager.isManagedProfile()).thenReturn(false);
|
when(mUserManager.isManagedProfile()).thenReturn(false);
|
||||||
|
mockMainlineModule(TEST_PACKAGE_NAME, false /* isMainlineModule */);
|
||||||
|
|
||||||
|
assertThat(mController.getAvailabilityStatus())
|
||||||
|
.isEqualTo(BasePreferenceController.DISABLED_FOR_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAvailabilityStatus_noAppLabel_andHaveMbaFile_shouldReturnAvailable()
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
|
mController = new AppInstallerInfoPreferenceController(mContext, TEST_CONTEXT_KEY);
|
||||||
|
mController.setPackageName(TEST_PACKAGE_NAME);
|
||||||
|
mController.setParentFragment(mFragment);
|
||||||
|
when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
|
||||||
|
.thenReturn(true);
|
||||||
|
mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
|
||||||
|
mockMainlineModule(TEST_PACKAGE_NAME, false /* isMainlineModule */);
|
||||||
|
|
||||||
|
assertThat(mController.getAvailabilityStatus())
|
||||||
|
.isEqualTo(BasePreferenceController.DISABLED_FOR_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAvailabilityStatus_noAppLabel_andMbaFeatureFlagDisabled_shouldReturnDisabled()
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
|
mController.setEnableMbaFlag(false);
|
||||||
|
when(mUserManager.isManagedProfile()).thenReturn(false);
|
||||||
|
mController = new AppInstallerInfoPreferenceController(mContext, TEST_CONTEXT_KEY);
|
||||||
|
mController.setPackageName(TEST_PACKAGE_NAME);
|
||||||
|
mController.setParentFragment(mFragment);
|
||||||
|
when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
|
||||||
|
.thenReturn(true);
|
||||||
|
mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
|
||||||
|
mockMainlineModule(TEST_PACKAGE_NAME, false /* isMainlineModule */);
|
||||||
|
|
||||||
assertThat(mController.getAvailabilityStatus())
|
assertThat(mController.getAvailabilityStatus())
|
||||||
.isEqualTo(BasePreferenceController.DISABLED_FOR_USER);
|
.isEqualTo(BasePreferenceController.DISABLED_FOR_USER);
|
||||||
@@ -105,13 +155,12 @@ public class AppInstallerInfoPreferenceControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getAvailabilityStatus_hasAppLabel_shouldReturnAvailable()
|
public void getAvailabilityStatus_hasAppLabel_shouldReturnAvailable()
|
||||||
throws PackageManager.NameNotFoundException {
|
throws PackageManager.NameNotFoundException {
|
||||||
final String packageName = "Package1";
|
|
||||||
when(mUserManager.isManagedProfile()).thenReturn(false);
|
when(mUserManager.isManagedProfile()).thenReturn(false);
|
||||||
when(mAppInfo.loadLabel(mPackageManager)).thenReturn("Label1");
|
when(mAppInfo.loadLabel(mPackageManager)).thenReturn("Label1");
|
||||||
mController = new AppInstallerInfoPreferenceController(mContext, "test_key");
|
mController = new AppInstallerInfoPreferenceController(mContext, TEST_CONTEXT_KEY);
|
||||||
mController.setPackageName(packageName);
|
mController.setPackageName(TEST_PACKAGE_NAME);
|
||||||
mController.setParentFragment(mFragment);
|
mController.setParentFragment(mFragment);
|
||||||
mockMainlineModule(packageName, false /* isMainlineModule */);
|
mockMainlineModule(TEST_PACKAGE_NAME, false /* isMainlineModule */);
|
||||||
|
|
||||||
assertThat(mController.getAvailabilityStatus())
|
assertThat(mController.getAvailabilityStatus())
|
||||||
.isEqualTo(BasePreferenceController.AVAILABLE);
|
.isEqualTo(BasePreferenceController.AVAILABLE);
|
||||||
@@ -129,7 +178,7 @@ public class AppInstallerInfoPreferenceControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateState_noAppStoreLink_shouldDisablePreference() {
|
public void updateState_noAppStoreLink_andNotMbaWithMetadata_shouldDisablePreference() {
|
||||||
final PackageInfo packageInfo = mock(PackageInfo.class);
|
final PackageInfo packageInfo = mock(PackageInfo.class);
|
||||||
packageInfo.applicationInfo = mAppInfo;
|
packageInfo.applicationInfo = mAppInfo;
|
||||||
when(mFragment.getPackageInfo()).thenReturn(packageInfo);
|
when(mFragment.getPackageInfo()).thenReturn(packageInfo);
|
||||||
@@ -139,6 +188,39 @@ public class AppInstallerInfoPreferenceControllerTest {
|
|||||||
|
|
||||||
verify(mPreference).setEnabled(false);
|
verify(mPreference).setEnabled(false);
|
||||||
}
|
}
|
||||||
|
@Test
|
||||||
|
public void updateState_noAppStoreLink_andMbaFeatureFlagDisabled_shouldDisablePreference() {
|
||||||
|
mController.setEnableMbaFlag(false);
|
||||||
|
when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
|
||||||
|
.thenReturn(true);
|
||||||
|
mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
|
||||||
|
final PackageInfo packageInfo = mock(PackageInfo.class);
|
||||||
|
packageInfo.applicationInfo = mAppInfo;
|
||||||
|
when(mFragment.getPackageInfo()).thenReturn(packageInfo);
|
||||||
|
when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(null);
|
||||||
|
|
||||||
|
mController.updateState(mPreference);
|
||||||
|
|
||||||
|
verify(mPreference).setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateState_noAppStoreLink_andMbaWithMetadata_shouldSetPreferenceIntent() {
|
||||||
|
when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
|
||||||
|
.thenReturn(true);
|
||||||
|
mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
|
||||||
|
final PackageInfo packageInfo = mock(PackageInfo.class);
|
||||||
|
packageInfo.applicationInfo = mAppInfo;
|
||||||
|
when(mFragment.getPackageInfo()).thenReturn(packageInfo);
|
||||||
|
when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(null);
|
||||||
|
|
||||||
|
mController.updateState(mPreference);
|
||||||
|
|
||||||
|
verify(mPreference, never()).setEnabled(false);
|
||||||
|
verify(mPreference).setIntent(mIntentArgumentCaptor.capture());
|
||||||
|
assertThat(mIntentArgumentCaptor.getValue().getAction())
|
||||||
|
.isEqualTo(ACTION_TRANSPARENCY_METADATA);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateState_hasAppStoreLink_shouldSetPreferenceIntent() {
|
public void updateState_hasAppStoreLink_shouldSetPreferenceIntent() {
|
||||||
@@ -154,7 +236,9 @@ public class AppInstallerInfoPreferenceControllerTest {
|
|||||||
mController.updateState(mPreference);
|
mController.updateState(mPreference);
|
||||||
|
|
||||||
verify(mPreference, never()).setEnabled(false);
|
verify(mPreference, never()).setEnabled(false);
|
||||||
verify(mPreference).setIntent(any(Intent.class));
|
verify(mPreference).setIntent(mIntentArgumentCaptor.capture());
|
||||||
|
assertThat(mIntentArgumentCaptor.getValue().getAction())
|
||||||
|
.isEqualTo(Intent.ACTION_SHOW_APP_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.applications.mobilebundledapps;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
|
||||||
|
import com.google.common.io.CharSource;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class ApplicationMetadataUtilsTest {
|
||||||
|
private static final String TEST_PACKAGE_NAME = "test";
|
||||||
|
private static final String TEST_SOURCE_DIR = "sourcedir";
|
||||||
|
|
||||||
|
private static final String TEST_XML_SCHEMA = "<transparency-info>\n"
|
||||||
|
+ " <template/>\n"
|
||||||
|
+ " <contains-ads/>\n"
|
||||||
|
+ " <developers>\n"
|
||||||
|
+ " <developer name=\"Example ODM\" relationship=\"ODM\" email=\"odm@example.com\""
|
||||||
|
+ " \n"
|
||||||
|
+ " website=\"http://odm.example.com\" country=\"US\"/>\n"
|
||||||
|
+ " <developer name=\"Example carrier\" relationship=\"CARRIER\" "
|
||||||
|
+ "email=\"carrier@example.com\" \n"
|
||||||
|
+ " country=\"US\"/>\n"
|
||||||
|
+ " </developers>\n"
|
||||||
|
+ " <contact url=\"http://example.com/contact-us\" email=\"contact@example.com\"/>\n"
|
||||||
|
+ " <privacy-policy url=\"https://www.example.com/privacy-policy.html\"/>\n"
|
||||||
|
+ " <description>This application provides the user with news "
|
||||||
|
+ "headlines</description>\n"
|
||||||
|
+ " <category name=\"News and magazines\"/>\n"
|
||||||
|
+ "</transparency-info>";
|
||||||
|
@Mock
|
||||||
|
private PackageManager mPackageManager;
|
||||||
|
|
||||||
|
private Document mDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||||
|
.parse(CharSource.wrap(TEST_XML_SCHEMA).asByteSource(StandardCharsets.UTF_8)
|
||||||
|
.openStream());
|
||||||
|
|
||||||
|
|
||||||
|
public ApplicationMetadataUtilsTest()
|
||||||
|
throws IOException, ParserConfigurationException, SAXException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup()
|
||||||
|
throws PackageManager.NameNotFoundException, IOException, ParserConfigurationException,
|
||||||
|
SAXException {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
final ApplicationInfo appInfo = new ApplicationInfo();
|
||||||
|
appInfo.sourceDir = TEST_SOURCE_DIR;
|
||||||
|
when(mPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME),
|
||||||
|
any(PackageManager.ApplicationInfoFlags.class))).thenReturn(appInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDefaultInstance_alwaysReturnSameInstance() {
|
||||||
|
final ApplicationMetadataUtils firstInstance =
|
||||||
|
ApplicationMetadataUtils.getDefaultInstance();
|
||||||
|
|
||||||
|
assertThat(firstInstance).isEqualTo(ApplicationMetadataUtils.getDefaultInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RuntimeException.class)
|
||||||
|
public void createInstance_bubblesUpException() throws PackageManager.NameNotFoundException {
|
||||||
|
final String testErrorMsg = "test";
|
||||||
|
when(mPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME),
|
||||||
|
any(PackageManager.ApplicationInfoFlags.class)))
|
||||||
|
.thenThrow(new Exception(testErrorMsg));
|
||||||
|
|
||||||
|
ApplicationMetadataUtils.newInstance(mPackageManager, TEST_PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fieldGetters_toReturnNull_whenEmptyOrError() {
|
||||||
|
final ApplicationMetadataUtils appUtils = new ApplicationMetadataUtils();
|
||||||
|
assertThat(appUtils.getContainsAds()).isEqualTo(false);
|
||||||
|
assertThat(appUtils.getCategoryName()).isNull();
|
||||||
|
assertThat(appUtils.getPrivacyPolicyUrl()).isNull();
|
||||||
|
assertThat(appUtils.getDescription()).isNull();
|
||||||
|
assertThat(appUtils.getDevelopers()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fieldGetters_toReturnCorrectValues_whenExists() {
|
||||||
|
final ApplicationMetadataUtils appUtils = new ApplicationMetadataUtils();
|
||||||
|
appUtils.setXmlDoc(mDocument);
|
||||||
|
assertThat(appUtils.getContainsAds()).isEqualTo(true);
|
||||||
|
assertThat(appUtils.getCategoryName()).isEqualTo("News and magazines");
|
||||||
|
assertThat(appUtils.getPrivacyPolicyUrl())
|
||||||
|
.isEqualTo("https://www.example.com/privacy-policy.html");
|
||||||
|
assertThat(appUtils.getDescription())
|
||||||
|
.isEqualTo("This application provides the user with news headlines");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDevelopers_returnsCorrectValues() {
|
||||||
|
final ApplicationMetadataUtils appUtils = new ApplicationMetadataUtils();
|
||||||
|
appUtils.setXmlDoc(mDocument);
|
||||||
|
final List<ApplicationMetadataUtils.MbaDeveloper> developers = appUtils.getDevelopers();
|
||||||
|
|
||||||
|
assertThat(developers.size()).isEqualTo(2);
|
||||||
|
assertThat(developers.get(0).country).isEqualTo("US");
|
||||||
|
assertThat(developers.get(0).email).isEqualTo("odm@example.com");
|
||||||
|
assertThat(developers.get(0).name).isEqualTo("Example ODM");
|
||||||
|
assertThat(developers.get(1).relationship).isEqualTo("CARRIER");
|
||||||
|
assertThat(developers.get(1).country).isEqualTo("US");
|
||||||
|
assertThat(developers.get(1).email).isEqualTo("carrier@example.com");
|
||||||
|
assertThat(developers.get(1).name).isEqualTo("Example carrier");
|
||||||
|
assertThat(developers.get(1).relationship).isEqualTo("CARRIER");
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user