diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2d2f5843cf3..b69cac73ad3 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2959,6 +2959,17 @@ android:value="true" /> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/panel_layout.xml b/res/layout/panel_layout.xml new file mode 100644 index 00000000000..cbdd53fce50 --- /dev/null +++ b/res/layout/panel_layout.xml @@ -0,0 +1,26 @@ + + + + \ No newline at end of file diff --git a/res/layout/settings_panel.xml b/res/layout/settings_panel.xml new file mode 100644 index 00000000000..aec898c99c6 --- /dev/null +++ b/res/layout/settings_panel.xml @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index eb7cc2becf6..bec62e9ad57 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10299,8 +10299,8 @@ Unavailable when connected to %1$s - - Medical info, emergency contacts + + Medical info, emergency contacts See more @@ -10322,6 +10322,12 @@ No connected devices + + Settings Panel + + + Internet Connectivity + Force desktop mode diff --git a/res/values/themes.xml b/res/values/themes.xml index b280482de6f..873216d5ae9 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -200,4 +200,8 @@ ?android:attr/colorBackground + + diff --git a/res/xml/network_and_internet.xml b/res/xml/network_and_internet.xml index 8e36e919220..c21886965fa 100644 --- a/res/xml/network_and_internet.xml +++ b/res/xml/network_and_internet.xml @@ -64,7 +64,7 @@ settings:useAdminDisabledSummary="true" /> + * Displays Wifi (full Slice) and Airplane mode. + *

