From e2346643c7de1a8d371e3bfba32a40d533303c72 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 14 May 2018 14:06:23 -0700 Subject: [PATCH] Move app setting link from Entity header to pref controller Change-Id: I13a27486a9c9b4c4fb358715d678473e63c1b624 Fixes: 79688822 Test: robotest --- res/xml/app_info_settings.xml | 9 +- .../AppHeaderViewPreferenceController.java | 2 +- .../appinfo/AppInfoDashboardFragment.java | 3 + .../AppSettingPreferenceController.java | 78 ++++++++++++ .../widget/EntityHeaderController.java | 43 +------ .../AppSettingPreferenceControllerTest.java | 114 ++++++++++++++++++ .../widget/EntityHeaderControllerTest.java | 70 ++--------- 7 files changed, 216 insertions(+), 103 deletions(-) create mode 100644 src/com/android/settings/applications/appinfo/AppSettingPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/applications/appinfo/AppSettingPreferenceControllerTest.java diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml index 86b0df03180..15afb5f0a8a 100644 --- a/res/xml/app_info_settings.xml +++ b/res/xml/app_info_settings.xml @@ -161,10 +161,17 @@ + + + settings:controller="com.android.settings.applications.appinfo.AppVersionPreferenceController" + settings:allowDividerAbove="true" /> \ No newline at end of file diff --git a/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java b/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java index 0f22716b54a..e37e6708209 100644 --- a/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java @@ -68,7 +68,7 @@ public class AppHeaderViewPreferenceController extends BasePreferenceController mEntityHeaderController = EntityHeaderController .newInstance(activity, mParent, mHeader.findViewById(R.id.entity_header)) .setPackageName(mPackageName) - .setButtonActions(EntityHeaderController.ActionType.ACTION_APP_PREFERENCE, + .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE, EntityHeaderController.ActionType.ACTION_NONE) .bindHeaderButtons(); } diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index c460809bf51..1f8a3a07585 100755 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -167,6 +167,9 @@ public class AppInfoDashboardFragment extends DashboardFragment use(AppOpenByDefaultPreferenceController.class).setParentFragment(this); use(AppPermissionPreferenceController.class).setParentFragment(this); use(AppPermissionPreferenceController.class).setPackageName(packageName); + use(AppSettingPreferenceController.class) + .setPackageName(packageName) + .setParentFragment(this); use(AppStoragePreferenceController.class).setParentFragment(this); use(AppVersionPreferenceController.class).setParentFragment(this); use(InstantAppDomainsPreferenceController.class).setParentFragment(this); diff --git a/src/com/android/settings/applications/appinfo/AppSettingPreferenceController.java b/src/com/android/settings/applications/appinfo/AppSettingPreferenceController.java new file mode 100644 index 00000000000..d20fe55b267 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppSettingPreferenceController.java @@ -0,0 +1,78 @@ +/* + * 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.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_OPEN_APP_SETTING; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.text.TextUtils; + +import com.android.settings.overlay.FeatureFactory; + +import androidx.preference.Preference; + +public class AppSettingPreferenceController extends AppInfoPreferenceControllerBase { + + private String mPackageName; + + public AppSettingPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + public AppSettingPreferenceController setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + @Override + public int getAvailabilityStatus() { + if (TextUtils.isEmpty(mPackageName) || mParent == null) { + return CONDITIONALLY_UNAVAILABLE; + } + final Intent intent = resolveIntent( + new Intent(Intent.ACTION_APPLICATION_PREFERENCES).setPackage(mPackageName)); + return intent != null ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + final Intent intent = resolveIntent( + new Intent(Intent.ACTION_APPLICATION_PREFERENCES).setPackage(mPackageName)); + if (intent == null) { + return false; + } + FeatureFactory.getFactory(mContext).getMetricsFeatureProvider() + .actionWithSource(mContext, mParent.getMetricsCategory(), + ACTION_OPEN_APP_SETTING); + mContext.startActivity(intent); + return true; + } + + private Intent resolveIntent(Intent i) { + ResolveInfo result = mContext.getPackageManager().resolveActivity(i, 0); + if (result != null) { + return new Intent(i.getAction()) + .setClassName(result.activityInfo.packageName, result.activityInfo.name); + } + return null; + } +} diff --git a/src/com/android/settings/widget/EntityHeaderController.java b/src/com/android/settings/widget/EntityHeaderController.java index eeb8b7929ce..05d2be1feb7 100644 --- a/src/com/android/settings/widget/EntityHeaderController.java +++ b/src/com/android/settings/widget/EntityHeaderController.java @@ -18,7 +18,6 @@ package com.android.settings.widget; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent .ACTION_OPEN_APP_NOTIFICATION_SETTING; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_OPEN_APP_SETTING; import android.annotation.IdRes; import android.annotation.UserIdInt; @@ -28,7 +27,6 @@ import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; -import android.content.pm.ResolveInfo; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -59,15 +57,13 @@ import java.lang.annotation.RetentionPolicy; public class EntityHeaderController { @IntDef({ActionType.ACTION_NONE, - ActionType.ACTION_APP_PREFERENCE, ActionType.ACTION_NOTIF_PREFERENCE, ActionType.ACTION_EDIT_PREFERENCE,}) @Retention(RetentionPolicy.SOURCE) public @interface ActionType { int ACTION_NONE = 0; - int ACTION_APP_PREFERENCE = 1; - int ACTION_NOTIF_PREFERENCE = 2; - int ACTION_EDIT_PREFERENCE = 3; + int ACTION_NOTIF_PREFERENCE = 1; + int ACTION_EDIT_PREFERENCE = 2; } public static final String PREF_KEY_APP_HEADER = "pref_app_header"; @@ -298,9 +294,9 @@ public class EntityHeaderController { @Override public void onClick(View v) { AppInfoBase.startAppInfoFragment( - AppInfoDashboardFragment.class, R.string.application_info_label, - mPackageName, mUid, mFragment, 0 /* request */, - mMetricsCategory); + AppInfoDashboardFragment.class, R.string.application_info_label, + mPackageName, mUid, mFragment, 0 /* request */, + mMetricsCategory); } }); return; @@ -372,27 +368,6 @@ public class EntityHeaderController { } return; } - case ActionType.ACTION_APP_PREFERENCE: { - final Intent intent = resolveIntent( - new Intent(Intent.ACTION_APPLICATION_PREFERENCES).setPackage(mPackageName)); - if (intent == null) { - button.setImageDrawable(null); - button.setVisibility(View.GONE); - return; - } - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider() - .actionWithSource(mAppContext, mMetricsCategory, - ACTION_OPEN_APP_SETTING); - mFragment.startActivity(intent); - } - }); - button.setImageResource(R.drawable.ic_settings_24dp); - button.setVisibility(View.VISIBLE); - return; - } case ActionType.ACTION_NONE: { button.setVisibility(View.GONE); return; @@ -400,14 +375,6 @@ public class EntityHeaderController { } } - private Intent resolveIntent(Intent i) { - ResolveInfo result = mAppContext.getPackageManager().resolveActivity(i, 0); - if (result != null) { - return new Intent(i.getAction()) - .setClassName(result.activityInfo.packageName, result.activityInfo.name); - } - return null; - } private void setText(@IdRes int id, CharSequence text) { TextView textView = mHeader.findViewById(id); diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppSettingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppSettingPreferenceControllerTest.java new file mode 100644 index 00000000000..1c5863c17c8 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppSettingPreferenceControllerTest.java @@ -0,0 +1,114 @@ +/* + * 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.google.common.truth.Truth.assertThat; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Application; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowPackageManager; + +import androidx.preference.Preference; + +@RunWith(SettingsRobolectricTestRunner.class) +public class AppSettingPreferenceControllerTest { + + private static final String TEST_PKG_NAME = "test_pkg"; + private static final String TEST_CLASS_NAME = "name"; + private static final Intent TEST_INTENT = + new Intent(Intent.ACTION_APPLICATION_PREFERENCES) + .setClassName(TEST_PKG_NAME, TEST_CLASS_NAME); + private static final Intent RESOLVED_INTENT = + new Intent(Intent.ACTION_APPLICATION_PREFERENCES) + .setPackage(TEST_PKG_NAME); + + @Mock + private AppInfoDashboardFragment mParent; + private Application mApplication; + private ShadowPackageManager mPackageManager; + private AppSettingPreferenceController mController; + private Preference mPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mApplication = RuntimeEnvironment.application; + mPackageManager = shadowOf(mApplication.getPackageManager()); + mController = new AppSettingPreferenceController(mApplication, "test_key"); + mController.setPackageName(TEST_PKG_NAME).setParentFragment(mParent); + mPreference = new Preference(mApplication); + mPreference.setKey(mController.getPreferenceKey()); + } + + @Test + public void getAvailabilityStatus_noAppSetting_shouldNotBeAvailable() { + assertThat(mController.isAvailable()) + .isFalse(); + } + + @Test + public void getAvailabilityStatus_noPackageName_shouldNotBeAvailable() { + mController.setPackageName(null); + + assertThat(mController.isAvailable()) + .isFalse(); + } + + @Test + public void getAvailabilityStatus_hasAppSetting_shouldBeAvailable() { + final ResolveInfo info = new ResolveInfo(); + info.activityInfo = new ActivityInfo(); + info.activityInfo.packageName = TEST_PKG_NAME; + info.activityInfo.name = TEST_CLASS_NAME; + + mPackageManager.addResolveInfoForIntent(RESOLVED_INTENT, info); + + assertThat(mController.isAvailable()) + .isTrue(); + } + + @Test + public void clickPreference_noAppSetting_shouldDoNothing() { + assertThat(mController.handlePreferenceTreeClick(mPreference)).isFalse(); + } + + @Test + public void clickPreference_hasAppSetting_shouldLaunchIntent() { + final ResolveInfo info = new ResolveInfo(); + info.activityInfo = new ActivityInfo(); + info.activityInfo.packageName = TEST_PKG_NAME; + info.activityInfo.name = TEST_CLASS_NAME; + + mPackageManager.addResolveInfoForIntent(RESOLVED_INTENT, info); + + assertThat(mController.handlePreferenceTreeClick(mPreference)).isTrue(); + assertThat(shadowOf(mApplication).getNextStartedActivity().getComponent()) + .isEqualTo(TEST_INTENT.getComponent()); + } +} diff --git a/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java b/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java index b82271fb853..44d355c8e33 100644 --- a/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java @@ -35,13 +35,11 @@ import android.content.pm.PackageInfo; import android.content.pm.ResolveInfo; import android.graphics.drawable.ColorDrawable; import android.os.UserHandle; -import androidx.preference.Preference; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageButton; import android.widget.TextView; -import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.applications.LayoutPreference; import com.android.settings.testutils.FakeFeatureFactory; @@ -55,6 +53,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; +import androidx.preference.Preference; + @RunWith(SettingsRobolectricTestRunner.class) public class EntityHeaderControllerTest { @@ -113,7 +113,7 @@ public class EntityHeaderControllerTest { public void bindViews_shouldBindAllData() { final String testString = "test"; final View header = - mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); + mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); final TextView label = header.findViewById(R.id.entity_header_title); final TextView summary = header.findViewById(R.id.entity_header_summary); final TextView secondSummary = header.findViewById(R.id.entity_header_second_summary); @@ -133,41 +133,6 @@ public class EntityHeaderControllerTest { assertThat(secondSummary.getText()).isEqualTo(testString); } - @Test - public void bindButton_hasAppPref_shouldShowButton() { - final ResolveInfo info = new ResolveInfo(); - info.activityInfo = new ActivityInfo(); - info.activityInfo.packageName = "123"; - info.activityInfo.name = "321"; - final View appLinks = - mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); - when(mActivity.getApplicationContext()).thenReturn(mContext); - when(mContext.getPackageManager().resolveActivity(any(Intent.class), anyInt())) - .thenReturn(info); - - mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks); - mController.setButtonActions( - EntityHeaderController.ActionType.ACTION_APP_PREFERENCE, - EntityHeaderController.ActionType.ACTION_NONE); - mController.done(mActivity); - - final ImageButton button1 = appLinks.findViewById(android.R.id.button1); - assertThat(button1).isNotNull(); - assertThat(button1.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(button1.getDrawable()).isNotNull(); - assertThat(appLinks.findViewById(android.R.id.button2).getVisibility()) - .isEqualTo(View.GONE); - try { - appLinks.findViewById(android.R.id.button1).performClick(); - } catch (Exception e) { - // Ignore exception because the launching intent is fake. - } - verify(mFeatureFactory.metricsFeatureProvider).actionWithSource(mContext, - MetricsProto.MetricsEvent.VIEW_UNKNOWN, - MetricsProto.MetricsEvent.ACTION_OPEN_APP_SETTING); - verify(mFragment).startActivity(any(Intent.class)); - } - @Test public void bindButton_hasEditClickListener_shouldShowButton() { final ResolveInfo info = new ResolveInfo(); @@ -216,31 +181,10 @@ public class EntityHeaderControllerTest { } - @Test - public void bindButton_noAppPref_shouldNotShowButton() { - final View appLinks = - mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); - when(mContext.getPackageManager().resolveActivity(any(Intent.class), anyInt())) - .thenReturn(null); - - mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks); - mController.setButtonActions( - EntityHeaderController.ActionType.ACTION_APP_PREFERENCE, - EntityHeaderController.ActionType.ACTION_NONE); - mController.done(mActivity); - - final ImageButton button1 = appLinks.findViewById(android.R.id.button1); - assertThat(button1).isNotNull(); - assertThat(button1.getVisibility()).isEqualTo(View.GONE); - assertThat(button1.getDrawable()).isNull(); - assertThat(appLinks.findViewById(android.R.id.button2).getVisibility()) - .isEqualTo(View.GONE); - } - @Test public void bindButton_noAppInfo_shouldNotAttachClickListener() { final View appLinks = - mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); + mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); final Activity activity = mock(Activity.class); when(mFragment.getActivity()).thenReturn(activity); @@ -265,7 +209,7 @@ public class EntityHeaderControllerTest { @Test public void bindButton_hasAppInfo_shouldAttachClickListener() { final View appLinks = - mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); + mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); final Activity activity = mock(Activity.class); when(mFragment.getActivity()).thenReturn(activity); when(mContext.getString(eq(R.string.application_info_label))).thenReturn("App Info"); @@ -281,13 +225,13 @@ public class EntityHeaderControllerTest { appLinks.findViewById(R.id.entity_header_content).performClick(); verify(activity) - .startActivityForResultAsUser(any(Intent.class), anyInt(), any(UserHandle.class)); + .startActivityForResultAsUser(any(Intent.class), anyInt(), any(UserHandle.class)); } @Test public void iconContentDescription_shouldWorkWithSetIcon() { final View view = - mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); + mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); when(mFragment.getActivity()).thenReturn(mock(Activity.class)); mController = EntityHeaderController.newInstance(mActivity, mFragment, view); String description = "Fake Description";