diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 1bd37da5700..ffdc7e83217 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4991,6 +4991,11 @@ + + diff --git a/res/drawable/ic_privatespace_icon.xml b/res/drawable/ic_privatespace_icon.xml new file mode 100644 index 00000000000..b651f5235e0 --- /dev/null +++ b/res/drawable/ic_privatespace_icon.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/res/drawable/privatespace_placeholder_image.xml b/res/drawable/privatespace_placeholder_image.xml new file mode 100644 index 00000000000..cde503ae4b2 --- /dev/null +++ b/res/drawable/privatespace_placeholder_image.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/privatespace_education_screen.xml b/res/layout/privatespace_education_screen.xml new file mode 100644 index 00000000000..e93ebfe06b2 --- /dev/null +++ b/res/layout/privatespace_education_screen.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/privatespace_setup_root.xml b/res/layout/privatespace_setup_root.xml new file mode 100644 index 00000000000..79d63ad0ea4 --- /dev/null +++ b/res/layout/privatespace_setup_root.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/res/navigation/privatespace_main_context_nav.xml b/res/navigation/privatespace_main_context_nav.xml new file mode 100644 index 00000000000..f92e5720194 --- /dev/null +++ b/res/navigation/privatespace_main_context_nav.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index cd451105a7f..3f6f65e4852 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1239,6 +1239,24 @@ Set screen lock Cancel + + Cancel + + Set up + + Set up Private Space + + Hide private apps in a secure space that only you can access + + How it works + + You can access Private Space from the bottom of your apps list + + Apps in Private Space are protected by a lock + + Notifications from apps in Private Space are hidden when it\'s locked + + Private Space apps won\'t appear in permission manager, privacy dashboard, and other settings when Private Space is locked You can add up to %d fingerprints diff --git a/res/values/styles.xml b/res/values/styles.xml index 1f5c743817e..3a2f9ade378 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -944,4 +944,40 @@ google-sans-text + + + + + + + + + + diff --git a/res/xml/privatespace_setup_education_entries.xml b/res/xml/privatespace_setup_education_entries.xml new file mode 100644 index 00000000000..18fb19d3f06 --- /dev/null +++ b/res/xml/privatespace_setup_education_entries.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + diff --git a/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivity.java b/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivity.java index 50a44e1783d..3f212b13b16 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivity.java +++ b/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivity.java @@ -92,20 +92,24 @@ public class PrivateSpaceAuthenticationActivity extends FragmentActivity { } } - /** Show private space settings page on device lock authentications */ + /** Starts private space setup flow or the PS settings page on device lock authentication */ @VisibleForTesting public void onLockAuthentication(Context context) { - new SubSettingLauncher(context) - .setDestination(PrivateSpaceDashboardFragment.class.getName()) - .setTransitionType( - SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE) - .setSourceMetricsCategory(SettingsEnums.PRIVATE_SPACE_SETTINGS) - .launch(); + if (mPrivateSpaceMaintainer.doesPrivateSpaceExist()) { + new SubSettingLauncher(context) + .setDestination(PrivateSpaceDashboardFragment.class.getName()) + .setTransitionType( + SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE) + .setSourceMetricsCategory(SettingsEnums.PRIVATE_SPACE_SETTINGS) + .launch(); + } else { + startActivity(new Intent(context, PrivateSpaceSetupActivity.class)); + } } @VisibleForTesting public void setPrivateSpaceMaintainer(Injector injector) { - mPrivateSpaceMaintainer = injector.injectPrivateSpaceMaintainer(getApplicationContext()); + mPrivateSpaceMaintainer = injector.injectPrivateSpaceMaintainer(this); } private void promptToSetDeviceLock() { diff --git a/src/com/android/settings/privatespace/PrivateSpaceEducation.java b/src/com/android/settings/privatespace/PrivateSpaceEducation.java new file mode 100644 index 00000000000..817765f5372 --- /dev/null +++ b/src/com/android/settings/privatespace/PrivateSpaceEducation.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 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.privatespace; + +import android.app.Activity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.android.settings.R; + +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupcompat.template.FooterButton; +import com.google.android.setupdesign.GlifLayout; + +/** Fragment educating about the usage of Private Space. */ +public class PrivateSpaceEducation extends Fragment { + @Override + public View onCreateView( + LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + GlifLayout rootView = + (GlifLayout) + inflater.inflate(R.layout.privatespace_education_screen, container, false); + final FooterBarMixin mixin = rootView.getMixin(FooterBarMixin.class); + mixin.setPrimaryButton( + new FooterButton.Builder(getContext()) + .setText(R.string.privatespace_setup_button_label) + .setListener(onSetup()) + .setButtonType(FooterButton.ButtonType.NEXT) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) + .build()); + mixin.getPrimaryButtonView().setFilterTouchesWhenObscured(true); + mixin.setSecondaryButton( + new FooterButton.Builder(getContext()) + .setText(R.string.privatespace_cancel_label) + .setListener(onCancel()) + .setButtonType(FooterButton.ButtonType.CANCEL) + .setTheme( + com.google.android.setupdesign.R.style + .Base_TextAppearance_AppCompat_Widget_Button) + .build()); + mixin.getSecondaryButtonView().setFilterTouchesWhenObscured(true); + + return rootView; + } + + private View.OnClickListener onSetup() { + return v -> { + if (PrivateSpaceMaintainer.getInstance(getContext()).createPrivateSpace()) { + finishActivity(); + } + }; + } + + private View.OnClickListener onCancel() { + return v -> { + finishActivity(); + }; + } + + private void finishActivity() { + Activity activity = getActivity(); + if (activity != null) { + activity.finish(); + } + } +} diff --git a/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java b/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java new file mode 100644 index 00000000000..79e19fc5b9d --- /dev/null +++ b/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 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.privatespace; + +import android.os.Bundle; + +import androidx.fragment.app.FragmentActivity; +import androidx.navigation.fragment.NavHostFragment; + +import com.android.settings.R; +import com.android.settings.SetupWizardUtils; + +import com.google.android.setupdesign.util.ThemeHelper; + +public class PrivateSpaceSetupActivity extends FragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(SetupWizardUtils.getTheme(this, getIntent())); + ThemeHelper.trySetDynamicColor(this); + super.onCreate(savedInstanceState); + setContentView(R.layout.privatespace_setup_root); + NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() + .findFragmentById(R.id.ps_nav_host_fragment); + navHostFragment.getNavController().setGraph(R.navigation.privatespace_main_context_nav); + } +} diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivityTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivityTest.java index d2e12707fb8..4c6feea36ab 100644 --- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivityTest.java +++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivityTest.java @@ -16,14 +16,13 @@ package com.android.settings.privatespace; -import static com.android.settings.privatespace.PrivateSpaceSafetySource.SAFETY_SOURCE_ID; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; @@ -86,13 +85,17 @@ public class PrivateSpaceAuthenticationActivityTest { mPrivateSpaceAuthenticationActivity.setPrivateSpaceMaintainer(injector); } - /** Tests that on lock authentication Private space settings is launched. */ + /** Tests that when Private does not exist setup flow is started. */ + //TODO(b/307729746) Plan to add more tests for complete setup flow @Test @RequiresFlagsEnabled(Flags.FLAG_ALLOW_PRIVATE_PROFILE) - public void deviceSecurePrivateSpaceExists() { + public void whenPrivateProfileDoesNotExist_triggersSetupFlow() { + when(mPrivateSpaceMaintainer.doesPrivateSpaceExist()).thenReturn(false); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); mPrivateSpaceAuthenticationActivity.onLockAuthentication(mContext); verify(mPrivateSpaceAuthenticationActivity).startActivity(intentCaptor.capture()); - assertThat(intentCaptor.getValue().getIdentifier()).isEqualTo(SAFETY_SOURCE_ID); + assertThat(intentCaptor.getValue().getComponent().getClassName()) + .isEqualTo(PrivateSpaceSetupActivity.class.getName()); } } diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java index bb3f891a55c..cf9ea051ebf 100644 --- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java @@ -138,7 +138,9 @@ public class PrivateSpaceSafetySourceTest { any(), eq(SAFETY_SOURCE_ID), captor.capture(), eq(EVENT_TYPE_DEVICE_REBOOTED)); SafetySourceData safetySourceData = captor.getValue(); SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); - assertThat(safetySourceStatus.getPendingIntent().getIntent() - .equals(PrivateSpaceAuthenticationActivity.class)); + assertThat(safetySourceStatus.getPendingIntent().getIntent().getComponent().getClassName()) + .isEqualTo(PrivateSpaceAuthenticationActivity.class.getName()); + assertThat(safetySourceStatus.getPendingIntent().getIntent().getIdentifier()) + .isEqualTo(SAFETY_SOURCE_ID); } }