Merge "Changes to installed app details screen for instant apps"

This commit is contained in:
TreeHugger Robot
2017-03-28 01:22:46 +00:00
committed by Android (Google) Code Review
9 changed files with 392 additions and 31 deletions

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/instant_app_button_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:visibility="gone">
<Button
android:id="@+id/install"
style="@style/AppActionPrimaryButton"
android:enabled="false"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/install_text"/>
<Button
android:id="@+id/clear_data"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/clear_instant_app_data"/>
</LinearLayout>

View File

@@ -8593,6 +8593,9 @@
<string name="storage_percent_full">full</string> <string name="storage_percent_full">full</string>
<!-- Label for button allow user to clear the data for an instant app -->
<string name="clear_instant_app_data">Clear app</string>
<!-- Title of games app storage screen [CHAR LIMIT=30] --> <!-- Title of games app storage screen [CHAR LIMIT=30] -->
<string name="game_storage_settings">Games</string> <string name="game_storage_settings">Games</string>

View File

@@ -23,11 +23,17 @@
android:selectable="false" android:selectable="false"
android:order="-10000"/> android:order="-10000"/>
<com.android.settings.applications.LayoutPreference
android:key="instant_app_buttons"
android:layout="@layout/instant_app_buttons"
android:selectable="false"
android:order="-9999"/>
<com.android.settings.applications.LayoutPreference <com.android.settings.applications.LayoutPreference
android:key="action_buttons" android:key="action_buttons"
android:layout="@layout/app_action_buttons" android:layout="@layout/app_action_buttons"
android:selectable="false" android:selectable="false"
android:order="-9999"/> android:order="-9998"/>
<Preference <Preference
android:key="notification_settings" android:key="notification_settings"

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.util.Log;
// This class provides methods that help dealing with app stores.
public class AppStoreUtil {
private static final String LOG_TAG = "AppStoreUtil";
private static Intent resolveIntent(Context context, Intent i) {
ResolveInfo result = context.getPackageManager().resolveActivity(i, 0);
return result != null ? new Intent(i.getAction())
.setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
}
// Returns the package name of the app which installed a given packageName, if one is
// available.
public static String getInstallerPackageName(Context context, String packageName) {
String installerPackageName = null;
try {
installerPackageName =
context.getPackageManager().getInstallerPackageName(packageName);
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e);
}
if (installerPackageName == null) {
return null;
}
return installerPackageName;
}
// Returns a link to the installer app store for a given package name.
public static Intent getAppStoreLink(Context context, String installerPackageName,
String packageName) {
Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO)
.setPackage(installerPackageName);
final Intent result = resolveIntent(context, intent);
if (result != null) {
result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
return result;
}
return null;
}
// Convenience method that looks up the installerPackageName for you.
public static Intent getAppStoreLink(Context context, String packageName) {
String installerPackageName = getInstallerPackageName(context, packageName);
return getAppStoreLink(context, installerPackageName, packageName);
}
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.applications; package com.android.settings.applications;
import com.android.settings.applications.instantapps.InstantAppButtonsController;
import android.app.Fragment; import android.app.Fragment;
import android.content.Intent; import android.content.Intent;
import android.view.View; import android.view.View;
@@ -29,6 +31,14 @@ public interface ApplicationFeatureProvider {
*/ */
AppHeaderController newAppHeaderController(Fragment fragment, View appHeader); AppHeaderController newAppHeaderController(Fragment fragment, View appHeader);
/**
*
* Returns a new {@link InstantAppButtonsController} instance for showing buttons
* only relevant to instant apps.
*/
InstantAppButtonsController newInstantAppButtonsController(Fragment fragment,
View view);
/** /**
* Calculates the total number of apps installed on the device via policy across all users * Calculates the total number of apps installed on the device via policy across all users
* and managed profiles. * and managed profiles.

View File

@@ -29,6 +29,7 @@ import android.os.UserManager;
import android.util.ArraySet; import android.util.ArraySet;
import android.view.View; import android.view.View;
import com.android.settings.applications.instantapps.InstantAppButtonsController;
import com.android.settings.enterprise.DevicePolicyManagerWrapper; import com.android.settings.enterprise.DevicePolicyManagerWrapper;
import java.util.List; import java.util.List;
@@ -56,6 +57,12 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide
return new AppHeaderController(mContext, fragment, appHeader); return new AppHeaderController(mContext, fragment, appHeader);
} }
@Override
public InstantAppButtonsController newInstantAppButtonsController(Fragment fragment,
View view) {
return new InstantAppButtonsController(mContext, fragment, view);
}
@Override @Override
public void calculateNumberOfPolicyInstalledApps(boolean async, NumberOfAppsCallback callback) { public void calculateNumberOfPolicyInstalledApps(boolean async, NumberOfAppsCallback callback) {
final AllUserPolicyInstalledAppCounter counter = final AllUserPolicyInstalledAppCounter counter =

View File

@@ -146,6 +146,7 @@ public class InstalledAppDetails extends AppInfoBase
private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3; private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3;
private static final String KEY_HEADER = "header_view"; private static final String KEY_HEADER = "header_view";
private static final String KEY_INSTANT_APP_BUTTONS = "instant_app_buttons";
private static final String KEY_ACTION_BUTTONS = "action_buttons"; private static final String KEY_ACTION_BUTTONS = "action_buttons";
private static final String KEY_NOTIFICATION = "notification_settings"; private static final String KEY_NOTIFICATION = "notification_settings";
private static final String KEY_STORAGE = "storage_settings"; private static final String KEY_STORAGE = "storage_settings";
@@ -222,13 +223,7 @@ public class InstalledAppDetails extends AppInfoBase
if (isBundled) { if (isBundled) {
enabled = handleDisableable(mUninstallButton); enabled = handleDisableable(mUninstallButton);
} else { } else {
if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0 enabled = initUnintsallButtonForUserApp();
&& mUserManager.getUsers().size() >= 2) {
// When we have multiple users, there is a separate menu
// to uninstall for all users.
enabled = false;
}
mUninstallButton.setText(R.string.uninstall_text);
} }
// If this is a device admin, it can't be uninstalled or disabled. // If this is a device admin, it can't be uninstalled or disabled.
// We do this here so the text of the button is still set correctly. // We do this here so the text of the button is still set correctly.
@@ -298,6 +293,22 @@ public class InstalledAppDetails extends AppInfoBase
} }
} }
@VisibleForTesting
boolean initUnintsallButtonForUserApp() {
boolean enabled = true;
if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0
&& mUserManager.getUsers().size() >= 2) {
// When we have multiple users, there is a separate menu
// to uninstall for all users.
enabled = false;
} else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
enabled = false;
mUninstallButton.setVisibility(View.GONE);
}
mUninstallButton.setText(R.string.uninstall_text);
return enabled;
}
/** Returns if the supplied package is device owner or profile owner of at least one user */ /** Returns if the supplied package is device owner or profile owner of at least one user */
private boolean isProfileOrDeviceOwner(String packageName) { private boolean isProfileOrDeviceOwner(String packageName) {
List<UserInfo> userInfos = mUserManager.getUsers(); List<UserInfo> userInfos = mUserManager.getUsers();
@@ -455,12 +466,6 @@ public class InstalledAppDetails extends AppInfoBase
mForceStopButton.setEnabled(false); mForceStopButton.setEnabled(false);
} }
private Intent resolveIntent(Intent i) {
ResolveInfo result = getContext().getPackageManager().resolveActivity(i, 0);
return result != null ? new Intent(i.getAction())
.setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset) menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset)
@@ -570,6 +575,8 @@ public class InstalledAppDetails extends AppInfoBase
} else if (PackageUtil.countPackageInUsers(mPm, mUserManager, mPackageName) < 2 } else if (PackageUtil.countPackageInUsers(mPm, mUserManager, mPackageName) < 2
&& (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { && (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
showIt = false; showIt = false;
} else if (AppUtils.isInstant(appEntry.info)) {
showIt = false;
} }
return showIt; return showIt;
} }
@@ -800,11 +807,15 @@ public class InstalledAppDetails extends AppInfoBase
} }
} }
private void checkForceStop() { @VisibleForTesting
void checkForceStop() {
if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
// User can't force stop device admin. // User can't force stop device admin.
Log.w(LOG_TAG, "User can't force stop device admin"); Log.w(LOG_TAG, "User can't force stop device admin");
updateForceStopButton(false); updateForceStopButton(false);
} else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
updateForceStopButton(false);
mForceStopButton.setVisibility(View.GONE);
} else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) { } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
// If the app isn't explicitly stopped, then always show the // If the app isn't explicitly stopped, then always show the
// force stop button. // force stop button.
@@ -1064,6 +1075,7 @@ public class InstalledAppDetails extends AppInfoBase
} }
addAppInstallerInfoPref(screen); addAppInstallerInfoPref(screen);
maybeAddInstantAppButtons();
} }
private boolean isPotentialAppSource() { private boolean isPotentialAppSource() {
@@ -1074,16 +1086,9 @@ public class InstalledAppDetails extends AppInfoBase
} }
private void addAppInstallerInfoPref(PreferenceScreen screen) { private void addAppInstallerInfoPref(PreferenceScreen screen) {
String installerPackageName = null; String installerPackageName =
try { AppStoreUtil.getInstallerPackageName(getContext(), mPackageName);
installerPackageName =
getContext().getPackageManager().getInstallerPackageName(mPackageName);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Exception while retrieving the package installer of " + mPackageName, e);
}
if (installerPackageName == null) {
return;
}
final CharSequence installerLabel = Utils.getApplicationLabel(getContext(), final CharSequence installerLabel = Utils.getApplicationLabel(getContext(),
installerPackageName); installerPackageName);
if (installerLabel == null) { if (installerLabel == null) {
@@ -1096,18 +1101,31 @@ public class InstalledAppDetails extends AppInfoBase
pref.setTitle(R.string.app_install_details_title); pref.setTitle(R.string.app_install_details_title);
pref.setKey("app_info_store"); pref.setKey("app_info_store");
pref.setSummary(getString(R.string.app_install_details_summary, installerLabel)); pref.setSummary(getString(R.string.app_install_details_summary, installerLabel));
final Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO)
.setPackage(installerPackageName); Intent intent =
final Intent result = resolveIntent(intent); AppStoreUtil.getAppStoreLink(getContext(), installerPackageName, mPackageName);
if (result != null) { if (intent != null) {
result.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName); pref.setIntent(intent);
pref.setIntent(result);
} else { } else {
pref.setEnabled(false); pref.setEnabled(false);
} }
category.addPreference(pref); category.addPreference(pref);
} }
@VisibleForTesting
void maybeAddInstantAppButtons() {
if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
LayoutPreference buttons = (LayoutPreference) findPreference(KEY_INSTANT_APP_BUTTONS);
final Activity activity = getActivity();
FeatureFactory.getFactory(activity)
.getApplicationFeatureProvider(activity)
.newInstantAppButtonsController(this,
buttons.findViewById(R.id.instant_app_button_container))
.setPackageName(mPackageName)
.show();
}
}
private boolean hasPermission(String permission) { private boolean hasPermission(String permission) {
if (mPackageInfo == null || mPackageInfo.requestedPermissions == null) { if (mPackageInfo == null || mPackageInfo.requestedPermissions == null) {
return false; return false;

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications.instantapps;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.applications.AppStoreUtil;
import com.android.settings.overlay.FeatureFactory;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/** Encapsulates a container for buttons relevant to instant apps */
public class InstantAppButtonsController {
private final Context mContext;
private final Fragment mFragment;
private final View mView;
private String mPackageName;
public InstantAppButtonsController(Context context, Fragment fragment, View view) {
mContext = context;
mFragment = fragment;
mView = view;
}
public InstantAppButtonsController setPackageName(String packageName) {
mPackageName = packageName;
return this;
}
public void bindButtons() {
Button installButton = (Button)mView.findViewById(R.id.install);
Button clearDataButton = (Button)mView.findViewById(R.id.clear_data);
Intent installIntent = AppStoreUtil.getAppStoreLink(mContext, mPackageName);
if (installIntent != null) {
installButton.setEnabled(true);
installButton.setOnClickListener(v -> mFragment.startActivity(installIntent));
}
clearDataButton.setOnClickListener(v -> {
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext,
MetricsEvent.ACTION_SETTINGS_CLEAR_INSTANT_APP, mPackageName);
PackageManager pm = mContext.getPackageManager();
pm.clearApplicationUserData(mPackageName, null);
});
}
public InstantAppButtonsController show() {
bindButtons();
mView.setVisibility(View.VISIBLE);
return this;
}
}

View File

@@ -23,11 +23,18 @@ import android.content.Context;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.os.UserManager; import android.os.UserManager;
import android.support.v7.preference.Preference;
import android.view.View;
import android.widget.Button;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.applications.instantapps.InstantAppButtonsController;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
import com.android.settingslib.applications.StorageStatsSource.AppStorageStats; import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
import org.junit.Before; import org.junit.Before;
@@ -41,6 +48,7 @@ import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
@@ -51,6 +59,11 @@ import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class InstalledAppDetailsTest { public final class InstalledAppDetailsTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@Mock
ApplicationFeatureProvider mApplicationFeatureProvider;
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private UserManager mUserManager; private UserManager mUserManager;
@@ -65,6 +78,10 @@ public final class InstalledAppDetailsTest {
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mAppDetail = new InstalledAppDetails(); mAppDetail = new InstalledAppDetails();
// Default to not considering any apps to be instant (individual tests can override this).
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
(InstantAppDataProvider) (i -> false));
} }
@Test @Test
@@ -136,4 +153,119 @@ public final class InstalledAppDetailsTest {
assertThat(mAppDetail.ensurePackageInfoAvailable(mActivity)).isTrue(); assertThat(mAppDetail.ensurePackageInfoAvailable(mActivity)).isTrue();
verify(mActivity, never()).finishAndRemoveTask(); verify(mActivity, never()).finishAndRemoveTask();
} }
// Tests that we don't show the "uninstall for all users" button for instant apps.
@Test
public void instantApps_noUninstallForAllButton() {
// Make this app appear to be instant.
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
(InstantAppDataProvider) (i -> true));
when(mDevicePolicyManager.packageHasActiveAdmins(anyString())).thenReturn(false);
when(mUserManager.getUsers().size()).thenReturn(2);
final ApplicationInfo info = new ApplicationInfo();
info.enabled = true;
final AppEntry appEntry = mock(AppEntry.class);
appEntry.info = info;
final PackageInfo packageInfo = mock(PackageInfo.class);
ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager);
ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager);
ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
assertThat(mAppDetail.shouldShowUninstallForAll(appEntry)).isFalse();
}
// Tests that we don't show the uninstall button for instant apps"
@Test
public void instantApps_noUninstallButton() {
// Make this app appear to be instant.
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
(InstantAppDataProvider) (i -> true));
final ApplicationInfo info = new ApplicationInfo();
info.flags = ApplicationInfo.FLAG_INSTALLED;
info.enabled = true;
final AppEntry appEntry = mock(AppEntry.class);
appEntry.info = info;
final PackageInfo packageInfo = mock(PackageInfo.class);
packageInfo.applicationInfo = info;
final Button uninstallButton = mock(Button.class);
ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager);
ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry);
ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
ReflectionHelpers.setField(mAppDetail, "mUninstallButton", uninstallButton);
mAppDetail.initUnintsallButtonForUserApp();
verify(uninstallButton).setVisibility(View.GONE);
}
// Tests that we don't show the force stop button for instant apps (they aren't allowed to run
// when they aren't in the foreground).
@Test
public void instantApps_noForceStop() {
// Make this app appear to be instant.
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
(InstantAppDataProvider) (i -> true));
final PackageInfo packageInfo = mock(PackageInfo.class);
final AppEntry appEntry = mock(AppEntry.class);
final ApplicationInfo info = new ApplicationInfo();
appEntry.info = info;
final Button forceStopButton = mock(Button.class);
ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager);
ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry);
ReflectionHelpers.setField(mAppDetail, "mForceStopButton", forceStopButton);
mAppDetail.checkForceStop();
verify(forceStopButton).setVisibility(View.GONE);
}
// A helper class for testing the InstantAppButtonsController - it lets us look up the
// preference associated with a key for instant app buttons and get back a mock
// LayoutPreference (to avoid a null pointer exception).
public static class InstalledAppDetailsWithMockInstantButtons extends InstalledAppDetails {
@Mock
private LayoutPreference mInstantButtons;
public InstalledAppDetailsWithMockInstantButtons() {
super();
MockitoAnnotations.initMocks(this);
}
@Override
public Preference findPreference(CharSequence key) {
if (key == "instant_app_buttons") {
return mInstantButtons;
}
return super.findPreference(key);
}
}
@Test
public void instantApps_instantSpecificButtons() {
// Make this app appear to be instant.
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
(InstantAppDataProvider) (i -> true));
final PackageInfo packageInfo = mock(PackageInfo.class);
final InstalledAppDetailsWithMockInstantButtons
fragment = new InstalledAppDetailsWithMockInstantButtons();
ReflectionHelpers.setField(fragment, "mPackageInfo", packageInfo);
final InstantAppButtonsController buttonsController =
mock(InstantAppButtonsController.class);
when(buttonsController.setPackageName(anyString())).thenReturn(buttonsController);
FakeFeatureFactory.setupForTest(mContext);
FakeFeatureFactory factory =
(FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
when(factory.applicationFeatureProvider.newInstantAppButtonsController(any(),
any())).thenReturn(buttonsController);
fragment.maybeAddInstantAppButtons();
verify(buttonsController).setPackageName(anyString());
verify(buttonsController).show();
}
} }