diff --git a/res/values/strings.xml b/res/values/strings.xml index 3008965e79a..aa484835065 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1926,9 +1926,6 @@ %1$s app will no longer connect to your %2$s - - %1$s will no longer be paired with any device linked to this account - Forget device @@ -6364,8 +6361,8 @@ Wi\u2011Fi Bluetooth - - Mobile network standby + + Mobile network Voice calls diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 9c81895e976..226e1de1895 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -58,6 +58,7 @@ import androidx.preference.PreferenceManager; import com.android.internal.util.ArrayUtils; import com.android.settings.Settings.WifiSettingsActivity; import com.android.settings.activityembedding.ActivityEmbeddingUtils; +import com.android.settings.activityembedding.SplitStateObserver; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.core.OnActivityResultListener; import com.android.settings.core.SettingsBaseActivity; @@ -255,11 +256,8 @@ public class SettingsActivity extends SettingsBaseActivity // Should happen before any call to getIntent() getMetaData(); final Intent intent = getIntent(); - if (shouldShowTwoPaneDeepLink(intent)) { - launchHomepageForTwoPaneDeepLink(intent); - finishAndRemoveTask(); - return; - } + + registerSplitStateObserverForTwoPaneDeepLink(); final FeatureFactory factory = FeatureFactory.getFactory(this); mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this); @@ -364,6 +362,29 @@ public class SettingsActivity extends SettingsBaseActivity } } + private void registerSplitStateObserverForTwoPaneDeepLink() { + if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { + return; + } + + final SplitStateObserver splitStateObserver = new SplitStateObserver(this /* activity*/, + true /* listenOnce */, + splitInfos -> { + if (!splitInfos.isEmpty()) { + // It's already in 2-pane and no need to go 2-pane deep link flow. + return; + } + + if (shouldShowTwoPaneDeepLink(getIntent())) { + launchHomepageForTwoPaneDeepLink(getIntent()); + finishAndRemoveTask(); + return; + } + } + ); + getLifecycle().addObserver(splitStateObserver); + } + private boolean isSubSettings(Intent intent) { return this instanceof SubSettings || intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); @@ -413,10 +434,6 @@ public class SettingsActivity extends SettingsBaseActivity } private boolean shouldShowTwoPaneDeepLink(Intent intent) { - if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { - return false; - } - // Only starts trampoline for deep links. Should return false for all the cases that // Settings app starts SettingsActivity or SubSetting by itself. if (intent.getAction() == null) { @@ -434,11 +451,6 @@ public class SettingsActivity extends SettingsBaseActivity return false; } - if (intent.getBooleanExtra(SettingsHomepageActivity.EXTRA_IS_FROM_SETTINGS_HOMEPAGE, - /* defaultValue */ false)) { - return false; - } - if (TextUtils.equals(intent.getAction(), Intent.ACTION_CREATE_SHORTCUT)) { // Returns false to show full screen for Intent.ACTION_CREATE_SHORTCUT because // - Launcher startActivityForResult for Intent.ACTION_CREATE_SHORTCUT and activity diff --git a/src/com/android/settings/activityembedding/SplitStateObserver.java b/src/com/android/settings/activityembedding/SplitStateObserver.java new file mode 100644 index 00000000000..ba13c825c5c --- /dev/null +++ b/src/com/android/settings/activityembedding/SplitStateObserver.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 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.activityembedding; + +import static androidx.lifecycle.Lifecycle.Event.ON_START; +import static androidx.lifecycle.Lifecycle.Event.ON_STOP; + +import android.app.Activity; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.util.Consumer; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; +import androidx.window.embedding.SplitController; +import androidx.window.embedding.SplitInfo; + +import java.util.List; + +/** A lifecycle-aware observer listens to active split state. */ +public class SplitStateObserver implements LifecycleObserver, Consumer> { + + private final Activity mActivity; + private final boolean mListenOnce; + private final SplitStateListener mListener; + private final SplitController mSplitController; + + public SplitStateObserver(@NonNull Activity activity, boolean listenOnce, + @NonNull SplitStateListener listener) { + mActivity = activity; + mListenOnce = listenOnce; + mListener = listener; + mSplitController = SplitController.getInstance(); + } + + /** + * Start lifecycle event. + */ + @OnLifecycleEvent(ON_START) + public void onStart() { + mSplitController.addSplitListener(mActivity, ContextCompat.getMainExecutor(mActivity), + this); + } + + /** + * Stop lifecycle event. + */ + @OnLifecycleEvent(ON_STOP) + public void onStop() { + mSplitController.removeSplitListener(this); + } + + @Override + public void accept(List splitInfos) { + if (mListenOnce) { + mSplitController.removeSplitListener(this); + } + mListener.onSplitInfoChanged(splitInfos); + } + + /** This interface makes as class that it wants to listen to {@link SplitInfo} changes. */ + public interface SplitStateListener { + + /** Receive a set of split info change */ + void onSplitInfoChanged(List splitInfos); + } +} diff --git a/src/com/android/settings/bluetooth/ForgetDeviceDialogFragment.java b/src/com/android/settings/bluetooth/ForgetDeviceDialogFragment.java index 6d8fb3380e1..1da8672b760 100644 --- a/src/com/android/settings/bluetooth/ForgetDeviceDialogFragment.java +++ b/src/com/android/settings/bluetooth/ForgetDeviceDialogFragment.java @@ -29,7 +29,6 @@ import androidx.appcompat.app.AlertDialog; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; -import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -73,8 +72,6 @@ public class ForgetDeviceDialogFragment extends InstrumentedDialogFragment { }; Context context = getContext(); mDevice = getDevice(context); - final boolean untetheredHeadset = BluetoothUtils.getBooleanMetaData( - mDevice.getDevice(), BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET); AlertDialog dialog = new AlertDialog.Builder(context) .setPositiveButton(R.string.bluetooth_unpair_dialog_forget_confirm_button, @@ -82,9 +79,7 @@ public class ForgetDeviceDialogFragment extends InstrumentedDialogFragment { .setNegativeButton(android.R.string.cancel, null) .create(); dialog.setTitle(R.string.bluetooth_unpair_dialog_title); - dialog.setMessage(context.getString(untetheredHeadset - ? R.string.bluetooth_untethered_unpair_dialog_body - : R.string.bluetooth_unpair_dialog_body, + dialog.setMessage(context.getString(R.string.bluetooth_unpair_dialog_body, mDevice.getName())); return dialog; } diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index ae8c61e3b30..0322cfa8b5e 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -63,10 +63,6 @@ public class SettingsHomepageActivity extends FragmentActivity implements private static final String TAG = "SettingsHomepageActivity"; - // Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK. - // Put true value to the intent when startActivity for a deep link intent from this Activity. - public static final String EXTRA_IS_FROM_SETTINGS_HOMEPAGE = "is_from_settings_homepage"; - // Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK. // Set & get Uri of the Intent separately to prevent failure of Intent#ParseUri. public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA = @@ -268,11 +264,11 @@ public class SettingsHomepageActivity extends FragmentActivity implements // Sender of intent may want to send intent extra data to the destination of targetIntent. targetIntent.replaceExtras(intent); - targetIntent.putExtra(EXTRA_IS_FROM_SETTINGS_HOMEPAGE, true); targetIntent.putExtra(SettingsActivity.EXTRA_IS_FROM_SLICE, false); targetIntent.setData(intent.getParcelableExtra( SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA)); + // Set 2-pane pair rule for the deep link page. ActivityEmbeddingRulesController.registerTwoPanePairRule(this, getDeepLinkComponent(), diff --git a/src/com/android/settings/wifi/RequestToggleWiFiActivity.java b/src/com/android/settings/wifi/RequestToggleWiFiActivity.java index 34ef7cf2705..6887b7802d0 100644 --- a/src/com/android/settings/wifi/RequestToggleWiFiActivity.java +++ b/src/com/android/settings/wifi/RequestToggleWiFiActivity.java @@ -19,6 +19,8 @@ package com.android.settings.wifi; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.app.Activity; +import android.app.ActivityManager; +import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -29,11 +31,13 @@ import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.net.wifi.WifiManager; import android.os.Bundle; +import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AlertActivity; import com.android.settings.R; @@ -63,6 +67,8 @@ public class RequestToggleWiFiActivity extends AlertActivity private @NonNull WifiManager mWiFiManager; private @NonNull CharSequence mAppLabel; + @VisibleForTesting + protected IActivityManager mActivityManager = ActivityManager.getService(); private int mState = STATE_UNKNOWN; private int mLastUpdateState = STATE_UNKNOWN; @@ -75,20 +81,8 @@ public class RequestToggleWiFiActivity extends AlertActivity setResult(Activity.RESULT_CANCELED); - String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); - if (TextUtils.isEmpty(packageName)) { - finish(); - return; - } - - try { - ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo( - packageName, 0); - mAppLabel = applicationInfo.loadSafeLabel(getPackageManager(), - PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, PackageItemInfo.SAFE_LABEL_FLAG_TRIM - | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE); - } catch (PackageManager.NameNotFoundException e) { - Log.e(LOG_TAG, "Couldn't find app with package name " + packageName); + mAppLabel = getAppLabel(); + if (TextUtils.isEmpty(mAppLabel)) { finish(); return; } @@ -140,7 +134,6 @@ public class RequestToggleWiFiActivity extends AlertActivity @Override protected void onStart() { super.onStart(); - mReceiver.register(); final int wifiState = mWiFiManager.getWifiState(); @@ -223,6 +216,32 @@ public class RequestToggleWiFiActivity extends AlertActivity super.onStop(); } + @VisibleForTesting + protected CharSequence getAppLabel() { + String packageName; + try { + packageName = mActivityManager.getLaunchedFromPackage(getActivityToken()); + if (TextUtils.isEmpty(packageName)) { + Log.d(LOG_TAG, "Package name is null"); + return null; + } + } catch (RemoteException e) { + Log.e(LOG_TAG, "Can not get the package from activity manager"); + return null; + } + + try { + ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo( + packageName, 0); + return applicationInfo.loadSafeLabel(getPackageManager(), + PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, PackageItemInfo.SAFE_LABEL_FLAG_TRIM + | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE); + } catch (PackageManager.NameNotFoundException e) { + Log.e(LOG_TAG, "Couldn't find app with package name " + packageName); + return null; + } + } + private void updateUi() { if (mLastUpdateState == mState) { return; diff --git a/src/com/android/settings/wifi/addappnetworks/AddAppNetworksActivity.java b/src/com/android/settings/wifi/addappnetworks/AddAppNetworksActivity.java index 7b5eaa9ecb1..6bf91040443 100644 --- a/src/com/android/settings/wifi/addappnetworks/AddAppNetworksActivity.java +++ b/src/com/android/settings/wifi/addappnetworks/AddAppNetworksActivity.java @@ -16,9 +16,14 @@ package com.android.settings.wifi.addappnetworks; +import android.app.ActivityManager; +import android.app.IActivityManager; import android.content.Intent; import android.os.Bundle; +import android.os.RemoteException; import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.Window; import android.view.WindowManager; @@ -48,12 +53,17 @@ public class AddAppNetworksActivity extends FragmentActivity { @VisibleForTesting final Bundle mBundle = new Bundle(); + @VisibleForTesting + IActivityManager mActivityManager = ActivityManager.getService(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.settings_panel); - showAddNetworksFragment(); + if (!showAddNetworksFragment()) { + finish(); + return; + } getLifecycle().addObserver(new HideNonSystemOverlayMixin(this)); // Move the window to the bottom of screen, and make it take up the entire screen width. @@ -67,13 +77,22 @@ public class AddAppNetworksActivity extends FragmentActivity { protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); - showAddNetworksFragment(); + if (!showAddNetworksFragment()) { + finish(); + return; + } } @VisibleForTesting - void showAddNetworksFragment() { + protected boolean showAddNetworksFragment() { + String packageName = getCallingAppPackageName(); + if (TextUtils.isEmpty(packageName)) { + Log.d(TAG, "Package name is null"); + return false; + } + // TODO: Check the new intent status. - mBundle.putString(KEY_CALLING_PACKAGE_NAME, getCallingPackage()); + mBundle.putString(KEY_CALLING_PACKAGE_NAME, packageName); mBundle.putParcelableArrayList(Settings.EXTRA_WIFI_NETWORK_LIST, getIntent().getParcelableArrayListExtra(Settings.EXTRA_WIFI_NETWORK_LIST)); @@ -86,5 +105,19 @@ public class AddAppNetworksActivity extends FragmentActivity { } else { ((AddAppNetworksFragment) fragment).createContent(mBundle); } + + return true; + } + + @VisibleForTesting + protected String getCallingAppPackageName() { + String packageName; + try { + packageName = mActivityManager.getLaunchedFromPackage(getActivityToken()); + } catch (RemoteException e) { + Log.e(TAG, "Can not get the package from activity manager"); + return null; + } + return packageName; } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/ForgetDeviceDialogFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/ForgetDeviceDialogFragmentTest.java index b4f4f97f228..5c232386121 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ForgetDeviceDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ForgetDeviceDialogFragmentTest.java @@ -98,20 +98,6 @@ public class ForgetDeviceDialogFragmentTest { assertThat(mActivity.isFinishing()).isTrue(); } - @Test - public void createDialog_untetheredDevice_showUntetheredMessage() { - when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) - .thenReturn("true".getBytes()); - - FragmentController.setupFragment(mFragment, FragmentActivity.class, - 0 /* containerViewId */, null /* bundle */); - final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog); - - assertThat(shadowDialog.getMessage()).isEqualTo( - mContext.getString(R.string.bluetooth_untethered_unpair_dialog_body, DEVICE_NAME)); - } - @Test public void createDialog_normalDevice_showNormalMessage() { when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) diff --git a/tests/robotests/src/com/android/settings/wifi/addappnetworks/AddAppNetworksActivityTest.java b/tests/robotests/src/com/android/settings/wifi/addappnetworks/AddAppNetworksActivityTest.java index b6146985142..39408e2fc9c 100644 --- a/tests/robotests/src/com/android/settings/wifi/addappnetworks/AddAppNetworksActivityTest.java +++ b/tests/robotests/src/com/android/settings/wifi/addappnetworks/AddAppNetworksActivityTest.java @@ -18,27 +18,70 @@ package com.android.settings.wifi.addappnetworks; import static com.google.common.truth.Truth.assertThat; -import static org.robolectric.Shadows.shadowOf; +import android.annotation.Nullable; +import android.app.IActivityManager; +import android.os.RemoteException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class AddAppNetworksActivityTest { + @Mock + private IActivityManager mIActivityManager; + + private AddAppNetworksActivity mActivity; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mActivity = Robolectric.buildActivity(AddAppNetworksActivity.class).create().get(); + mActivity.mActivityManager = mIActivityManager; + } + @Test - public void startActivity_withPackageName_bundleShouldHaveRightPackageName() { - final String packageName = RuntimeEnvironment.application.getPackageName(); - final AddAppNetworksActivity activity = - Robolectric.buildActivity(AddAppNetworksActivity.class).create().get(); - shadowOf(activity).setCallingPackage(packageName); + public void getCallingAppPackageName_nullPackageName_returnNotNull() { + fakeCallingPackage("com.android.settings"); - activity.showAddNetworksFragment(); + assertThat(mActivity.getCallingAppPackageName()).isNotNull(); + } - assertThat(activity.mBundle.getString(AddAppNetworksActivity.KEY_CALLING_PACKAGE_NAME)) - .isEqualTo(packageName); + @Test + public void getCallingAppPackageName_withPackageName_returnNull() { + fakeCallingPackage(null); + + assertThat(mActivity.getCallingAppPackageName()).isNull(); + } + + @Test + public void showAddNetworksFragment_nullPackageName_returnFalse() { + fakeCallingPackage(null); + + assertThat(mActivity.showAddNetworksFragment()).isFalse(); + } + + @Test + public void showAddNetworksFragment_withPackageName_returnTrue() { + fakeCallingPackage("com.android.settings"); + + assertThat(mActivity.showAddNetworksFragment()).isTrue(); + } + + private void fakeCallingPackage(@Nullable String packageName) { + try { + when(mIActivityManager.getLaunchedFromPackage(any())).thenReturn(packageName); + } catch (RemoteException e) { + // Do nothing. + } } } diff --git a/tests/unit/src/com/android/settings/wifi/RequestToggleWiFiActivityTest.java b/tests/unit/src/com/android/settings/wifi/RequestToggleWiFiActivityTest.java new file mode 100644 index 00000000000..0e3dd4038e4 --- /dev/null +++ b/tests/unit/src/com/android/settings/wifi/RequestToggleWiFiActivityTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 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.wifi; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import android.annotation.Nullable; +import android.app.IActivityManager; +import android.content.Context; +import android.content.Intent; +import android.net.wifi.WifiManager; +import android.os.RemoteException; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +public class RequestToggleWiFiActivityTest { + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + private WifiManager mWifiManager; + @Mock + private IActivityManager mIActivityManager; + + private ActivityScenario mActivityScenario; + private RequestToggleWiFiActivity mActivity; + + @Before + public void setUp() { + when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager); + when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED); + + mActivityScenario = ActivityScenario.launch(new Intent(WifiManager.ACTION_REQUEST_ENABLE)); + mActivityScenario.onActivity(activity -> mActivity = activity); + + } + + @After + public void cleanUp() { + mActivity = null; + if (mActivityScenario != null) { + mActivityScenario.close(); + } + } + + @Test + public void getAppLabel_nullPackageName_returnNull() { + fakeCallingPackage(null); + + assertThat(mActivity.getAppLabel()).isNull(); + } + + @Test + public void getAppLabel_settingsPackageName_returnNotNull() { + fakeCallingPackage("com.android.settings"); + + assertThat(mActivity.getAppLabel()).isNotNull(); + } + + private void fakeCallingPackage(@Nullable String packageName) { + assertThat(mActivity).isNotNull(); + mActivity.mActivityManager = mIActivityManager; + try { + when(mIActivityManager.getLaunchedFromPackage(any())).thenReturn(packageName); + } catch (RemoteException e) { + // Do nothing. + } + } +}