Merge "Remove app info page v1 related codes."
diff --git a/Android.mk b/Android.mk
index b9121ec..df85bbc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -37,8 +37,8 @@
ims-common
LOCAL_STATIC_JAVA_LIBRARIES := \
- apptoolkit-arch-core-runtime \
- apptoolkit-lifecycle-extensions \
+ android-arch-lifecycle-runtime \
+ android-arch-lifecycle-extensions \
jsr305 \
settings-logtags \
diff --git a/res/layout/suggestion_container.xml b/res/layout/suggestion_container.xml
index 9110c58..640a91f 100644
--- a/res/layout/suggestion_container.xml
+++ b/res/layout/suggestion_container.xml
@@ -55,7 +55,7 @@
android:id="@+id/suggestion_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingTop="18dp"
+ android:paddingTop="14dp"
android:paddingBottom="16dp"
android:scrollbars="none"/>
diff --git a/res/layout/suggestion_tile_v2.xml b/res/layout/suggestion_tile_v2.xml
index e04febb..a7717b7 100644
--- a/res/layout/suggestion_tile_v2.xml
+++ b/res/layout/suggestion_tile_v2.xml
@@ -18,7 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/suggestion_card"
- android:layout_width="328dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
@@ -65,7 +65,7 @@
android:layout_marginEnd="12dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.SuggestionTitleV2"
- android:ellipsize="marquee"
+ android:ellipsize="end"
android:fadingEdge="horizontal" />
<TextView
diff --git a/res/layout/suggestion_tile_with_button_v2.xml b/res/layout/suggestion_tile_with_button_v2.xml
index 5f4ed18..bedc6da 100644
--- a/res/layout/suggestion_tile_with_button_v2.xml
+++ b/res/layout/suggestion_tile_with_button_v2.xml
@@ -18,8 +18,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/suggestion_card"
- android:layout_width="328dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
app:cardElevation="2dp"
app:cardCornerRadius="@dimen/suggestion_card_corner_radius">
@@ -37,16 +38,17 @@
<ImageView
android:id="@android:id/icon"
- android:layout_width="@dimen/dashboard_tile_image_size"
- android:layout_height="@dimen/dashboard_tile_image_size"
+ android:layout_width="@dimen/suggestion_card_icon_size"
+ android:layout_height="@dimen/suggestion_card_icon_size"
style="@style/SuggestionCardIcon"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp" />
<ImageView
android:id="@+id/close_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:alpha="0.54"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
@@ -62,8 +64,8 @@
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:singleLine="true"
- android:textAppearance="@style/TextAppearance.TileTitle"
- android:ellipsize="marquee"
+ android:textAppearance="@style/TextAppearance.SuggestionTitleV2"
+ android:ellipsize="end"
android:fadingEdge="horizontal" />
<TextView
diff --git a/res/values-sw400dp/dimens.xml b/res/values-sw400dp/dimens.xml
index 4f13e09..8d45dd4 100755
--- a/res/values-sw400dp/dimens.xml
+++ b/res/values-sw400dp/dimens.xml
@@ -23,9 +23,9 @@
<dimen name="support_escalation_card_padding_end">56dp</dimen>
<!-- Suggestion cards-->
- <dimen name="suggestion_card_width_one_card">380dp</dimen>
- <dimen name="suggestion_card_width_two_cards">184dp</dimen>
- <dimen name="suggestion_card_width_multiple_cards">176dp</dimen>
+ <dimen name="suggestion_card_width_one_card">384dp</dimen>
+ <dimen name="suggestion_card_width_two_cards">188dp</dimen>
+ <dimen name="suggestion_card_width_multiple_cards">180dp</dimen>
<dimen name="suggestion_card_padding_bottom_one_card">22dp</dimen>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 332deea..f9ab821 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -304,9 +304,9 @@
<!-- Suggestion cards size and padding -->
<dimen name="suggestion_card_icon_size">24dp</dimen>
- <dimen name="suggestion_card_width_one_card">328dp</dimen>
- <dimen name="suggestion_card_width_two_cards">158dp</dimen>
- <dimen name="suggestion_card_width_multiple_cards">152dp</dimen>
+ <dimen name="suggestion_card_width_one_card">332dp</dimen>
+ <dimen name="suggestion_card_width_two_cards">162dp</dimen>
+ <dimen name="suggestion_card_width_multiple_cards">156dp</dimen>
<dimen name="suggestion_card_outer_margin">16dp</dimen>
<dimen name="suggestion_card_inner_margin">12dp</dimen>
<dimen name="suggestion_card_padding_bottom_one_card">16dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f917d81..fe32e9d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1133,8 +1133,12 @@
<!-- Title for suggested actions for settings up a fingerprint lock [CHAR LIMIT=34] -->
<string name="suggested_fingerprint_lock_settings_title">Unlock with fingerprint</string>
- <!-- Summary for suggested actions for settings up a fingerprint lock -->
- <string name="suggested_fingerprint_lock_settings_summary">Unlock with your fingerprint</string>
+ <!-- Summary for suggested actions for settings up a fingerprint lock (tablet) -->
+ <string name="suggested_fingerprint_lock_settings_summary" product="tablet">Unlock with your fingerprint</string>
+ <!-- Summary for suggested actions for settings up a fingerprint lock (device) -->
+ <string name="suggested_fingerprint_lock_settings_summary" product="device">Unlock with your fingerprint</string>
+ <!-- Summary for suggested actions for settings up a fingerprint lock (phone) -->
+ <string name="suggested_fingerprint_lock_settings_summary" product="default">Unlock with your fingerprint</string>
<!-- Title for security picker to choose the unlock method: None/Pattern/PIN/Password [CHAR LIMIT=22] -->
<string name="lock_settings_picker_title">Choose screen lock</string>
@@ -2579,6 +2583,7 @@
<!-- [CHAR LIMIT=30] Title of the preference that opens the Ambient display settings screen. -->
<string name="ambient_display_screen_title">Ambient display</string>
+
<!-- [CHAR LIMIT=50] Summary of the preference that opens the Ambient display settings screen, when Ambient display is set to be always on -->
<string name="ambient_display_screen_summary_always_on">Always on / Increased battery usage</string>
<!-- [CHAR LIMIT=30] Summary of the preference that opens the Ambient display settings screen, when Ambient display is set to show when new notifications come in. -->
@@ -6714,6 +6719,8 @@
<string name="keywords_sim_status">network, mobile network state, service state, signal strength, mobile network type, roaming, iccid</string>
<string name="keywords_model_and_hardware">serial number, hardware version</string>
<string name="keywords_android_version">android security patch level, baseband version, kernel version</string>
+ <!-- Search keyword for Ambient display settings screen. -->
+ <string name="keywords_ambient_display_screen">Ambient display, Lock screen display</string>
<!-- NFC Wi-Fi pairing/setup strings-->
diff --git a/res/xml/ambient_display_settings.xml b/res/xml/ambient_display_settings.xml
index a8ded0d..8bd9bd6 100644
--- a/res/xml/ambient_display_settings.xml
+++ b/res/xml/ambient_display_settings.xml
@@ -17,7 +17,9 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="ambient_display_preference_screen"
+ settings:keywords="@string/keywords_ambient_display_screen"
android:title="@string/ambient_display_screen_title">
<PreferenceCategory
diff --git a/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java b/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java
index 130138c..1a5a285 100644
--- a/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java
@@ -16,18 +16,14 @@
package com.android.settings.applications.appinfo;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -35,7 +31,6 @@
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.PreferenceScreen;
-import android.util.Log;
import android.webkit.IWebViewUpdateService;
import com.android.settings.R;
@@ -71,16 +66,6 @@
private UserManager mUserManager;
private PackageManager mPm;
- private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
- Log.d(TAG, "Got broadcast response: Restart status for "
- + mParent.getAppEntry().info.packageName + " " + enabled);
- updateForceStopButton(enabled);
- }
- };
-
public AppActionButtonPreferenceController(Context context, AppInfoDashboardFragment parent,
String packageName) {
super(context, KEY_ACTION_BUTTONS);
@@ -101,9 +86,7 @@
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mActionButtons = ((ActionButtonPreference) screen.findPreference(KEY_ACTION_BUTTONS))
- .setButton2Text(R.string.force_stop)
- .setButton2Positive(false)
- .setButton2Enabled(false);
+ .setButton2Visible(false);
}
@Override
@@ -140,7 +123,6 @@
}
}
- checkForceStop(appEntry, packageInfo);
initUninstallButtons(appEntry, packageInfo);
}
@@ -269,41 +251,6 @@
return disableable;
}
- private void updateForceStopButton(boolean enabled) {
- final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(
- mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId);
- mActionButtons
- .setButton2Enabled(disallowedBySystem ? false : enabled)
- .setButton2OnClickListener(
- disallowedBySystem ? null : v -> mParent.handleForceStopButtonClick());
- }
-
- void checkForceStop(AppEntry appEntry, PackageInfo packageInfo) {
- if (mDpm.packageHasActiveAdmins(packageInfo.packageName)) {
- // User can't force stop device admin.
- Log.w(TAG, "User can't force stop device admin");
- updateForceStopButton(false);
- } else if (AppUtils.isInstant(packageInfo.applicationInfo)) {
- updateForceStopButton(false);
- mActionButtons.setButton2Visible(false);
- } else if ((appEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
- // If the app isn't explicitly stopped, then always show the
- // force stop button.
- Log.w(TAG, "App is not explicitly stopped");
- updateForceStopButton(true);
- } else {
- final Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
- Uri.fromParts("package", appEntry.info.packageName, null));
- intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { appEntry.info.packageName });
- intent.putExtra(Intent.EXTRA_UID, appEntry.info.uid);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(appEntry.info.uid));
- Log.d(TAG, "Sending broadcast to query restart status for "
- + appEntry.info.packageName);
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
- mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
- }
- }
-
private boolean signaturesMatch(String pkg1, String pkg2) {
if (pkg1 != null && pkg2 != null) {
try {
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index f363473..a99ba65 100755
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -19,7 +19,6 @@
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -85,6 +84,7 @@
// Menu identifiers
private static final int UNINSTALL_ALL_USERS_MENU = 1;
private static final int UNINSTALL_UPDATES = 2;
+ static final int FORCE_STOP_MENU = 3;
// Result code identifiers
@VisibleForTesting
@@ -99,7 +99,7 @@
// Dialog identifiers used in showDialog
private static final int DLG_BASE = 0;
- private static final int DLG_FORCE_STOP = DLG_BASE + 1;
+ static final int DLG_FORCE_STOP = DLG_BASE + 1;
private static final int DLG_DISABLE = DLG_BASE + 2;
private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3;
@@ -137,6 +137,7 @@
private InstantAppButtonsPreferenceController mInstantAppButtonPreferenceController;
private AppActionButtonPreferenceController mAppActionButtonPreferenceController;
+ private ForceStopOptionsMenuController mForceStopOptionsMenuController;
/**
* Callback to invoke when app info has been changed.
@@ -168,6 +169,9 @@
return;
}
+ mForceStopOptionsMenuController =
+ new ForceStopOptionsMenuController(activity, this /* parent */, mDpm,
+ mMetricsFeatureProvider, getLifecycle());
setHasOptionsMenu(true);
}
@@ -264,6 +268,10 @@
return mAppEntry;
}
+ void setAppEntry(ApplicationsState.AppEntry appEntry) {
+ mAppEntry = appEntry;
+ }
+
PackageInfo getPackageInfo() {
if (mAppEntry == null) {
retrieveAppEntry();
@@ -271,6 +279,10 @@
return mPackageInfo;
}
+ ApplicationsState getAppState() {
+ return mState;
+ }
+
@Override
public void onPackageSizeChanged(String packageName) {
if (!TextUtils.equals(packageName, mPackageName)) {
@@ -311,6 +323,7 @@
if (mFinishing) {
return;
}
+ super.onPrepareOptionsMenu(menu);
menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry));
mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
final MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES);
@@ -331,7 +344,7 @@
uninstallPkg(mAppEntry.info.packageName, false, false);
return true;
}
- return false;
+ return super.onOptionsItemSelected(item);
}
@Override
@@ -461,18 +474,10 @@
})
.setNegativeButton(R.string.dlg_cancel, null)
.create();
- case DLG_FORCE_STOP:
- return new AlertDialog.Builder(getActivity())
- .setTitle(getActivity().getText(R.string.force_stop_dlg_title))
- .setMessage(getActivity().getText(R.string.force_stop_dlg_text))
- .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Force stop
- forceStopPackage(mAppEntry.info.packageName);
- }
- })
- .setNegativeButton(R.string.dlg_cancel, null)
- .create();
+ }
+ final AlertDialog dialog = mForceStopOptionsMenuController.createDialog(id);
+ if (dialog != null) {
+ return dialog;
}
return mInstantAppButtonPreferenceController.createDialog(id);
}
@@ -489,21 +494,6 @@
mDisableAfterUninstall = andDisable;
}
- private void forceStopPackage(String pkgName) {
- mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName);
- final ActivityManager am = (ActivityManager) getActivity().getSystemService(
- Context.ACTIVITY_SERVICE);
- Log.d(TAG, "Stopping package " + pkgName);
- am.forceStopPackage(pkgName);
- final int userId = UserHandle.getUserId(mAppEntry.info.uid);
- mState.invalidatePackage(pkgName, userId);
- final AppEntry newEnt = mState.getEntry(pkgName, userId);
- if (newEnt != null) {
- mAppEntry = newEnt;
- }
- mAppActionButtonPreferenceController.checkForceStop(mAppEntry, mPackageInfo);
- }
-
public static void startAppInfoFragment(Class<?> fragment, int title,
SettingsPreferenceFragment caller, AppEntry appEntry) {
// start new fragment to display extended information
@@ -564,20 +554,6 @@
}
}
- void handleForceStopButtonClick() {
- if (mAppEntry == null) {
- setIntentAndFinish(true, true);
- return;
- }
- if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
- RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
- getActivity(), mAppsControlDisallowedAdmin);
- } else {
- showDialogInner(DLG_FORCE_STOP, 0);
- //forceStopPackage(mAppInfo.packageName);
- }
- }
-
/** Returns whether there is only one user on this device, not including the system-only user */
private boolean isSingleUser() {
final int userCount = mUserManager.getUserCount();
@@ -675,7 +651,7 @@
}
}
- private void setIntentAndFinish(boolean finish, boolean appChanged) {
+ void setIntentAndFinish(boolean finish, boolean appChanged) {
if (localLOGV) Log.i(TAG, "appChanged="+appChanged);
final Intent intent = new Intent();
intent.putExtra(ManageApplications.APP_CHG, appChanged);
diff --git a/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.java b/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.java
new file mode 100644
index 0000000..cf87147
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuController.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2018 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.appinfo;
+
+import static com.android.settings.applications.appinfo.AppInfoDashboardFragment.FORCE_STOP_MENU;
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.wrapper.DevicePolicyManagerWrapper;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
+import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected;
+import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu;
+
+public class ForceStopOptionsMenuController implements LifecycleObserver, OnCreateOptionsMenu,
+ OnPrepareOptionsMenu, OnOptionsItemSelected {
+
+ private static final String TAG = "ForceStopMenuController";
+
+ private final Context mContext;
+ private final AppInfoDashboardFragment mParent;
+ private final DevicePolicyManagerWrapper mDpm;
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
+
+ private int mUserId;
+ private MenuItem mForceStopMenu;
+
+ private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
+ Log.d(TAG, "Got broadcast response: Restart status for "
+ + mParent.getAppEntry().info.packageName + " " + enabled);
+ enableForceStopMenu(enabled);
+ }
+ };
+
+ public ForceStopOptionsMenuController(Context context, AppInfoDashboardFragment parent,
+ DevicePolicyManagerWrapper devicePolicyManager,
+ MetricsFeatureProvider metricsFeatureProvider, Lifecycle lifecycle) {
+ mContext = context;
+ mParent = parent;
+ mDpm = devicePolicyManager;
+ mMetricsFeatureProvider = metricsFeatureProvider;
+ mUserId = UserHandle.myUserId();
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ menu.add(0, FORCE_STOP_MENU, 2, R.string.force_stop)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ if (menuItem.getItemId() == FORCE_STOP_MENU) {
+ handleForceStopMenuClick();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ mForceStopMenu = menu.findItem(FORCE_STOP_MENU);
+ updateForceStopMenu(mParent.getAppEntry(), mParent.getPackageInfo());
+ }
+
+ @VisibleForTesting
+ void updateForceStopMenu(AppEntry appEntry, PackageInfo packageInfo) {
+ boolean enabled = false;
+ if (mDpm.packageHasActiveAdmins(packageInfo.packageName)) {
+ // User can't force stop device admin.
+ Log.w(TAG, "User can't force stop device admin");
+ } else if (AppUtils.isInstant(packageInfo.applicationInfo)) {
+ // No force stop for instant app
+ if (mForceStopMenu != null) {
+ mForceStopMenu.setVisible(false);
+ }
+ } else if ((appEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
+ // If the app isn't explicitly stopped, then always show the
+ // force stop button.
+ Log.w(TAG, "App is not explicitly stopped");
+ enabled = true;
+ } else {
+ final Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
+ Uri.fromParts("package", appEntry.info.packageName, null));
+ intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { appEntry.info.packageName });
+ intent.putExtra(Intent.EXTRA_UID, appEntry.info.uid);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(appEntry.info.uid));
+ Log.d(TAG, "Sending broadcast to query restart status for "
+ + appEntry.info.packageName);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
+ mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
+ }
+ enableForceStopMenu(enabled);
+ }
+
+ private void enableForceStopMenu(boolean enabled) {
+ if (mForceStopMenu != null) {
+ final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(
+ mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId);
+ mForceStopMenu.setEnabled(disallowedBySystem ? false : enabled);
+ }
+ }
+
+ @VisibleForTesting
+ void handleForceStopMenuClick() {
+ if (mParent.getAppEntry() == null) {
+ mParent.setIntentAndFinish(true, true);
+ return;
+ }
+ final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
+ mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId);
+ final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(
+ mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId);
+ if (admin != null && !disallowedBySystem) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, admin);
+ } else {
+ mParent.showDialogInner(mParent.DLG_FORCE_STOP, 0);
+ }
+ }
+
+ private void forceStopPackage(String pkgName) {
+ mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_APP_FORCE_STOP, pkgName);
+ final ActivityManager am = (ActivityManager) mContext.getSystemService(
+ Context.ACTIVITY_SERVICE);
+ Log.d(TAG, "Stopping package " + pkgName);
+ am.forceStopPackage(pkgName);
+ final int userId = UserHandle.getUserId(mParent.getAppEntry().info.uid);
+ final ApplicationsState appState = mParent.getAppState();
+ appState.invalidatePackage(pkgName, userId);
+ final AppEntry newEnt = appState.getEntry(pkgName, userId);
+ if (newEnt != null) {
+ mParent.setAppEntry(newEnt);
+ }
+ }
+
+ public AlertDialog createDialog(int id) {
+ if (id != mParent.DLG_FORCE_STOP) {
+ return null;
+ }
+ return new AlertDialog.Builder(mContext)
+ .setTitle(mContext.getText(R.string.force_stop_dlg_title))
+ .setMessage(mContext.getText(R.string.force_stop_dlg_text))
+ .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Force stop
+ forceStopPackage(mParent.getAppEntry().info.packageName);
+ }
+ })
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
+ }
+
+}
diff --git a/src/com/android/settings/dashboard/DashboardAdapterV2.java b/src/com/android/settings/dashboard/DashboardAdapterV2.java
index 7cd4f38..f98b440 100644
--- a/src/com/android/settings/dashboard/DashboardAdapterV2.java
+++ b/src/com/android/settings/dashboard/DashboardAdapterV2.java
@@ -151,13 +151,10 @@
if (list.size() == 1) {
// The only suggestion is dismissed, and the the empty suggestion container will
// remain as the dashboard item. Need to refresh the dashboard list.
- final DashboardDataV2 prevData = mDashboardData;
- mDashboardData = new DashboardDataV2.Builder(prevData)
- .setSuggestions(null)
- .build();
- notifyDashboardDataChanged(prevData);
+ setSuggestions(null);
} else {
mSuggestionAdapter.removeSuggestion(suggestion);
+ notifyItemChanged(0, null);
}
}
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java
index 483af92..e29ca5f 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java
@@ -18,6 +18,8 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
@@ -34,6 +36,7 @@
import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder;
import com.android.settings.dashboard.DashboardAdapterV2.IconCache;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.Utils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -55,6 +58,7 @@
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final IconCache mCache;
private final ArrayList<String> mSuggestionsShownLogged;
+ private final SuggestionFeatureProvider mSuggestionFeatureProvider;
private final SuggestionControllerMixin mSuggestionControllerMixin;
private final Callback mCallback;
private final CardConfig mConfig;
@@ -75,6 +79,7 @@
mCache = new IconCache(context);
final FeatureFactory factory = FeatureFactory.getFactory(context);
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
+ mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
mCallback = callback;
if (savedInstanceState != null) {
mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
@@ -109,7 +114,12 @@
mSuggestionsShownLogged.add(id);
}
mConfig.setCardLayout(holder, suggestionCount, position);
- holder.icon.setImageDrawable(mCache.getIcon(suggestion.getIcon()));
+ final Icon icon = suggestion.getIcon();
+ final Drawable drawable = mCache.getIcon(icon);
+ if (drawable != null && TextUtils.equals(icon.getResPackage(), mContext.getPackageName())) {
+ drawable.setTint(Utils.getColorAccent(mContext));
+ }
+ holder.icon.setImageDrawable(drawable);
holder.title.setText(suggestion.getTitle());
holder.title.setSingleLine(suggestionCount == 1);
@@ -129,13 +139,13 @@
final ImageView closeButton = holder.itemView.findViewById(R.id.close_button);
if (closeButton != null) {
- if (mCallback != null) {
- closeButton.setOnClickListener(v -> {
+ closeButton.setOnClickListener(v -> {
+ mSuggestionFeatureProvider.dismissSuggestion(
+ mContext, mSuggestionControllerMixin, suggestion);
+ if (mCallback != null) {
mCallback.onSuggestionClosed(suggestion);
- });
- } else {
- closeButton.setOnClickListener(null);
- }
+ }
+ });
}
View clickHandler = holder.itemView;
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index e0954e5..2a841f9 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -93,14 +93,10 @@
private static final int MENU_STATS_TYPE = Menu.FIRST;
@VisibleForTesting
static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 3;
- @VisibleForTesting
- static final int MENU_TOGGLE_APPS = Menu.FIRST + 4;
private static final int MENU_HELP = Menu.FIRST + 5;
public static final int DEBUG_INFO_LOADER = 3;
@VisibleForTesting
- boolean mShowAllApps = false;
- @VisibleForTesting
PowerGaugePreference mScreenUsagePref;
@VisibleForTesting
PowerGaugePreference mLastFullChargePref;
@@ -221,7 +217,6 @@
mAnomalySparseArray = new SparseArray<>();
restartBatteryInfoLoader();
- restoreSavedInstance(icicle);
}
@Override
@@ -230,12 +225,6 @@
}
@Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(KEY_SHOW_ALL_APPS, mShowAllApps);
- }
-
- @Override
public boolean onPreferenceTreeClick(Preference preference) {
if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
performBatteryHeaderClick();
@@ -284,11 +273,6 @@
menu.add(Menu.NONE, MENU_HIGH_POWER_APPS, Menu.NONE, R.string.high_power_apps);
- if (mPowerFeatureProvider.isPowerAccountingToggleEnabled()) {
- menu.add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE,
- mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps);
- }
-
super.onCreateOptionsMenu(menu, inflater);
}
@@ -322,25 +306,11 @@
metricsFeatureProvider.action(context,
MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_OPTIMIZATION);
return true;
- case MENU_TOGGLE_APPS:
- mShowAllApps = !mShowAllApps;
- item.setTitle(mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps);
- metricsFeatureProvider.action(context,
- MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, mShowAllApps);
- restartBatteryStatsLoader(false /* clearHeader */);
- return true;
default:
return super.onOptionsItemSelected(item);
}
}
- @VisibleForTesting
- void restoreSavedInstance(Bundle savedInstance) {
- if (savedInstance != null) {
- mShowAllApps = savedInstance.getBoolean(KEY_SHOW_ALL_APPS, false);
- }
- }
-
private void performBatteryHeaderClick() {
if (mPowerFeatureProvider.isAdvancedUiEnabled()) {
Utils.startWithFragment(getContext(), PowerUsageAdvanced.class.getName(), null,
@@ -375,8 +345,8 @@
final CharSequence timeSequence = Utils.formatRelativeTime(context, lastFullChargeTime,
false);
- mBatteryAppListPreferenceController.refreshAppListGroup(mStatsHelper, mShowAllApps,
- timeSequence);
+ mBatteryAppListPreferenceController.refreshAppListGroup(mStatsHelper,
+ false /* showAllApps */, timeSequence);
}
@VisibleForTesting
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java
index 7d5eb31..70b9cc9 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java
@@ -18,27 +18,18 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
import android.os.UserManager;
import android.support.v7.preference.PreferenceScreen;
@@ -120,16 +111,14 @@
}
@Test
- public void displayPreference_shouldInitializeForceStopButton() {
+ public void displayPreference_shouldSetButton2Invisible() {
final PreferenceScreen screen = mock(PreferenceScreen.class);
final ActionButtonPreference preference = spy(new ActionButtonPreference(mContext));
when(screen.findPreference(mController.getPreferenceKey())).thenReturn(preference);
mController.displayPreference(screen);
- verify(preference).setButton2Positive(false);
- verify(preference).setButton2Text(R.string.force_stop);
- verify(preference).setButton2Enabled(false);
+ verify(preference).setButton2Visible(false);
}
@Test
@@ -138,14 +127,12 @@
final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
final ApplicationInfo info = new ApplicationInfo();
appEntry.info = info;
- doNothing().when(mController).checkForceStop(appEntry, packageInfo);
doNothing().when(mController).initUninstallButtons(appEntry, packageInfo);
when(mFragment.getAppEntry()).thenReturn(appEntry);
when(mFragment.getPackageInfo()).thenReturn(packageInfo);
mController.refreshUi();
- verify(mController).checkForceStop(appEntry, packageInfo);
verify(mController).initUninstallButtons(appEntry, packageInfo);
}
@@ -198,71 +185,6 @@
assertThat(mController.initUninstallButtonForUserApp()).isFalse();
}
- // 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 checkForceStop_instantApps_shouldNotShowForceStop() {
- // Make this app appear to be instant.
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> true));
- final PackageInfo packageInfo = mock(PackageInfo.class);
- final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
- final ApplicationInfo info = new ApplicationInfo();
- appEntry.info = info;
-
- mController.checkForceStop(appEntry, packageInfo);
-
- verify(mController.mActionButtons).setButton2Visible(false);
- }
-
- @Test
- public void checkForceStop_hasActiveAdmin_shouldDisableForceStop() {
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> false));
- final String packageName = "Package1";
- final PackageInfo packageInfo = new PackageInfo();
- packageInfo.packageName = packageName;
- final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
- when(mDevicePolicyManager.packageHasActiveAdmins(packageName)).thenReturn(true);
-
- mController.checkForceStop(appEntry, packageInfo);
-
- verify(mController.mActionButtons).setButton2Enabled(false);
- }
-
- @Test
- public void checkForceStop_appRunning_shouldEnableForceStop() {
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> false));
- final PackageInfo packageInfo = mock(PackageInfo.class);
- final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
- final ApplicationInfo info = new ApplicationInfo();
- appEntry.info = info;
-
- mController.checkForceStop(appEntry, packageInfo);
-
- verify(mController.mActionButtons).setButton2Enabled(true);
- }
-
- @Test
- public void checkForceStop_appStopped_shouldQueryPackageRestart() {
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> false));
- final PackageInfo packageInfo = mock(PackageInfo.class);
- final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
- final ApplicationInfo info = new ApplicationInfo();
- appEntry.info = info;
- info.flags = ApplicationInfo.FLAG_STOPPED;
- info.packageName = "com.android.setting";
-
- mController.checkForceStop(appEntry, packageInfo);
-
- verify(mContext).sendOrderedBroadcastAsUser(argThat(intent-> intent != null
- && intent.getAction().equals(Intent.ACTION_QUERY_PACKAGE_RESTART)),
- any(UserHandle.class), nullable(String.class), any(BroadcastReceiver.class),
- nullable(Handler.class), anyInt(), nullable(String.class), nullable(Bundle.class));
- }
-
@Test
public void handleDisableable_appIsHomeApp_buttonShouldNotWork() {
final ApplicationInfo info = new ApplicationInfo();
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java
index ee870b7..7108ef0 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceControllerTest.java
@@ -42,7 +42,9 @@
import com.android.settings.TestConfig;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
@@ -53,6 +55,7 @@
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -103,7 +106,8 @@
appEntry.info = info;
when(mFragment.getAppEntry()).thenReturn(appEntry);
when(mFragment.getPackageInfo()).thenReturn(packageInfo);
-
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
final TextView title = mHeader.findViewById(R.id.entity_header_title);
final TextView summary = mHeader.findViewById(R.id.entity_header_summary);
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuControllerTest.java
new file mode 100644
index 0000000..4719008
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/ForceStopOptionsMenuControllerTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2018 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.appinfo;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.wrapper.DevicePolicyManagerWrapper;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(
+ manifest = TestConfig.MANIFEST_PATH,
+ sdk = TestConfig.SDK_VERSION
+)
+public final class ForceStopOptionsMenuControllerTest {
+
+ private static final String PACKAGE_NAME = "test_package_name";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private UserManager mUserManager;
+ @Mock
+ private SettingsActivity mActivity;
+ @Mock
+ private DevicePolicyManagerWrapper mDevicePolicyManager;
+ @Mock
+ private PackageManager mPackageManager;
+
+ private AppInfoDashboardFragment mFragment;
+ private ForceStopOptionsMenuController mController;
+ private Context mShadowContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mShadowContext = spy(RuntimeEnvironment.application);
+ mFragment = spy(new AppInfoDashboardFragment());
+ ReflectionHelpers.setField(mFragment, "mDpm", mDevicePolicyManager);
+ ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
+ doReturn(mActivity).when(mFragment).getActivity();
+ doReturn(mShadowContext).when(mFragment).getContext();
+ doReturn(mPackageManager).when(mActivity).getPackageManager();
+ when(mShadowContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ mController = spy(new ForceStopOptionsMenuController(
+ mShadowContext, mFragment, mDevicePolicyManager,
+ null /* metricsFeatureProvider */, null /* lifecycle */));
+
+ // Default to not considering any apps to be instant (individual tests can override this).
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+ }
+
+ @Test
+ public void onCreateOptionsMenu_shouldAddForceStop() {
+ final Menu menu = mock(Menu.class);
+ when(menu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mock(MenuItem.class));
+
+ mController.onCreateOptionsMenu(menu, null /* inflater */);
+
+ verify(menu).add(anyInt(), eq(AppInfoDashboardFragment.FORCE_STOP_MENU), anyInt(),
+ eq(R.string.force_stop));
+ }
+
+ @Test
+ public void onPrepareOptionsMenu_shouldUpdateForceStopMenu() {
+ final Menu menu = mock(Menu.class);
+ doNothing().when(mController).updateForceStopMenu(any(), any());
+ doReturn(mock(AppEntry.class)).when(mFragment).getAppEntry();
+ doReturn(mock(PackageInfo.class)).when(mFragment).getPackageInfo();
+
+ mController.onPrepareOptionsMenu(menu);
+
+ verify(mController).updateForceStopMenu(any(), any());
+ }
+
+ @Test
+ public void onOptionsItemSelected_shouldHandleForceStopMenuClick() {
+ doReturn(mock(AppEntry.class)).when(mFragment).getAppEntry();
+ doNothing().when(mController).handleForceStopMenuClick();
+ final MenuItem menu = mock(MenuItem.class);
+ when(menu.getItemId()).thenReturn(AppInfoDashboardFragment.FORCE_STOP_MENU);
+
+ mController.onOptionsItemSelected(menu);
+
+ verify(mController).handleForceStopMenuClick();
+ }
+
+ // 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 updateForceStopMenu_instantApps_shouldNotShowForceStop() {
+ when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
+ final MenuItem forceStopMenu = mock(MenuItem.class);
+ ReflectionHelpers.setField(mController, "mForceStopMenu", forceStopMenu);
+ // 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;
+
+ mController.updateForceStopMenu(appEntry, packageInfo);
+
+ verify(forceStopMenu).setVisible(false);
+ }
+
+ @Test
+ public void updateForceStopMenu_hasActiveAdmin_shouldDisableForceStop() {
+ when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
+ final MenuItem forceStopMenu = mock(MenuItem.class);
+ ReflectionHelpers.setField(mController, "mForceStopMenu", forceStopMenu);
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+ final String packageName = "Package1";
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ final AppEntry appEntry = mock(AppEntry.class);
+ when(mDevicePolicyManager.packageHasActiveAdmins(packageName)).thenReturn(true);
+
+ mController.updateForceStopMenu(appEntry, packageInfo);
+
+ verify(forceStopMenu).setEnabled(false);
+ }
+
+ @Test
+ public void updateForceStopMenu_appRunning_shouldEnableForceStop() {
+ when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
+ final MenuItem forceStopMenu = mock(MenuItem.class);
+ ReflectionHelpers.setField(mController, "mForceStopMenu", forceStopMenu);
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ final AppEntry appEntry = mock(AppEntry.class);
+ final ApplicationInfo info = new ApplicationInfo();
+ appEntry.info = info;
+
+ mController.updateForceStopMenu(appEntry, packageInfo);
+
+ verify(forceStopMenu).setEnabled(true);
+ }
+
+ @Test
+ public void updateForceStopMenu_appStopped_shouldQueryPackageRestart() {
+ when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ final AppEntry appEntry = mock(AppEntry.class);
+ final ApplicationInfo info = new ApplicationInfo();
+ appEntry.info = info;
+ info.flags = ApplicationInfo.FLAG_STOPPED;
+ info.packageName = "com.android.setting";
+
+ mController.updateForceStopMenu(appEntry, packageInfo);
+
+ verify(mShadowContext).sendOrderedBroadcastAsUser(argThat(intent-> intent != null
+ && intent.getAction().equals(Intent.ACTION_QUERY_PACKAGE_RESTART)),
+ any(UserHandle.class), nullable(String.class), any(BroadcastReceiver.class),
+ nullable(Handler.class), anyInt(), nullable(String.class), nullable(Bundle.class));
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2Test.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2Test.java
index 1e76e2d..181c878 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2Test.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2Test.java
@@ -17,12 +17,19 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.service.settings.suggestions.Suggestion;
import android.view.LayoutInflater;
import android.view.View;
@@ -46,6 +53,7 @@
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@@ -200,7 +208,71 @@
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionHolder.itemView.findViewById(R.id.close_button).performClick();
- verify(callback).onSuggestionClosed(suggestions.get(0));
+ final Suggestion suggestion = suggestions.get(0);
+ verify(mFeatureFactory.suggestionsFeatureProvider).dismissSuggestion(
+ mActivity, mSuggestionControllerMixin, suggestion);
+ verify(callback).onSuggestionClosed(suggestion);
+ }
+
+ @Test
+ public void onBindViewHolder_differentPackage_shouldNotTintIcon()
+ throws PendingIntent.CanceledException {
+ final Icon icon = mock(Icon.class);
+ when(icon.getResPackage()).thenReturn("pkg1");
+ when(mActivity.getPackageName()).thenReturn("pkg2");
+ final Suggestion suggestion = new Suggestion.Builder("pkg1")
+ .setPendingIntent(mock(PendingIntent.class))
+ .setIcon(icon)
+ .build();
+ final List<Suggestion> suggestions = new ArrayList<>();
+ suggestions.add(suggestion);
+ mSuggestionAdapter = new SuggestionAdapterV2(mActivity, mSuggestionControllerMixin,
+ null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
+ mSuggestionAdapter.setSuggestions(suggestions);
+ mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
+ new FrameLayout(RuntimeEnvironment.application),
+ mSuggestionAdapter.getItemViewType(0));
+ DashboardAdapterV2.IconCache cache = mock(DashboardAdapterV2.IconCache.class);
+ final Drawable drawable = mock(Drawable.class);
+ when(cache.getIcon(icon)).thenReturn(drawable);
+ ReflectionHelpers.setField(mSuggestionAdapter, "mCache", cache);
+
+ mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+
+ verify(drawable, never()).setTint(anyInt());
+ }
+
+ @Test
+ public void onBindViewHolder_samePackage_shouldTintIcon()
+ throws PendingIntent.CanceledException {
+ final Icon icon = mock(Icon.class);
+ final String packageName = "pkg1";
+ when(icon.getResPackage()).thenReturn(packageName);
+ when(mActivity.getPackageName()).thenReturn(packageName);
+ final Suggestion suggestion = new Suggestion.Builder(packageName)
+ .setPendingIntent(mock(PendingIntent.class))
+ .setIcon(icon)
+ .build();
+ final List<Suggestion> suggestions = new ArrayList<>();
+ suggestions.add(suggestion);
+ mSuggestionAdapter = new SuggestionAdapterV2(mActivity, mSuggestionControllerMixin,
+ null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
+ mSuggestionAdapter.setSuggestions(suggestions);
+ mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
+ new FrameLayout(RuntimeEnvironment.application),
+ mSuggestionAdapter.getItemViewType(0));
+ DashboardAdapterV2.IconCache cache = mock(DashboardAdapterV2.IconCache.class);
+ final Drawable drawable = mock(Drawable.class);
+ when(cache.getIcon(icon)).thenReturn(drawable);
+ ReflectionHelpers.setField(mSuggestionAdapter, "mCache", cache);
+ TypedArray typedArray = mock(TypedArray.class);
+ final int colorAccent = 1234;
+ when(mActivity.obtainStyledAttributes(any())).thenReturn(typedArray);
+ when(typedArray.getColor(anyInt(), anyInt())).thenReturn(colorAccent);
+
+ mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+
+ verify(drawable).setTint(colorAccent);
}
private void setupSuggestions(Context context, List<Suggestion> suggestions) {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacyTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacyTest.java
index 45448a9..e707ede 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacyTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacyTest.java
@@ -15,8 +15,8 @@
*/
package com.android.settings.fuelgauge;
-import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_HIGH_POWER_APPS;
-import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_TOGGLE_APPS;
+import static com.android.settings.fuelgauge.PowerUsageSummaryLegacy.MENU_HIGH_POWER_APPS;
+import static com.android.settings.fuelgauge.PowerUsageSummaryLegacy.MENU_TOGGLE_APPS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
index 6fecf3c..35af8bb 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
@@ -16,7 +16,6 @@
package com.android.settings.fuelgauge;
import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_HIGH_POWER_APPS;
-import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_TOGGLE_APPS;
import static com.google.common.truth.Truth.assertThat;
@@ -170,7 +169,6 @@
doReturn(mock(LoaderManager.class)).when(mFragment).getLoaderManager();
when(mFragment.getActivity()).thenReturn(mSettingsActivity);
- when(mToggleAppsMenu.getItemId()).thenReturn(MENU_TOGGLE_APPS);
when(mHighPowerMenu.getItemId()).thenReturn(MENU_HIGH_POWER_APPS);
when(mFeatureFactory.powerUsageFeatureProvider.getAdditionalBatteryInfoIntent())
.thenReturn(sAdditionalBatteryInfoIntent);
@@ -215,39 +213,6 @@
}
@Test
- public void testOptionsMenu_menuAppToggle_metricEventInvoked() {
- mFragment.onOptionsItemSelected(mToggleAppsMenu);
- mFragment.mShowAllApps = false;
-
- verify(mFeatureFactory.metricsFeatureProvider).action(mContext,
- MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, true);
- }
-
- @Test
- public void testOptionsMenu_toggleAppsEnabled() {
- when(mFeatureFactory.powerUsageFeatureProvider.isPowerAccountingToggleEnabled())
- .thenReturn(true);
- mFragment.mShowAllApps = false;
-
- mFragment.onCreateOptionsMenu(mMenu, mMenuInflater);
-
- verify(mMenu).add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE, R.string.show_all_apps);
- }
-
- @Test
- public void testOptionsMenu_clickToggleAppsMenu_dataChanged() {
- testToggleAllApps(true);
- testToggleAllApps(false);
- }
-
- private void testToggleAllApps(final boolean isShowApps) {
- mFragment.mShowAllApps = isShowApps;
-
- mFragment.onOptionsItemSelected(mToggleAppsMenu);
- assertThat(mFragment.mShowAllApps).isEqualTo(!isShowApps);
- }
-
- @Test
public void testUpdateLastFullChargePreference_showCorrectSummary() {
doReturn(mRealContext).when(mFragment).getContext();
@@ -324,18 +289,6 @@
}
@Test
- public void testSaveInstanceState_showAllAppsRestored() {
- Bundle bundle = new Bundle();
- mFragment.mShowAllApps = true;
- doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen();
-
- mFragment.onSaveInstanceState(bundle);
- mFragment.restoreSavedInstance(bundle);
-
- assertThat(mFragment.mShowAllApps).isTrue();
- }
-
- @Test
public void testDebugMode() {
doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isEstimateDebugEnabled();
diff --git a/tests/uitests/src/com/android/settings/ui/ZonePickerSettingsTest.java b/tests/uitests/src/com/android/settings/ui/ZonePickerSettingsTest.java
new file mode 100644
index 0000000..109c3bc
--- /dev/null
+++ b/tests/uitests/src/com/android/settings/ui/ZonePickerSettingsTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2018 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.ui;
+
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.system.helpers.SettingsHelper;
+import android.system.helpers.SettingsHelper.SettingsType;
+import android.widget.ListView;
+import android.widget.Spinner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.TimeZone;
+
+import static com.android.settings.ui.testutils.SettingsTestUtils.SETTINGS_PACKAGE;
+import static com.android.settings.ui.testutils.SettingsTestUtils.TIMEOUT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ZonePickerSettingsTest {
+
+ private static final BySelector SELECTOR_SELECT_TIME_ZONE =
+ By.hasChild(By.text("Select time zone"));
+
+ private UiDevice mDevice;
+ private SettingsHelper mHelper;
+ private String mIsV2EnabledByDefault;
+ private int mIsAutoZoneEnabled;
+
+ @Before
+ public void setUp() throws Exception {
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mHelper = SettingsHelper.getInstance();
+ try {
+ mDevice.setOrientationNatural();
+ } catch (RemoteException e) {
+ throw new RuntimeException("failed to freeze device orientation", e);
+ }
+ mIsV2EnabledByDefault = mHelper.getStringSetting(SettingsType.GLOBAL,
+ "settings_zone_picker_v2");
+ mHelper.setStringSetting(SettingsType.GLOBAL, "settings_zone_picker_v2", "true");
+ mIsAutoZoneEnabled = mHelper.getIntSetting(SettingsType.GLOBAL,
+ Settings.Global.AUTO_TIME_ZONE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Go back to home for next test.
+ mDevice.pressBack();
+ mDevice.pressBack();
+ mDevice.pressHome();
+ mDevice.waitForIdle(TIMEOUT * 2);
+ mHelper.setStringSetting(SettingsType.GLOBAL, "settings_zone_picker_v2",
+ mIsV2EnabledByDefault);
+ mHelper.setIntSetting(SettingsType.GLOBAL, Settings.Global.AUTO_TIME_ZONE,
+ mIsAutoZoneEnabled);
+ }
+
+ @Test
+ public void zonePickerDisabled() throws Exception {
+ mHelper.setIntSetting(SettingsType.GLOBAL, Settings.Global.AUTO_TIME_ZONE, 1);
+
+ SettingsHelper.launchSettingsPage(
+ InstrumentationRegistry.getContext(), Settings.ACTION_DATE_SETTINGS);
+ UiObject2 selectTimeZone = wait(SELECTOR_SELECT_TIME_ZONE);
+ assertFalse(selectTimeZone.isEnabled());
+ }
+
+ // Test 2 time zones with no DST
+ @Test
+ public void testSelectReykjavik() throws Exception {
+ testSelectTimeZone("Iceland", "Reykjavik", "GMT+00:00", "Atlantic/Reykjavik");
+ }
+
+ @Test
+ public void testSelectPhoenix() throws Exception {
+ testSelectTimeZone("United States", "Phoenix", "GMT-07:00", "America/Phoenix");
+ }
+
+ private void testSelectTimeZone(String region, String timezone, String expectedTimeZoneOffset,
+ String expectedTimeZoneId) throws Exception {
+ mHelper.setIntSetting(SettingsType.GLOBAL, Settings.Global.AUTO_TIME_ZONE, 0);
+
+ SettingsHelper.launchSettingsPage(
+ InstrumentationRegistry.getContext(), Settings.ACTION_DATE_SETTINGS);
+
+ UiObject2 selectTimeZone = wait(SELECTOR_SELECT_TIME_ZONE);
+ assertTrue(selectTimeZone.isEnabled());
+ selectTimeZone.click();
+
+ // Select region in the dropdown list
+ selectScrollableItem(selectDropDownInSpinner(By.clazz(Spinner.class)),
+ new UiSelector().textContains(region))
+ .click();
+
+ // Select time zone
+ selectScrollableItem(selectTimeZoneList(),
+ new UiSelector().textContains(timezone))
+ .click();
+
+ // The select button should include the GMT offset in the summary
+ BySelector summarySelector = By.res("android:id/summary");
+ UiObject2 selectedTimeZone = selectTimeZone.findObject(summarySelector);
+ assertUiObjectFound(selectedTimeZone, summarySelector);
+ assertTrue("Expect " + expectedTimeZoneOffset + " is shown for " + timezone,
+ selectedTimeZone.getText().startsWith(expectedTimeZoneOffset));
+
+ waitAndAssertTimeGetDefault(expectedTimeZoneId);
+ assertEquals("Time zone change in Settings should update persist.sys.timezone",
+ expectedTimeZoneId, SystemProperties.get("persist.sys.timezone"));
+ }
+
+ private static final long CHECK_DEFAULT_TIMEZONE_INTERVAL = 200L;
+ private static final long CHECK_DEFAULT_TIMEZONE_TIMEOUT = 3000L;
+
+ /**
+ * Wait for the broadcast ACTION_TIMEZONE_CHANGED propagated, and update the default TimeZone
+ * by ApplicationThread.
+ */
+ private static void waitAndAssertTimeGetDefault(String expectedTimeZoneId)
+ throws InterruptedException {
+ for (int i = 0; i < CHECK_DEFAULT_TIMEZONE_TIMEOUT / CHECK_DEFAULT_TIMEZONE_INTERVAL; i++) {
+ if (expectedTimeZoneId.equals(TimeZone.getDefault().getID())) {
+ return;
+ }
+ Thread.sleep(CHECK_DEFAULT_TIMEZONE_INTERVAL);
+ }
+
+ assertEquals(expectedTimeZoneId, TimeZone.getDefault().getID());
+ }
+
+ /**
+ * Perform click on {@link Spinner} and return the pop-up dropdown list.
+ * @return UiScrollable representing the pop-up dropdown after clicking on the spinner
+ */
+ private UiScrollable selectDropDownInSpinner(BySelector spinnerSelector)
+ throws UiObjectNotFoundException {
+ UiObject2 spinner = wait(spinnerSelector);
+ spinner.click();
+
+ UiSelector dropDownSelector = new UiSelector().className(ListView.class);
+ return new UiScrollable(dropDownSelector);
+ }
+
+ private UiScrollable selectTimeZoneList() {
+ return new UiScrollable(new UiSelector().resourceId(SETTINGS_PACKAGE + ":id/tz_list"));
+ }
+
+ /**
+ * Select the child object in the UiScrollable
+ * @throws UiObjectNotFoundException if scrollable or child is not found
+ */
+ private UiObject selectScrollableItem(UiScrollable scrollable, UiSelector childSelector)
+ throws UiObjectNotFoundException {
+ if (!scrollable.waitForExists(TIMEOUT)) {
+ throw newUiObjectNotFoundException(scrollable.getSelector());
+ }
+ scrollable.scrollIntoView(childSelector);
+
+ UiObject child = mDevice.findObject(childSelector);
+ assertUiObjectFound(child, childSelector);
+ return child;
+ }
+
+ /**
+ * @throws UiObjectNotFoundException if UiDevice.wait returns null
+ */
+ private UiObject2 wait(BySelector selector) throws UiObjectNotFoundException {
+ UiObject2 item = mDevice.wait(Until.findObject(selector), TIMEOUT);
+ assertUiObjectFound(item, selector);
+ return item;
+ }
+
+ private static void assertUiObjectFound(UiObject2 obj, BySelector selector)
+ throws UiObjectNotFoundException {
+ if (obj == null) {
+ throw newUiObjectNotFoundException(selector);
+ }
+ }
+
+
+ private static void assertUiObjectFound(UiObject obj, UiSelector selector)
+ throws UiObjectNotFoundException {
+ if (obj == null) {
+ throw newUiObjectNotFoundException(selector);
+ }
+ }
+
+ private static UiObjectNotFoundException newUiObjectNotFoundException(Object selector) {
+ return new UiObjectNotFoundException(
+ String.format("UI object not found: %s", selector.toString()));
+ }
+}