diff --git a/res/layout/wifi_dpp_qrcode_generator_fragment.xml b/res/layout/wifi_dpp_qrcode_generator_fragment.xml index 76eecb0a05d..1d9656cca2a 100644 --- a/res/layout/wifi_dpp_qrcode_generator_fragment.xml +++ b/res/layout/wifi_dpp_qrcode_generator_fragment.xml @@ -42,6 +42,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" + android:id="@+id/wifi_dpp_layout" android:orientation="vertical"> launchWifiDppConfiguratorActivity(mSelectedWifiEntry)); + return true; case MENU_ID_MODIFY: showDialog(mSelectedWifiEntry, WifiConfigUiBase2.MODE_MODIFY); return true; @@ -1115,6 +1121,23 @@ public class WifiSettings extends RestrictedSettingsFragment .launch(); } + private void launchWifiDppConfiguratorActivity(WifiEntry wifiEntry) { + final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(getContext(), + mWifiManager, wifiEntry); + + if (intent == null) { + Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!"); + } else { + mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, + SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_QR_CODE, + SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR, + /* key */ null, + /* value */ Integer.MIN_VALUE); + + startActivity(intent); + } + } + /** Helper method to return whether a WifiEntry is disabled due to a wrong password */ private static boolean isDisabledByWrongPassword(WifiEntry wifiEntry) { WifiConfiguration config = wifiEntry.getWifiConfiguration(); diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java index 603ea1014b8..aa67bde3493 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java @@ -16,9 +16,17 @@ package com.android.settings.wifi.dpp; +import android.annotation.Nullable; import android.app.settings.SettingsEnums; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -27,9 +35,13 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.chooser.DisplayResolveInfo; +import com.android.internal.app.chooser.TargetInfo; import com.android.settings.R; import com.android.settings.wifi.qrcode.QrCodeGenerator; @@ -45,6 +57,15 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment { private ImageView mQrCodeView; private String mQrCode; + private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label"; + private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon"; + private static final String EXTRA_WIFI_CREDENTIALS_BUNDLE = + "android.intent.extra.WIFI_CREDENTIALS_BUNDLE"; + private static final String EXTRA_SSID = "android.intent.extra.SSID"; + private static final String EXTRA_PASSWORD = "android.intent.extra.PASSWORD"; + private static final String EXTRA_SECURITY_TYPE = "android.intent.extra.SECURITY_TYPE"; + private static final String EXTRA_HIDDEN_SSID = "android.intent.extra.HIDDEN_SSID"; + @Override public int getMetricsCategory() { return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR; @@ -56,10 +77,12 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment { // setTitle for TalkBack final WifiNetworkConfig wifiNetworkConfig = getWifiNetworkConfigFromHostActivity(); - if (wifiNetworkConfig.isHotspot()) { - getActivity().setTitle(R.string.wifi_dpp_share_hotspot); - } else { - getActivity().setTitle(R.string.wifi_dpp_share_wifi); + if (getActivity() != null) { + if (wifiNetworkConfig.isHotspot()) { + getActivity().setTitle(R.string.wifi_dpp_share_hotspot); + } else { + getActivity().setTitle(R.string.wifi_dpp_share_wifi); + } } } @@ -112,10 +135,127 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment { } } + final Intent intent = new Intent().setComponent(getNearbySharingComponent()); + addActionButton(view.findViewById(R.id.wifi_dpp_layout), createNearbyButton(intent, v -> { + intent.setAction(Intent.ACTION_SEND); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + + Bundle wifiCredentialBundle = new Bundle(); + + String ssid = WifiDppUtils.removeFirstAndLastDoubleQuotes(wifiNetworkConfig.getSsid()); + + String passwordExtra = wifiNetworkConfig.getPreSharedKey(); + String securityType = wifiNetworkConfig.getSecurity(); + boolean hiddenSsid = wifiNetworkConfig.getHiddenSsid(); + + wifiCredentialBundle.putString(EXTRA_SSID, ssid); + wifiCredentialBundle.putString(EXTRA_PASSWORD, passwordExtra); + wifiCredentialBundle.putString(EXTRA_SECURITY_TYPE, securityType); + wifiCredentialBundle.putBoolean(EXTRA_HIDDEN_SSID, hiddenSsid); + + intent.putExtra(EXTRA_WIFI_CREDENTIALS_BUNDLE, wifiCredentialBundle); + startActivity(intent); + })); + mQrCode = wifiNetworkConfig.getQrCode(); setQrCode(); } + @VisibleForTesting ComponentName getNearbySharingComponent() { + String nearbyComponent = Settings.Secure.getString( + getContext().getContentResolver(), + Settings.Secure.NEARBY_SHARING_COMPONENT); + if (TextUtils.isEmpty(nearbyComponent)) { + nearbyComponent = getString( + com.android.internal.R.string.config_defaultNearbySharingComponent); + } + if (TextUtils.isEmpty(nearbyComponent)) { + return null; + } + return ComponentName.unflattenFromString(nearbyComponent); + } + + private TargetInfo getNearbySharingTarget(Intent originalIntent) { + final ComponentName cn = getNearbySharingComponent(); + if (cn == null) return null; + + final Intent resolveIntent = new Intent(originalIntent); + resolveIntent.setComponent(cn); + PackageManager pm = getContext().getPackageManager(); + final ResolveInfo resolveInfo = pm.resolveActivity( + resolveIntent, PackageManager.GET_META_DATA); + if (resolveInfo == null || resolveInfo.activityInfo == null) { + Log.e(TAG, "Device-specified nearby sharing component (" + cn + + ") not available"); + return null; + } + + // Allow the nearby sharing component to provide a more appropriate icon and label + // for the chip. + CharSequence name = null; + Drawable icon = null; + final Bundle metaData = resolveInfo.activityInfo.metaData; + if (metaData != null) { + try { + final Resources pkgRes = pm.getResourcesForActivity(cn); + final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY); + name = pkgRes.getString(nameResId); + final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY); + icon = pkgRes.getDrawable(resId); + } catch (Resources.NotFoundException ex) { + } catch (PackageManager.NameNotFoundException ex) { + } + } + if (TextUtils.isEmpty(name)) { + name = resolveInfo.loadLabel(pm); + } + if (icon == null) { + icon = resolveInfo.loadIcon(pm); + } + + final DisplayResolveInfo dri = new DisplayResolveInfo( + originalIntent, resolveInfo, name, "", resolveIntent, null); + dri.setDisplayIcon(icon); + return dri; + } + + private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) { + Button b = (Button) LayoutInflater.from(getContext()).inflate( + com.android.internal.R.layout.chooser_action_button, null); + if (icon != null) { + final int size = getResources() + .getDimensionPixelSize( + com.android.internal.R.dimen.chooser_action_button_icon_size); + icon.setBounds(0, 0, size, size); + b.setCompoundDrawablesRelative(icon, null, null, null); + } + b.setText(title); + b.setOnClickListener(r); + return b; + } + + private void addActionButton(ViewGroup parent, Button b) { + if (b == null) return; + final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + final int gap = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.resolver_icon_margin) / 2; + lp.setMarginsRelative(gap, 0, gap, 0); + parent.addView(b, lp); + } + + @VisibleForTesting + @Nullable + Button createNearbyButton(Intent originalIntent, View.OnClickListener r) { + final TargetInfo ti = getNearbySharingTarget(originalIntent); + if (ti == null) return null; + + return createActionButton(ti.getDisplayIcon(getContext()), ti.getDisplayLabel(), r); + } + private void setQrCode() { try { final int qrcodeSize = getContext().getResources().getDimensionPixelSize( diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java index bd3eaa661dd..2957e1fb8bd 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java +++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java @@ -145,7 +145,7 @@ public class WifiDppUtils { return wifiConfiguration.preSharedKey; } - private static String removeFirstAndLastDoubleQuotes(String str) { + static String removeFirstAndLastDoubleQuotes(String str) { if (TextUtils.isEmpty(str)) { return str; } diff --git a/tests/robotests/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragmentTest.java b/tests/robotests/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragmentTest.java new file mode 100644 index 00000000000..b36d7f5a9e2 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragmentTest.java @@ -0,0 +1,161 @@ +/* + * 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.wifi.dpp; + +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.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; + +import androidx.fragment.app.FragmentTransaction; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; + +@RunWith(AndroidJUnit4.class) +public class WifiDppQrCodeGeneratorFragmentTest { + + private WifiDppConfiguratorActivity mActivity; + private WifiDppQrCodeGeneratorFragment mFragment; + private Context mContext; + + + @Before + public void setUp() { + Intent intent = + new Intent(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR); + intent.putExtra(WifiDppUtils.EXTRA_WIFI_SSID, "GoogleGuest"); + intent.putExtra(WifiDppUtils.EXTRA_WIFI_SECURITY, "WPA"); + intent.putExtra(WifiDppUtils.EXTRA_WIFI_PRE_SHARED_KEY, "\\012345678,"); + + MockitoAnnotations.initMocks(this); + mActivity = Robolectric.setupActivity(WifiDppConfiguratorActivity.class); + mActivity.setWifiNetworkConfig(WifiNetworkConfig.getValidConfigOrNull(intent)); + mActivity.startActivity(intent); + + mFragment = spy(new WifiDppQrCodeGeneratorFragment()); + FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction(); + ft.add(mFragment, null); + ft.commit(); + + mContext = spy(InstrumentationRegistry.getTargetContext()); + when(mFragment.getContext()).thenReturn(mContext); + } + + @Test + public void rotateScreen_shouldNotCrash() { + mActivity.setRequestedOrientation( + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + mActivity.setRequestedOrientation( + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + + @Test + public void createNearbyButton_returnsNull() { + assertThat(mFragment.createNearbyButton(new Intent(), v -> { + })).isNull(); + } + + private static ResolveInfo createResolveInfo(int userId) { + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = createActivityInfo(); + resolveInfo.targetUserId = userId; + return resolveInfo; + } + + private static ActivityInfo createActivityInfo() { + ActivityInfo ai = new ActivityInfo(); + ai.name = "activity_name"; + ai.packageName = "foo_bar"; + ai.enabled = true; + ai.exported = true; + ai.permission = null; + ai.applicationInfo = new ApplicationInfo(); + ai.applicationInfo.packageName = "com.google.android.gms"; + + Bundle metadata = mock(Bundle.class); + when(metadata.getInt(anyString())).thenReturn(1); + ai.metaData = metadata; + return ai; + } + + @Test + public void createNearbyButtonFromSetting_notNull() + throws PackageManager.NameNotFoundException { + doReturn(ComponentName.unflattenFromString( + "com.google.android.gms/com.google.android.gms.nearby.sharing.ShareSheetActivity")) + .when(mFragment).getNearbySharingComponent(); + PackageManager packageManager = mock(PackageManager.class); + doReturn(createResolveInfo(0)).when(packageManager).resolveActivity(any(), anyInt()); + + Resources resources = mock(Resources.class); + when(resources.getString(anyInt())).thenReturn("Nearby"); + Drawable drawable = mock(Drawable.class); + when(resources.getDrawable(anyInt())).thenReturn(drawable); + + when(packageManager.getResourcesForActivity(any())).thenReturn(resources); + + when(mContext.getPackageManager()).thenReturn(packageManager); + + + assertThat(mFragment.createNearbyButton(new Intent(), v -> { + })).isNotNull(); + } + + @Test + public void createNearbyButtonFromConfig_notNull() throws PackageManager.NameNotFoundException { + doReturn( + "com.google.android.gms/com.google.android.gms.nearby.sharing.ShareSheetActivity") + .when(mFragment).getString(anyInt()); + PackageManager packageManager = mock(PackageManager.class); + doReturn(createResolveInfo(0)).when(packageManager).resolveActivity(any(), anyInt()); + + Resources resources = mock(Resources.class); + when(resources.getString(anyInt())).thenReturn("Nearby"); + Drawable drawable = mock(Drawable.class); + when(resources.getDrawable(anyInt())).thenReturn(drawable); + + when(packageManager.getResourcesForActivity(any())).thenReturn(resources); + + when(mContext.getPackageManager()).thenReturn(packageManager); + + + assertThat(mFragment.createNearbyButton(new Intent(), v -> { + })).isNotNull(); + } +}