+ */ +public class InternetConnectivityPanel implements PanelContent { + + @VisibleForTesting + static final Uri AIRPLANE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSlicesContract.AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(SettingsSlicesContract.KEY_AIRPLANE_MODE) + .build(); + + private final Context mContext; + + public static InternetConnectivityPanel create(Context context) { + return new InternetConnectivityPanel(context); + } + + private InternetConnectivityPanel(Context context) { + mContext = context.getApplicationContext(); + } + + @Override + public String getTitle() { + return (String) mContext.getText(R.string.internet_connectivity_panel_title); + } + + @Override + public List getSlices() { + final List uris = new ArrayList<>(); + uris.add(WifiSlice.WIFI_URI); + uris.add(AIRPLANE_URI); + return uris; + } + + @Override + public Intent getSeeMoreIntent() { + return null; + } +} diff --git a/src/com/android/settings/panel/PanelContent.java b/src/com/android/settings/panel/PanelContent.java new file mode 100644 index 00000000000..bd84c2fa617 --- /dev/null +++ b/src/com/android/settings/panel/PanelContent.java @@ -0,0 +1,49 @@ +/* + * 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.panel; + +import android.content.Intent; +import android.net.Uri; + +import java.util.List; + +/** + * Represents the data class needed to create a Settings Panel. See {@link PanelFragment}. + */ +public interface PanelContent { + + /** + * @return a string for the title of the Panel. + */ + CharSequence getTitle(); + + /** + * @return an ordered list of the Slices to be displayed in the Panel. The first item in the + * list is shown on top of the Panel. + */ + List getSlices(); + + + /** + * @return an {@link Intent} to the full content in Settings that is summarized by the Panel. + * + *

+ * For example, for the connectivity panel you would intent to the Network & Internet page. + *

+ */ + Intent getSeeMoreIntent(); +} diff --git a/src/com/android/settings/panel/PanelFeatureProvider.java b/src/com/android/settings/panel/PanelFeatureProvider.java new file mode 100644 index 00000000000..7d6c5581d25 --- /dev/null +++ b/src/com/android/settings/panel/PanelFeatureProvider.java @@ -0,0 +1,27 @@ +/* + * 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.panel; + +import android.content.Context; + +public interface PanelFeatureProvider { + + /** + * Returns {@link PanelContent} as specified by the {@param panelType}. + */ + PanelContent getPanel(Context context, String panelType); +} diff --git a/src/com/android/settings/panel/PanelFeatureProviderImpl.java b/src/com/android/settings/panel/PanelFeatureProviderImpl.java new file mode 100644 index 00000000000..2e840786b7f --- /dev/null +++ b/src/com/android/settings/panel/PanelFeatureProviderImpl.java @@ -0,0 +1,32 @@ +/* + * 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.panel; + +import android.content.Context; + +public class PanelFeatureProviderImpl implements PanelFeatureProvider { + + @Override + public PanelContent getPanel(Context context, String panelType) { + switch (panelType) { + case SettingsPanelActivity.PANEL_TYPE_WIFI: + return InternetConnectivityPanel.create(context); + } + + throw new IllegalStateException("No matching panel for: " + panelType); + } +} diff --git a/src/com/android/settings/panel/PanelFragment.java b/src/com/android/settings/panel/PanelFragment.java new file mode 100644 index 00000000000..bbdaec351f4 --- /dev/null +++ b/src/com/android/settings/panel/PanelFragment.java @@ -0,0 +1,88 @@ +/* + * 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.panel; + +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import android.widget.LinearLayout; + +import androidx.lifecycle.LiveData; +import androidx.slice.Slice; +import androidx.slice.widget.SliceLiveData; +import androidx.slice.widget.SliceView; + +import com.android.settings.R; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.overlay.FeatureFactory; + +import java.util.ArrayList; +import java.util.List; + +public class PanelFragment extends Fragment { + + private static final String TAG = "PanelFragment"; + + private List mSliceViewList; + private List> mSliceDataList; + private LinearLayout mPanelLayout; + + public PanelFragment() { + mSliceViewList = new ArrayList<>(); + mSliceDataList = new ArrayList<>(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + final FragmentActivity activity = getActivity(); + final View view = inflater.inflate(R.layout.panel_layout, container, false); + + mPanelLayout = view.findViewById(R.id.panel_parent_layout); + final Bundle arguments = getArguments(); + + final String panelType = arguments.getString(SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT); + + final PanelContent panel = FeatureFactory.getFactory(activity) + .getPanelFeatureProvider() + .getPanel(activity, panelType); + + activity.setTitle(panel.getTitle()); + + + for (Uri uri : panel.getSlices()) { + final SliceView sliceView = new SliceView(activity); + mPanelLayout.addView(sliceView); + final LiveData liveData = SliceLiveData.fromUri(activity, uri); + liveData.observe(this /* lifecycleOwner */, sliceView); + + mSliceDataList.add(liveData); + mSliceViewList.add(sliceView); + } + + return view; + } +} diff --git a/src/com/android/settings/panel/SettingsPanelActivity.java b/src/com/android/settings/panel/SettingsPanelActivity.java new file mode 100644 index 00000000000..db1f60d3e49 --- /dev/null +++ b/src/com/android/settings/panel/SettingsPanelActivity.java @@ -0,0 +1,103 @@ +/* + * 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.panel; + +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; + +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.Window; +import android.view.WindowManager; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import com.android.settings.R; + +/** + * Dialog Activity to host Settings Slices. + * + * TODO link to action / framework API + */ +public class SettingsPanelActivity extends FragmentActivity { + + private final String TAG = "panel_activity"; + + /** + * Key specifying which Panel the app is requesting. + */ + public static final String KEY_PANEL_TYPE_ARGUMENT = "PANEL_TYPE_ARGUMENT"; + + + // TODO (b/117804442) move to framework + public static final String EXTRA_PANEL_TYPE = "com.android.settings.panel.extra"; + + // TODO (b/117804442) move to framework + public static final String PANEL_TYPE_WIFI = "wifi_panel"; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final ComponentName callingActivityName = getCallingActivity(); + + if (callingActivityName == null) { + Log.e(TAG, "Must start with startActivityForResult. Closing."); + finish(); + return; + } + + final Intent callingIntent = getIntent(); + if (callingIntent == null) { + Log.e(TAG, "Null intent, closing Panel Activity"); + finish(); + return; + } + + final String typeExtra = callingIntent.getStringExtra(EXTRA_PANEL_TYPE); + if (TextUtils.isEmpty(typeExtra)) { + Log.e(TAG, "No intent passed, closing Panel Activity"); + return; + } + + setContentView(R.layout.settings_panel); + + // Move the window to the bottom of screen, and make it take up the entire screen width. + final Window window = getWindow(); + window.setGravity(Gravity.BOTTOM); + window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT); + + + final Bundle bundle = new Bundle(); + bundle.putString(KEY_PANEL_TYPE_ARGUMENT, typeExtra); + + final PanelFragment panelFragment = new PanelFragment(); + panelFragment.setArguments(bundle); + + final FragmentManager fragmentManager = getSupportFragmentManager(); + final Fragment fragment = fragmentManager.findFragmentById(R.id.main_content); + if (fragment == null) { + fragmentManager.beginTransaction().add(R.id.main_content, panelFragment).commit(); + } + } +} diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index fa669bb451a..952fc8b4596 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -427,7 +427,7 @@ public class SettingsSliceProvider extends SliceProvider { try { sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri); } catch (IllegalStateException e) { - Log.d(TAG, "Could not create slicedata for uri: " + uri); + Log.d(TAG, "Could not create slicedata for uri: " + uri, e); return; } diff --git a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java index 39d385eb9d4..508eb1c1d78 100644 --- a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java +++ b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java @@ -1,3 +1,19 @@ +/* + * 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.slices; import android.content.Context; diff --git a/tests/robotests/assets/grandfather_not_implementing_instrumentable b/tests/robotests/assets/grandfather_not_implementing_instrumentable index 625d9ffecf2..04ef0ef9594 100644 --- a/tests/robotests/assets/grandfather_not_implementing_instrumentable +++ b/tests/robotests/assets/grandfather_not_implementing_instrumentable @@ -6,4 +6,5 @@ com.android.settings.password.ChooseLockPattern$SaveAndFinishWorker com.android.settings.RestrictedListPreference$RestrictedListPreferenceDialogFragment com.android.settings.password.ConfirmDeviceCredentialBaseFragment$LastTryDialog com.android.settings.password.CredentialCheckResultTracker -com.android.settings.dashboard.profileselector.ProfileSelectDialog \ No newline at end of file +com.android.settings.dashboard.profileselector.ProfileSelectDialog +com.android.settings.panel.PanelFragment \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java index 91a545c595d..46d177f88a9 100644 --- a/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java @@ -187,11 +187,4 @@ public class AirplaneModePreferenceControllerTest { new AirplaneModePreferenceController(mContext,"toggle_airplane"); assertThat(controller.isSliceable()).isTrue(); } - - @Test - public void isSliceableIncorrectKey_returnsFalse() { - final AirplaneModePreferenceController controller = - new AirplaneModePreferenceController(mContext, "bad_key"); - assertThat(controller.isSliceable()).isFalse(); - } } diff --git a/tests/robotests/src/com/android/settings/panel/InternetConnectivityPanelTest.java b/tests/robotests/src/com/android/settings/panel/InternetConnectivityPanelTest.java new file mode 100644 index 00000000000..3e210f5298c --- /dev/null +++ b/tests/robotests/src/com/android/settings/panel/InternetConnectivityPanelTest.java @@ -0,0 +1,52 @@ +/* + * 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.panel; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.wifi.WifiSlice; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) + +public class InternetConnectivityPanelTest { + + private InternetConnectivityPanel mPanel; + + @Before + public void setUp() { + mPanel = InternetConnectivityPanel.create(RuntimeEnvironment.application); + } + + @Test + public void getSlices_containsNecessarySlices() { + final List uris = mPanel.getSlices(); + + assertThat(uris).containsExactly(WifiSlice.WIFI_URI, + InternetConnectivityPanel.AIRPLANE_URI); + } +} diff --git a/tests/robotests/src/com/android/settings/panel/PanelFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/panel/PanelFeatureProviderImplTest.java new file mode 100644 index 00000000000..050fd0cd899 --- /dev/null +++ b/tests/robotests/src/com/android/settings/panel/PanelFeatureProviderImplTest.java @@ -0,0 +1,52 @@ +/* + * 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.panel; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class PanelFeatureProviderImplTest { + + private Context mContext; + private PanelFeatureProviderImpl mProvider; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mProvider = new PanelFeatureProviderImpl(); + } + + @Test + public void getPanel_internetConnectivityKey_returnsCorrectPanel() { + final PanelContent panel = mProvider.getPanel(mContext, + SettingsPanelActivity.PANEL_TYPE_WIFI); + + assertThat(panel).isInstanceOf(InternetConnectivityPanel.class); + } + +} diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java index 24db8298c80..e14ef1f4b9d 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -33,6 +33,7 @@ import com.android.settings.overlay.DockUpdaterFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.SupportFeatureProvider; import com.android.settings.overlay.SurveyFeatureProvider; +import com.android.settings.panel.PanelFeatureProvider; import com.android.settings.search.SearchFeatureProvider; import com.android.settings.security.SecurityFeatureProvider; import com.android.settings.slices.SlicesFeatureProvider; @@ -61,6 +62,7 @@ public class FakeFeatureFactory extends FeatureFactory { public final UserFeatureProvider userFeatureProvider; public final AssistGestureFeatureProvider assistGestureFeatureProvider; public final AccountFeatureProvider mAccountFeatureProvider; + public final PanelFeatureProvider mPanelFeatureProvider; public SlicesFeatureProvider slicesFeatureProvider; public SearchFeatureProvider searchFeatureProvider; @@ -102,6 +104,7 @@ public class FakeFeatureFactory extends FeatureFactory { assistGestureFeatureProvider = mock(AssistGestureFeatureProvider.class); slicesFeatureProvider = mock(SlicesFeatureProvider.class); mAccountFeatureProvider = mock(AccountFeatureProvider.class); + mPanelFeatureProvider = mock(PanelFeatureProvider.class); } @Override @@ -183,4 +186,9 @@ public class FakeFeatureFactory extends FeatureFactory { public AccountFeatureProvider getAccountFeatureProvider() { return mAccountFeatureProvider; } + + @Override + public PanelFeatureProvider getPanelFeatureProvider() { + return mPanelFeatureProvider; + } }