1 /* 2 * Copyright 2019, 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 package com.android.managedprovisioning.preprovisioning.consent; 17 18 import static com.android.internal.util.Preconditions.checkNotNull; 19 import static java.util.Collections.emptyList; 20 import static java.util.Collections.unmodifiableList; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.Activity; 25 import android.content.Intent; 26 import android.graphics.Bitmap; 27 import android.graphics.BitmapFactory; 28 import android.graphics.drawable.BitmapDrawable; 29 import android.graphics.drawable.Drawable; 30 import android.text.Spannable; 31 import android.text.SpannableString; 32 import android.text.Spanned; 33 import android.text.TextUtils; 34 import android.text.method.LinkMovementMethod; 35 import android.text.style.ClickableSpan; 36 import android.view.View; 37 import android.widget.Button; 38 import android.widget.ImageView; 39 import android.widget.TextView; 40 import com.android.managedprovisioning.R; 41 import com.android.managedprovisioning.common.AccessibilityContextMenuMaker; 42 import com.android.managedprovisioning.common.ClickableSpanFactory; 43 import com.android.managedprovisioning.common.ProvisionLogger; 44 import com.android.managedprovisioning.common.StringConcatenator; 45 import com.android.managedprovisioning.common.TouchTargetEnforcer; 46 import com.android.managedprovisioning.common.Utils; 47 import com.android.managedprovisioning.model.CustomizationParams; 48 import com.android.managedprovisioning.preprovisioning.PreProvisioningController.UiParams; 49 import com.android.managedprovisioning.preprovisioning.anim.BenefitsAnimation; 50 import com.google.android.setupdesign.GlifLayout; 51 import java.util.ArrayList; 52 import java.util.List; 53 54 /** 55 * Implements functionality for the legacy consent screen. 56 * <p>Android Auto still uses the old UI, so we need to ensure compatibility with it. 57 */ 58 class LegacyConsentUiHelper implements ConsentUiHelper { 59 private static final List<Integer> SLIDE_CAPTIONS = createImmutableList( 60 R.string.info_anim_title_0, 61 R.string.info_anim_title_1, 62 R.string.info_anim_title_2); 63 private static final List<Integer> SLIDE_CAPTIONS_COMP = createImmutableList( 64 R.string.info_anim_title_0, 65 R.string.one_place_for_work_apps, 66 R.string.info_anim_title_2); 67 68 private final Activity mActivity; 69 private final ClickableSpanFactory mClickableSpanFactory; 70 private final TouchTargetEnforcer mTouchTargetEnforcer; 71 private final ConsentUiHelperCallback mCallback; 72 private final Utils mUtils; 73 private @Nullable BenefitsAnimation mBenefitsAnimation; 74 private final AccessibilityContextMenuMaker mContextMenuMaker; 75 LegacyConsentUiHelper(Activity activity, AccessibilityContextMenuMaker contextMenuMaker, ConsentUiHelperCallback callback, Utils utils)76 LegacyConsentUiHelper(Activity activity, AccessibilityContextMenuMaker contextMenuMaker, 77 ConsentUiHelperCallback callback, Utils utils) { 78 mActivity = checkNotNull(activity); 79 mContextMenuMaker = contextMenuMaker; 80 mCallback = callback; 81 mTouchTargetEnforcer = 82 new TouchTargetEnforcer(activity.getResources().getDisplayMetrics().density); 83 mClickableSpanFactory = new ClickableSpanFactory(mActivity.getColor(R.color.blue_text)); 84 mUtils = utils; 85 } 86 87 @Override onStart()88 public void onStart() { 89 if (mBenefitsAnimation != null) { 90 mBenefitsAnimation.start(); 91 } 92 } 93 94 @Override onStop()95 public void onStop() { 96 if (mBenefitsAnimation != null) { 97 mBenefitsAnimation.stop(); 98 } 99 } 100 setDpcIconAndLabel(@onNull String appName, Drawable packageIcon, String orgName)101 private void setDpcIconAndLabel(@NonNull String appName, Drawable packageIcon, String orgName) { 102 if (packageIcon == null || TextUtils.isEmpty(appName)) { 103 return; 104 } 105 106 // make a container with all parts of DPC app description visible 107 mActivity.findViewById(R.id.intro_device_owner_app_info_container) 108 .setVisibility(View.VISIBLE); 109 110 if (TextUtils.isEmpty(orgName)) { 111 orgName = mActivity.getString(R.string.your_organization_beginning); 112 } 113 String message = mActivity.getString(R.string.your_org_app_used, orgName); 114 TextView appInfoText = mActivity.findViewById(R.id.device_owner_app_info_text); 115 appInfoText.setText(message); 116 117 ImageView imageView = mActivity.findViewById(R.id.device_manager_icon_view); 118 imageView.setImageDrawable(packageIcon); 119 imageView.setContentDescription( 120 mActivity.getResources().getString(R.string.mdm_icon_label, appName)); 121 122 TextView deviceManagerName = mActivity.findViewById(R.id.device_manager_name); 123 deviceManagerName.setText(appName); 124 } 125 126 // TODO: refactor complex localized string assembly to an abstraction http://b/34288292 127 // there is a bit of copy-paste, and some details easy to forget (e.g. setMovementMethod) assembleDOTermsMessage( @onNull String termsHeaders, String orgName, Intent showTermsIntent)128 private Spannable assembleDOTermsMessage( 129 @NonNull String termsHeaders, String orgName, Intent showTermsIntent) { 130 String linkText = mActivity.getString(R.string.view_terms); 131 132 if (TextUtils.isEmpty(orgName)) { 133 orgName = mActivity.getString(R.string.your_organization_middle); 134 } 135 String messageText = termsHeaders.isEmpty() 136 ? mActivity.getString(R.string.device_owner_info, orgName, linkText) 137 : mActivity 138 .getString(R.string.device_owner_info_with_terms_headers, orgName, termsHeaders, 139 linkText); 140 141 Spannable result = new SpannableString(messageText); 142 int start = messageText.indexOf(linkText); 143 144 ClickableSpan span = mClickableSpanFactory.create(showTermsIntent); 145 result.setSpan(span, start, start + linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 146 return result; 147 } 148 createImmutableList(int... values)149 private static List<Integer> createImmutableList(int... values) { 150 if (values == null || values.length == 0) { 151 return emptyList(); 152 } 153 List<Integer> result = new ArrayList<>(values.length); 154 for (int value : values) { 155 result.add(value); 156 } 157 return unmodifiableList(result); 158 } 159 160 @Override initiateUi(UiParams uiParams)161 public void initiateUi(UiParams uiParams) { 162 int layoutResId = 0; 163 int titleResId = 0; 164 int headerResId = 0; 165 String packageLabel = null; 166 Drawable packageIcon = null; 167 if (mUtils.isProfileOwnerAction(uiParams.provisioningAction)) { 168 titleResId = R.string.setup_profile; 169 headerResId = R.string.work_profile_provisioning_accept_header; 170 layoutResId = R.layout.intro_profile_owner; 171 } else if (mUtils.isDeviceOwnerAction(uiParams.provisioningAction)) { 172 titleResId = R.string.setup_device; 173 headerResId = R.string.fully_managed_device_provisioning_accept_header; 174 layoutResId = R.layout.intro_device_owner; 175 packageLabel = uiParams.packageInfo != null 176 ? uiParams.packageInfo.appLabel 177 : uiParams.deviceAdminLabel != null 178 ? uiParams.deviceAdminLabel : uiParams.packageName; 179 packageIcon = uiParams.packageInfo != null ? uiParams.packageInfo.packageIcon 180 : getDeviceAdminIconDrawable(uiParams.deviceAdminIconFilePath); 181 } 182 183 final CustomizationParams customization = uiParams.customization; 184 mCallback.initializeLayoutParams( 185 layoutResId, 186 headerResId, 187 customization); 188 189 setupAcceptAndContinueButton(uiParams.isSilentProvisioning); 190 191 mActivity.setTitle(titleResId); 192 193 final String headers = 194 new StringConcatenator(mActivity.getResources()).join(uiParams.disclaimerHeadings); 195 if (mUtils.isProfileOwnerAction(uiParams.provisioningAction)) { 196 initiateUIProfileOwner(headers, uiParams.isDeviceManaged, customization, 197 uiParams.viewTermsIntent); 198 } else if (mUtils.isDeviceOwnerAction(uiParams.provisioningAction)){ 199 initiateUIDeviceOwner(packageLabel, packageIcon, headers, customization, 200 uiParams.viewTermsIntent); 201 } 202 } 203 setupAcceptAndContinueButton(boolean isSilentProvisioning)204 private void setupAcceptAndContinueButton(boolean isSilentProvisioning) { 205 final GlifLayout layout = mActivity.findViewById(R.id.setup_wizard_layout); 206 Utils.addAcceptAndContinueButton(layout, v -> onNextButtonClicked()); 207 if (isSilentProvisioning) { 208 onNextButtonClicked(); 209 } 210 } 211 onNextButtonClicked()212 private void onNextButtonClicked() { 213 ProvisionLogger.logi("Next button (next_button) is clicked."); 214 mCallback.nextAfterUserConsent(); 215 } 216 initiateUIProfileOwner(String termsHeaders, boolean isComp, CustomizationParams customizationParams, Intent showTermsIntent)217 private void initiateUIProfileOwner(String termsHeaders, boolean isComp, 218 CustomizationParams customizationParams, Intent showTermsIntent) { 219 // set up the back button 220 Button backButton = mActivity.findViewById(R.id.back_button); 221 backButton.setOnClickListener(v -> { 222 ProvisionLogger.logi("Back button (back_button) is clicked."); 223 mActivity.onBackPressed(); 224 }); 225 226 int messageId = isComp ? R.string.profile_owner_info_comp : R.string.profile_owner_info; 227 int messageWithTermsId = isComp ? R.string.profile_owner_info_with_terms_headers_comp 228 : R.string.profile_owner_info_with_terms_headers; 229 230 // set the short info text 231 TextView shortInfo = mActivity.findViewById(R.id.profile_owner_short_info); 232 shortInfo.setText(termsHeaders.isEmpty() 233 ? mActivity.getString(messageId) 234 : mActivity.getResources().getString(messageWithTermsId, termsHeaders)); 235 236 // set up show terms button 237 View viewTermsButton = mActivity.findViewById(R.id.show_terms_button); 238 viewTermsButton.setOnClickListener(v -> mActivity.startActivity(showTermsIntent)); 239 mTouchTargetEnforcer.enforce(viewTermsButton, (View) viewTermsButton.getParent()); 240 241 // show the intro animation 242 mBenefitsAnimation = new BenefitsAnimation( 243 mActivity, 244 isComp 245 ? SLIDE_CAPTIONS_COMP 246 : SLIDE_CAPTIONS, 247 isComp 248 ? R.string.comp_profile_benefits_description 249 : R.string.profile_benefits_description, 250 customizationParams); 251 } 252 initiateUIDeviceOwner( String packageName, Drawable packageIcon, String termsHeaders, CustomizationParams customization, Intent showTermsIntent)253 private void initiateUIDeviceOwner( 254 String packageName, Drawable packageIcon, String termsHeaders, 255 CustomizationParams customization, Intent showTermsIntent) { 256 // set up the cancel button if it exists 257 View cancelView = mActivity.findViewById(R.id.close_button); 258 if (cancelView != null) { 259 cancelView.setOnClickListener(v -> { 260 ProvisionLogger.logi("Close button (close_button) is clicked."); 261 mActivity.onBackPressed(); 262 }); 263 } 264 265 // short terms info text with clickable 'view terms' link 266 TextView shortInfoText = mActivity.findViewById(R.id.device_owner_terms_info); 267 shortInfoText.setText(assembleDOTermsMessage( 268 termsHeaders, customization.orgName, showTermsIntent)); 269 shortInfoText.setMovementMethod(LinkMovementMethod.getInstance()); // make clicks work 270 mContextMenuMaker.registerWithActivity(shortInfoText); 271 272 handleSupportUrl(customization); 273 274 // set up DPC icon and label 275 setDpcIconAndLabel(packageName, packageIcon, customization.orgName); 276 } 277 handleSupportUrl(CustomizationParams customization)278 private void handleSupportUrl(CustomizationParams customization) { 279 final TextView info = mActivity.findViewById(R.id.device_owner_provider_info); 280 final String deviceProvider = mActivity.getString(R.string.organization_admin); 281 final String contactDeviceProvider = 282 mActivity.getString(R.string.contact_device_provider, deviceProvider); 283 mUtils.handleSupportUrl(mActivity, customization, mClickableSpanFactory, 284 mContextMenuMaker, info, deviceProvider, contactDeviceProvider); 285 } 286 getDeviceAdminIconDrawable(@ullable String deviceAdminIconFilePath)287 private Drawable getDeviceAdminIconDrawable(@Nullable String deviceAdminIconFilePath) { 288 if (deviceAdminIconFilePath == null) { 289 return null; 290 } 291 292 Bitmap bitmap = BitmapFactory.decodeFile(deviceAdminIconFilePath); 293 if (bitmap == null) { 294 return null; 295 } 296 return new BitmapDrawable(mActivity.getResources(), bitmap); 297 } 298 } 299