424 lines
16 KiB
Java
424 lines
16 KiB
Java
package net.typeblog.shelter.ui;
|
|
|
|
import androidx.activity.result.ActivityResultLauncher;
|
|
import androidx.activity.result.contract.ActivityResultContract;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.fragment.app.Fragment;
|
|
|
|
import android.app.admin.DevicePolicyManager;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.os.Bundle;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.setupwizardlib.SetupWizardLayout;
|
|
import com.android.setupwizardlib.view.NavigationBar;
|
|
|
|
import net.typeblog.shelter.R;
|
|
import net.typeblog.shelter.receivers.ShelterDeviceAdminReceiver;
|
|
import net.typeblog.shelter.util.AuthenticationUtility;
|
|
import net.typeblog.shelter.util.LocalStorageManager;
|
|
import net.typeblog.shelter.util.Utility;
|
|
|
|
public class SetupWizardActivity extends AppCompatActivity {
|
|
// RESUME_SETUP should be used when MainActivity detects the provisioning has been
|
|
// finished by the system, but the Shelter inside the profile has never been brought up
|
|
// due to the user having not clicked on the notification yet (on Android 7 or lower).
|
|
// TODO: When we remove support for Android 7, get rid of all of these nonsense :)
|
|
public static final String ACTION_RESUME_SETUP = "net.typeblog.shelter.RESUME_SETUP";
|
|
public static final String ACTION_PROFILE_PROVISIONED = "net.typeblog.shelter.PROFILE_PROVISIONED";
|
|
|
|
private DevicePolicyManager mPolicyManager = null;
|
|
private LocalStorageManager mStorage = null;
|
|
|
|
private final ActivityResultLauncher<Void> mProvisionProfile =
|
|
registerForActivityResult(new ProfileProvisionContract(), this::setupProfileCb);
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
// The user could click on the "finish provisioning" notification while having removed
|
|
// this activity from the recents stack, in which case the notification will start a new
|
|
// instance of activity
|
|
if (ACTION_PROFILE_PROVISIONED.equals(getIntent().getAction()) && Utility.isWorkProfileAvailable(this)) {
|
|
// ...in which case we should finish immediately and go back to MainActivity
|
|
startActivity(new Intent(this, MainActivity.class));
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
setContentView(R.layout.activity_setup_wizard);
|
|
mPolicyManager = getSystemService(DevicePolicyManager.class);
|
|
mStorage = LocalStorageManager.getInstance();
|
|
// Don't use switchToFragment for the first time
|
|
// because we don't want animation for the first fragment
|
|
// (it would have nothing to animate upon, resulting in a black background)
|
|
getSupportFragmentManager()
|
|
.beginTransaction()
|
|
.replace(R.id.setup_wizard_container,
|
|
ACTION_RESUME_SETUP.equals(getIntent().getAction()) ?
|
|
new ActionRequiredFragment() : new WelcomeFragment())
|
|
.commit();
|
|
}
|
|
|
|
@Override
|
|
protected void onNewIntent(Intent intent) {
|
|
super.onNewIntent(intent);
|
|
// DummyActivity will start this activity with an empty intent
|
|
// once the provision is finalized
|
|
if (ACTION_PROFILE_PROVISIONED.equals(intent.getAction()) && Utility.isWorkProfileAvailable(this))
|
|
finishWithResult(true);
|
|
}
|
|
|
|
private<T extends BaseWizardFragment> void switchToFragment(T fragment, boolean reverseAnimation) {
|
|
getSupportFragmentManager()
|
|
.beginTransaction()
|
|
.setCustomAnimations(
|
|
reverseAnimation ? R.anim.slide_in_from_left : R.anim.slide_in_from_right,
|
|
reverseAnimation ? R.anim.slide_out_to_right : R.anim.slide_out_to_left
|
|
)
|
|
.replace(R.id.setup_wizard_container, fragment)
|
|
.commit();
|
|
}
|
|
|
|
private void finishWithResult(boolean succeeded) {
|
|
setResult(succeeded ? RESULT_OK : RESULT_CANCELED);
|
|
finish();
|
|
}
|
|
|
|
private void setupProfile() {
|
|
if (!mPolicyManager.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)) {
|
|
switchToFragment(new FailedFragment(), false);
|
|
return;
|
|
}
|
|
|
|
// The user may have aborted provisioning before without clearing data
|
|
// This can cause issues if the authentication utility thinks we
|
|
// could do authentication due to the presence of keys
|
|
AuthenticationUtility.reset();
|
|
|
|
try {
|
|
mProvisionProfile.launch(null);
|
|
} catch (ActivityNotFoundException e) {
|
|
// How could this fail???
|
|
switchToFragment(new FailedFragment(), false);
|
|
}
|
|
}
|
|
|
|
private void setupProfileCb(Boolean result) {
|
|
if (result) {
|
|
if (Utility.isWorkProfileAvailable(this)) {
|
|
// On Oreo and later versions, since we make use of the activity intent
|
|
// ACTION_PROVISIONING_SUCCESSFUL, the provisioning UI will not finish
|
|
// until that activity returns. In this case, there is really no need for us
|
|
// to do anything else here (and this callback may not even be called because
|
|
// the activity will likely be already finished by this point).
|
|
// There is no need for more action
|
|
finishWithResult(true);
|
|
return;
|
|
}
|
|
|
|
// Provisioning finished, but we still need to tell the user
|
|
// to click on the notification to bring up Shelter inside the
|
|
// profile. Otherwise, the setup will not be complete
|
|
mStorage.setBoolean(LocalStorageManager.PREF_IS_SETTING_UP, true);
|
|
switchToFragment(new ActionRequiredFragment(), false);
|
|
} else {
|
|
switchToFragment(new FailedFragment(), false);
|
|
}
|
|
}
|
|
|
|
public static class SetupWizardContract extends ActivityResultContract<Void, Boolean> {
|
|
@NonNull
|
|
@Override
|
|
public Intent createIntent(@NonNull Context context, Void input) {
|
|
return new Intent(context, SetupWizardActivity.class);
|
|
}
|
|
|
|
@Override
|
|
public Boolean parseResult(int resultCode, @Nullable Intent intent) {
|
|
return resultCode == RESULT_OK;
|
|
}
|
|
}
|
|
|
|
public static class ResumeSetupContract extends ActivityResultContract<Void, Boolean> {
|
|
@NonNull
|
|
@Override
|
|
public Intent createIntent(@NonNull Context context, Void input) {
|
|
Intent intent = new Intent(context, SetupWizardActivity.class);
|
|
intent.setAction(ACTION_RESUME_SETUP);
|
|
return intent;
|
|
}
|
|
|
|
@Override
|
|
public Boolean parseResult(int resultCode, @Nullable Intent intent) {
|
|
return resultCode == RESULT_OK;
|
|
}
|
|
}
|
|
|
|
private static class ProfileProvisionContract extends ActivityResultContract<Void, Boolean> {
|
|
@NonNull
|
|
@Override
|
|
public Intent createIntent(@NonNull Context context, Void input) {
|
|
ComponentName admin = new ComponentName(context.getApplicationContext(), ShelterDeviceAdminReceiver.class);
|
|
Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
|
|
intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, true);
|
|
intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, admin);
|
|
return intent;
|
|
}
|
|
|
|
@Override
|
|
public Boolean parseResult(int resultCode, @Nullable Intent intent) {
|
|
return resultCode == RESULT_OK;
|
|
}
|
|
}
|
|
|
|
// ==== SetupWizard steps ====
|
|
private static abstract class BaseWizardFragment extends Fragment implements NavigationBar.NavigationBarListener {
|
|
protected SetupWizardActivity mActivity = null;
|
|
protected SetupWizardLayout mWizard = null;
|
|
|
|
protected abstract int getLayoutResource();
|
|
|
|
@Override
|
|
public void onNavigateBack() {
|
|
// For sub-classes to implement
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateNext() {
|
|
// For sub-classes to implement
|
|
}
|
|
|
|
@Override
|
|
public void onAttach(@NonNull Context context) {
|
|
super.onAttach(context);
|
|
mActivity = (SetupWizardActivity) getActivity();
|
|
}
|
|
|
|
@Override
|
|
public void onDetach() {
|
|
super.onDetach();
|
|
mActivity = null;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
View view = inflater.inflate(getLayoutResource(), container, false);
|
|
mWizard = view.findViewById(R.id.wizard);
|
|
mWizard.getNavigationBar().setNavigationBarListener(this);
|
|
mWizard.setLayoutBackground(ContextCompat.getDrawable(inflater.getContext(), R.color.colorAccent));
|
|
return view;
|
|
}
|
|
}
|
|
|
|
protected static abstract class TextWizardFragment extends BaseWizardFragment {
|
|
protected abstract int getTextRes();
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
TextView tv = view.findViewById(R.id.setup_wizard_generic_text);
|
|
tv.setText(getTextRes());
|
|
}
|
|
}
|
|
|
|
public static class WelcomeFragment extends TextWizardFragment {
|
|
@Override
|
|
protected int getLayoutResource() {
|
|
return R.layout.fragment_setup_wizard_generic_text;
|
|
}
|
|
|
|
@Override
|
|
protected int getTextRes() {
|
|
return R.string.setup_wizard_welcome_text;
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateNext() {
|
|
super.onNavigateNext();
|
|
mActivity.switchToFragment(new PermissionsFragment(), false);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
mWizard.setHeaderText(R.string.setup_wizard_welcome);
|
|
mWizard.getNavigationBar().getBackButton().setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
public static class PermissionsFragment extends TextWizardFragment {
|
|
@Override
|
|
protected int getLayoutResource() {
|
|
return R.layout.fragment_setup_wizard_generic_text;
|
|
}
|
|
|
|
@Override
|
|
protected int getTextRes() {
|
|
return R.string.setup_wizard_permissions_text;
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateBack() {
|
|
super.onNavigateBack();
|
|
mActivity.switchToFragment(new WelcomeFragment(), true);
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateNext() {
|
|
super.onNavigateNext();
|
|
mActivity.switchToFragment(new CompatibilityFragment(), false);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
mWizard.setHeaderText(R.string.setup_wizard_permissions);
|
|
}
|
|
}
|
|
|
|
public static class CompatibilityFragment extends TextWizardFragment {
|
|
@Override
|
|
protected int getLayoutResource() {
|
|
return R.layout.fragment_setup_wizard_generic_text;
|
|
}
|
|
|
|
@Override
|
|
protected int getTextRes() {
|
|
return R.string.setup_wizard_compatibility_text;
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateBack() {
|
|
super.onNavigateBack();
|
|
mActivity.switchToFragment(new PermissionsFragment(), true);
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateNext() {
|
|
super.onNavigateNext();
|
|
mActivity.switchToFragment(new ReadyFragment(), false);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
mWizard.setHeaderText(R.string.setup_wizard_compatibility);
|
|
}
|
|
}
|
|
|
|
public static class ReadyFragment extends TextWizardFragment {
|
|
@Override
|
|
protected int getLayoutResource() {
|
|
return R.layout.fragment_setup_wizard_generic_text;
|
|
}
|
|
|
|
@Override
|
|
protected int getTextRes() {
|
|
return R.string.setup_wizard_ready_text;
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateBack() {
|
|
super.onNavigateBack();
|
|
mActivity.switchToFragment(new CompatibilityFragment(), true);
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateNext() {
|
|
super.onNavigateNext();
|
|
mActivity.switchToFragment(new PleaseWaitFragment(), false);
|
|
mActivity.setupProfile();
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
mWizard.setHeaderText(R.string.setup_wizard_ready);
|
|
}
|
|
}
|
|
|
|
public static class PleaseWaitFragment extends TextWizardFragment {
|
|
@Override
|
|
protected int getLayoutResource() {
|
|
return R.layout.fragment_setup_wizard_generic_text;
|
|
}
|
|
|
|
@Override
|
|
protected int getTextRes() {
|
|
return R.string.setup_wizard_please_wait_text;
|
|
}
|
|
|
|
@Override
|
|
public void onAttach(@NonNull Context context) {
|
|
super.onAttach(context);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
mWizard.setHeaderText(R.string.setup_wizard_please_wait);
|
|
mWizard.setProgressBarColor(view.getContext().getColorStateList(R.color.setup_wizard_progress_bar));
|
|
mWizard.setProgressBarShown(true);
|
|
mWizard.getNavigationBar().getBackButton().setVisibility(View.GONE);
|
|
mWizard.getNavigationBar().getNextButton().setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
public static class ActionRequiredFragment extends TextWizardFragment {
|
|
@Override
|
|
protected int getLayoutResource() {
|
|
return R.layout.fragment_setup_wizard_generic_text;
|
|
}
|
|
|
|
@Override
|
|
protected int getTextRes() {
|
|
return R.string.setup_wizard_action_required_text;
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
mWizard.setHeaderText(R.string.setup_wizard_action_required);
|
|
mWizard.setProgressBarColor(view.getContext().getColorStateList(R.color.setup_wizard_progress_bar));
|
|
mWizard.setProgressBarShown(true);
|
|
mWizard.getNavigationBar().getBackButton().setVisibility(View.GONE);
|
|
mWizard.getNavigationBar().getNextButton().setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
public static class FailedFragment extends TextWizardFragment {
|
|
@Override
|
|
protected int getLayoutResource() {
|
|
return R.layout.fragment_setup_wizard_generic_text;
|
|
}
|
|
|
|
@Override
|
|
protected int getTextRes() {
|
|
return R.string.setup_wizard_failed_text;
|
|
}
|
|
|
|
@Override
|
|
public void onNavigateNext() {
|
|
super.onNavigateNext();
|
|
mActivity.finishWithResult(false);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
mWizard.setHeaderText(R.string.setup_wizard_failed);
|
|
mWizard.getNavigationBar().getBackButton().setVisibility(View.GONE);
|
|
}
|
|
}
|
|
} |