1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.devicelockcontroller.activities; 18 19 import static com.android.devicelockcontroller.common.DeviceLockConstants.ACTION_START_DEVICE_FINANCING_DEFERRED_PROVISIONING; 20 import static com.android.devicelockcontroller.common.DeviceLockConstants.ACTION_START_DEVICE_FINANCING_PROVISIONING; 21 import static com.android.devicelockcontroller.common.DeviceLockConstants.ACTION_START_DEVICE_FINANCING_SECONDARY_USER_PROVISIONING; 22 import static com.android.devicelockcontroller.common.DeviceLockConstants.ACTION_START_DEVICE_SUBSIDY_DEFERRED_PROVISIONING; 23 import static com.android.devicelockcontroller.common.DeviceLockConstants.ACTION_START_DEVICE_SUBSIDY_PROVISIONING; 24 25 import static com.google.common.base.Preconditions.checkNotNull; 26 27 import android.Manifest; 28 import android.app.PendingIntent; 29 import android.content.Intent; 30 import android.content.pm.PackageManager; 31 import android.os.Bundle; 32 import android.text.TextUtils; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.widget.Button; 37 import android.widget.ImageView; 38 import android.widget.TextView; 39 import android.widget.Toast; 40 41 import androidx.activity.result.ActivityResultLauncher; 42 import androidx.activity.result.contract.ActivityResultContracts; 43 import androidx.annotation.NonNull; 44 import androidx.annotation.Nullable; 45 import androidx.core.content.ContextCompat; 46 import androidx.fragment.app.Fragment; 47 import androidx.lifecycle.ViewModelProvider; 48 import androidx.recyclerview.widget.RecyclerView; 49 import androidx.work.WorkManager; 50 51 import com.android.devicelockcontroller.R; 52 import com.android.devicelockcontroller.provision.worker.PauseProvisioningWorker; 53 import com.android.devicelockcontroller.util.LogUtil; 54 55 import java.time.Duration; 56 import java.time.Instant; 57 import java.util.Objects; 58 59 /** 60 * The screen that provides information about the provision. 61 */ 62 public final class ProvisionInfoFragment extends Fragment { 63 64 private static final String TAG = "ProvisionInfoFragment"; 65 66 private ActivityResultLauncher<String> requestPermissionLauncher = 67 registerForActivityResult(new ActivityResultContracts.RequestPermission(), 68 isGranted -> { 69 if (isGranted) { 70 createNotificationAndCloseActivity(); 71 } else { 72 Toast.makeText(getActivity(), 73 R.string.toast_message_grant_notification_permission, 74 Toast.LENGTH_LONG).show(); 75 } 76 } 77 ); 78 79 @Nullable 80 @Override onCreateView( @onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)81 public View onCreateView( 82 @NonNull LayoutInflater inflater, 83 @Nullable ViewGroup container, 84 @Nullable Bundle savedInstanceState) { 85 return inflater.inflate(R.layout.fragment_provision_info, container, false); 86 } 87 88 @Override onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)89 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 90 super.onViewCreated(view, savedInstanceState); 91 ProvisionInfoViewModel viewModel; 92 boolean isDeferredProvisioning = false; 93 switch (Objects.requireNonNull(getActivity()).getIntent().getAction()) { 94 case ACTION_START_DEVICE_FINANCING_PROVISIONING: 95 viewModel = new ViewModelProvider(this).get( 96 DeviceFinancingProvisionInfoViewModel.class); 97 break; 98 case ACTION_START_DEVICE_FINANCING_DEFERRED_PROVISIONING: 99 viewModel = new ViewModelProvider(this).get( 100 DeviceFinancingDeferredProvisionInfoViewModel.class); 101 isDeferredProvisioning = true; 102 break; 103 case ACTION_START_DEVICE_FINANCING_SECONDARY_USER_PROVISIONING: 104 viewModel = new ViewModelProvider(this).get( 105 DeviceFinancingSecondaryUserProvisionInfoViewModel.class); 106 break; 107 case ACTION_START_DEVICE_SUBSIDY_PROVISIONING: 108 viewModel = new ViewModelProvider(this).get( 109 DeviceSubsidyProvisionInfoViewModel.class); 110 break; 111 case ACTION_START_DEVICE_SUBSIDY_DEFERRED_PROVISIONING: 112 viewModel = new ViewModelProvider(this).get( 113 DeviceSubsidyDeferredProvisionInfoViewModel.class); 114 isDeferredProvisioning = true; 115 break; 116 default: 117 LogUtil.e(TAG, "Unknown action is received, exiting"); 118 return; 119 } 120 121 RecyclerView recyclerView = view.findViewById(R.id.recyclerview_provision_info); 122 if (recyclerView == null) { 123 LogUtil.e(TAG, "Could not find provision info RecyclerView, should not reach here."); 124 return; 125 } 126 ProvisionInfoListAdapter adapter = new ProvisionInfoListAdapter(viewModel, 127 getViewLifecycleOwner()); 128 viewModel.mProvisionInfoListLiveData.observe(getViewLifecycleOwner(), 129 adapter::submitList); 130 recyclerView.setAdapter(adapter); 131 ImageView imageView = view.findViewById(R.id.header_icon); 132 if (imageView == null) { 133 LogUtil.e(TAG, "Could not find header ImageView, should not reach here."); 134 return; 135 } 136 viewModel.mHeaderDrawableIdLiveData.observe(getViewLifecycleOwner(), 137 imageView::setImageResource); 138 139 TextView headerTextView = view.findViewById(R.id.header_text); 140 if (headerTextView == null) { 141 LogUtil.e(TAG, "Could not find header TextView, should not reach here."); 142 return; 143 } 144 viewModel.mHeaderTextLiveData.observe(getViewLifecycleOwner(), 145 pair -> { 146 if (pair.first > 0 && !TextUtils.isEmpty(pair.second)) { 147 headerTextView.setText(getString(pair.first, pair.second)); 148 } 149 }); 150 151 TextView subheaderTextView = view.findViewById(R.id.subheader_text); 152 if (subheaderTextView == null) { 153 LogUtil.e(TAG, "Could not find subheader TextView, should not reach here."); 154 return; 155 } 156 viewModel.mSubHeaderTextLiveData.observe(getViewLifecycleOwner(), 157 pair -> { 158 if (pair.first > 0 && !TextUtils.isEmpty(pair.second)) { 159 headerTextView.setText(getString(pair.first, pair.second)); 160 } 161 }); 162 Button next = view.findViewById(R.id.button_next); 163 checkNotNull(next); 164 if (isDeferredProvisioning) { 165 next.setText(R.string.start); 166 } 167 next.setOnClickListener( 168 v -> startActivity(new Intent(getContext(), ProvisioningActivity.class))); 169 updatePreviousButton(checkNotNull(view.findViewById(R.id.button_previous)), viewModel, 170 isDeferredProvisioning); 171 } 172 updatePreviousButton(Button previous, ProvisionInfoViewModel viewModel, boolean isDeferredProvisioning)173 private void updatePreviousButton(Button previous, ProvisionInfoViewModel viewModel, 174 boolean isDeferredProvisioning) { 175 if (!isDeferredProvisioning) { 176 previous.setVisibility(View.GONE); 177 return; 178 } 179 previous.setText(R.string.do_it_in_one_hour); 180 previous.setVisibility(View.VISIBLE); 181 182 viewModel.mIsProvisionForcedLiveData.observe(getViewLifecycleOwner(), 183 isProvisionForced -> { 184 previous.setEnabled(!isProvisionForced); 185 // Allow the user to defer provisioning only when provisioning is not forced. 186 if (!isProvisionForced) { 187 previous.setOnClickListener( 188 v -> { 189 WorkManager workManager = 190 WorkManager.getInstance(requireContext()); 191 PauseProvisioningWorker 192 .reportProvisionPausedByUser(workManager); 193 int notificationPermission = ContextCompat.checkSelfPermission( 194 requireContext(), 195 Manifest.permission.POST_NOTIFICATIONS); 196 if (PackageManager.PERMISSION_GRANTED 197 == notificationPermission) { 198 createNotificationAndCloseActivity(); 199 } else { 200 requestPermissionLauncher.launch( 201 Manifest.permission.POST_NOTIFICATIONS); 202 } 203 }); 204 } 205 }); 206 } 207 createNotificationAndCloseActivity()208 private void createNotificationAndCloseActivity() { 209 PendingIntent intent = PendingIntent.getActivity( 210 requireContext(), 211 /* requestCode= */ 0, 212 getActivity().getIntent(), 213 PendingIntent.FLAG_IMMUTABLE); 214 Instant resumeTime = Instant.now().plus(Duration.ofHours(1)); 215 DeviceLockNotificationManager.sendDeferredEnrollmentNotification(requireContext(), 216 resumeTime, intent); 217 getActivity().finish(); 218 } 219 } 220