1 /* 2 * Copyright 2016, 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.managedprovisioning.parser; 18 19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; 20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE; 21 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; 22 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE; 23 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; 24 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE; 25 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE; 26 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME; 27 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE; 28 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM; 29 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER; 30 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION; 31 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI; 32 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL; 33 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME; 34 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM; 35 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DISCLAIMERS; 36 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION; 37 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED; 38 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE; 39 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME; 40 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOGO_URI; 41 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_MAIN_COLOR; 42 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ORGANIZATION_NAME; 43 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION; 44 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_USER_CONSENT; 45 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_USER_SETUP; 46 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SUPPORT_URL; 47 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE; 48 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN; 49 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL; 50 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD; 51 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS; 52 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST; 53 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT; 54 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE; 55 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID; 56 import static com.android.internal.util.Preconditions.checkNotNull; 57 import static com.android.managedprovisioning.common.Globals.ACTION_RESUME_PROVISIONING; 58 import static com.android.managedprovisioning.model.ProvisioningParams.inferStaticDeviceAdminPackageName; 59 60 import android.app.admin.DevicePolicyManager; 61 import android.content.ComponentName; 62 import android.content.Context; 63 import android.content.Intent; 64 import android.net.Uri; 65 import android.os.Bundle; 66 import android.os.PersistableBundle; 67 import android.support.annotation.Nullable; 68 import android.support.annotation.VisibleForTesting; 69 import com.android.managedprovisioning.common.IllegalProvisioningArgumentException; 70 import com.android.managedprovisioning.common.LogoUtils; 71 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences; 72 import com.android.managedprovisioning.common.ProvisionLogger; 73 import com.android.managedprovisioning.common.StoreUtils; 74 import com.android.managedprovisioning.common.Utils; 75 import com.android.managedprovisioning.model.DisclaimersParam; 76 import com.android.managedprovisioning.model.PackageDownloadInfo; 77 import com.android.managedprovisioning.model.ProvisioningParams; 78 import com.android.managedprovisioning.model.WifiInfo; 79 import java.util.Arrays; 80 import java.util.Collections; 81 import java.util.HashSet; 82 import java.util.Set; 83 84 /** 85 * A parser which parses provisioning data from intent which stores in {@link Bundle} extras. 86 */ 87 88 @VisibleForTesting 89 public class ExtrasProvisioningDataParser implements ProvisioningDataParser { 90 private static final Set<String> PROVISIONING_ACTIONS_SUPPORT_ALL_PROVISIONING_DATA = 91 new HashSet<>(Collections.singletonList( 92 ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE)); 93 94 private static final Set<String> PROVISIONING_ACTIONS_SUPPORT_MIN_PROVISIONING_DATA = 95 new HashSet<>(Arrays.asList( 96 ACTION_PROVISION_MANAGED_DEVICE, 97 ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, 98 ACTION_PROVISION_MANAGED_USER, 99 ACTION_PROVISION_MANAGED_PROFILE)); 100 101 private final Utils mUtils; 102 private final Context mContext; 103 private final ManagedProvisioningSharedPreferences mSharedPreferences; 104 ExtrasProvisioningDataParser(Context context, Utils utils)105 ExtrasProvisioningDataParser(Context context, Utils utils) { 106 this(context, utils, new ManagedProvisioningSharedPreferences(context)); 107 } 108 109 @VisibleForTesting ExtrasProvisioningDataParser(Context context, Utils utils, ManagedProvisioningSharedPreferences sharedPreferences)110 ExtrasProvisioningDataParser(Context context, Utils utils, 111 ManagedProvisioningSharedPreferences sharedPreferences) { 112 mContext = checkNotNull(context); 113 mUtils = checkNotNull(utils); 114 mSharedPreferences = checkNotNull(sharedPreferences); 115 } 116 117 @Override parse(Intent provisioningIntent)118 public ProvisioningParams parse(Intent provisioningIntent) 119 throws IllegalProvisioningArgumentException{ 120 String provisioningAction = provisioningIntent.getAction(); 121 if (ACTION_RESUME_PROVISIONING.equals(provisioningAction)) { 122 return provisioningIntent.getParcelableExtra( 123 ProvisioningParams.EXTRA_PROVISIONING_PARAMS); 124 } 125 if (PROVISIONING_ACTIONS_SUPPORT_MIN_PROVISIONING_DATA.contains(provisioningAction)) { 126 ProvisionLogger.logi("Processing mininalist extras intent."); 127 return parseMinimalistSupportedProvisioningDataInternal(provisioningIntent, mContext) 128 .build(); 129 } else if (PROVISIONING_ACTIONS_SUPPORT_ALL_PROVISIONING_DATA.contains( 130 provisioningAction)) { 131 return parseAllSupportedProvisioningData(provisioningIntent, mContext); 132 } else { 133 throw new IllegalProvisioningArgumentException("Unsupported provisioning action: " 134 + provisioningAction); 135 } 136 } 137 138 /** 139 * Parses minimal supported set of parameters from bundle extras of a provisioning intent. 140 * 141 * <p>Here is the list of supported parameters. 142 * <ul> 143 * <li>{@link EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li> 144 * <li> 145 * {@link EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} only in 146 * {@link ACTION_PROVISION_MANAGED_PROFILE}. 147 * </li> 148 * <li>{@link EXTRA_PROVISIONING_LOGO_URI}</li> 149 * <li>{@link EXTRA_PROVISIONING_MAIN_COLOR}</li> 150 * <li> 151 * {@link EXTRA_PROVISIONING_SKIP_USER_SETUP} only in 152 * {@link ACTION_PROVISION_MANAGED_USER} and {@link ACTION_PROVISION_MANAGED_DEVICE}. 153 * </li> 154 * <li>{@link EXTRA_PROVISIONING_SKIP_ENCRYPTION}</li> 155 * <li>{@link EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}</li> 156 * <li>{@link EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE}</li> 157 * <li>{@link EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}</li> 158 * <li>{@link EXTRA_PROVISIONING_SKIP_USER_CONSENT}</li> 159 * </ul> 160 */ parseMinimalistSupportedProvisioningDataInternal( Intent intent, Context context)161 private ProvisioningParams.Builder parseMinimalistSupportedProvisioningDataInternal( 162 Intent intent, Context context) 163 throws IllegalProvisioningArgumentException { 164 final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); 165 boolean isProvisionManagedDeviceFromTrustedSourceIntent = 166 ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE.equals(intent.getAction()); 167 try { 168 final long provisioningId = mSharedPreferences.incrementAndGetProvisioningId(); 169 String provisioningAction = mUtils.mapIntentToDpmAction(intent); 170 final boolean isManagedProfileAction = 171 ACTION_PROVISION_MANAGED_PROFILE.equals(provisioningAction); 172 173 // Parse device admin package name and component name. 174 ComponentName deviceAdminComponentName = (ComponentName) intent.getParcelableExtra( 175 EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME); 176 // Device admin package name is deprecated. It is only supported in Profile Owner 177 // provisioning and when resuming NFC provisioning. 178 String deviceAdminPackageName = null; 179 if (isManagedProfileAction) { 180 // In L, we only support package name. This means some DPC may still send us the 181 // device admin package name only. Attempts to obtain the package name from extras. 182 deviceAdminPackageName = intent.getStringExtra( 183 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME); 184 // For profile owner, the device admin package should be installed. Verify the 185 // device admin package. 186 deviceAdminComponentName = mUtils.findDeviceAdmin( 187 deviceAdminPackageName, deviceAdminComponentName, context); 188 // Since the device admin package must be installed at this point and its component 189 // name has been obtained, it should be safe to set the deprecated package name 190 // value to null. 191 deviceAdminPackageName = null; 192 } 193 194 // Parse skip user setup in ACTION_PROVISION_MANAGED_USER and 195 // ACTION_PROVISION_MANAGED_DEVICE (sync auth) only. This extra is not supported if 196 // provisioning was started by trusted source, as it is not clear where SUW should 197 // continue from. 198 boolean skipUserSetup = ProvisioningParams.DEFAULT_SKIP_USER_SETUP; 199 if (!isProvisionManagedDeviceFromTrustedSourceIntent 200 && (provisioningAction.equals(ACTION_PROVISION_MANAGED_USER) 201 || provisioningAction.equals(ACTION_PROVISION_MANAGED_DEVICE))) { 202 skipUserSetup = intent.getBooleanExtra(EXTRA_PROVISIONING_SKIP_USER_SETUP, 203 ProvisioningParams.DEFAULT_SKIP_USER_SETUP); 204 } 205 206 // Only current DeviceOwner can specify EXTRA_PROVISIONING_SKIP_USER_CONSENT when 207 // provisioning PO with ACTION_PROVISION_MANAGED_PROFILE 208 final boolean skipUserConsent = isManagedProfileAction 209 && intent.getBooleanExtra(EXTRA_PROVISIONING_SKIP_USER_CONSENT, 210 ProvisioningParams.DEFAULT_EXTRA_PROVISIONING_SKIP_USER_CONSENT) 211 && mUtils.isPackageDeviceOwner(dpm, inferStaticDeviceAdminPackageName( 212 deviceAdminComponentName, deviceAdminPackageName)); 213 214 // Only when provisioning PO with ACTION_PROVISION_MANAGED_PROFILE 215 final boolean keepAccountMigrated = isManagedProfileAction 216 && intent.getBooleanExtra(EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION, 217 ProvisioningParams.DEFAULT_EXTRA_PROVISIONING_KEEP_ACCOUNT_MIGRATED); 218 219 // Parse main color and organization's logo. This is not supported in managed device 220 // from trusted source provisioning because, currently, there is no way to send 221 // organization logo to the device at this stage. 222 Integer mainColor = ProvisioningParams.DEFAULT_MAIN_COLOR; 223 if (!isProvisionManagedDeviceFromTrustedSourceIntent) { 224 if (intent.hasExtra(EXTRA_PROVISIONING_MAIN_COLOR)) { 225 mainColor = intent.getIntExtra(EXTRA_PROVISIONING_MAIN_COLOR, 0 /* not used */); 226 } 227 parseOrganizationLogoUrlFromExtras(context, intent); 228 } 229 230 DisclaimersParam disclaimersParam = new DisclaimersParser(context, provisioningId) 231 .parse(intent.getParcelableArrayExtra(EXTRA_PROVISIONING_DISCLAIMERS)); 232 233 String deviceAdminLabel = null; 234 String organizationName = null; 235 String supportUrl = null; 236 String deviceAdminIconFilePath = null; 237 if (isProvisionManagedDeviceFromTrustedSourceIntent) { 238 deviceAdminLabel = intent.getStringExtra( 239 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL); 240 organizationName = intent.getStringExtra(EXTRA_PROVISIONING_ORGANIZATION_NAME); 241 supportUrl = intent.getStringExtra(EXTRA_PROVISIONING_SUPPORT_URL); 242 deviceAdminIconFilePath = new DeviceAdminIconParser(context, provisioningId).parse( 243 intent.getParcelableExtra( 244 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI)); 245 } 246 247 final boolean leaveAllSystemAppsEnabled = isManagedProfileAction 248 ? false 249 : intent.getBooleanExtra( 250 EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, 251 ProvisioningParams.DEFAULT_LEAVE_ALL_SYSTEM_APPS_ENABLED); 252 253 return ProvisioningParams.Builder.builder() 254 .setProvisioningId(provisioningId) 255 .setProvisioningAction(provisioningAction) 256 .setDeviceAdminComponentName(deviceAdminComponentName) 257 .setDeviceAdminPackageName(deviceAdminPackageName) 258 .setSkipEncryption(intent.getBooleanExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION, 259 ProvisioningParams.DEFAULT_EXTRA_PROVISIONING_SKIP_ENCRYPTION)) 260 .setLeaveAllSystemAppsEnabled(leaveAllSystemAppsEnabled) 261 .setAdminExtrasBundle((PersistableBundle) intent.getParcelableExtra( 262 EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE)) 263 .setMainColor(mainColor) 264 .setDisclaimersParam(disclaimersParam) 265 .setSkipUserConsent(skipUserConsent) 266 .setKeepAccountMigrated(keepAccountMigrated) 267 .setSkipUserSetup(skipUserSetup) 268 .setAccountToMigrate(intent.getParcelableExtra( 269 EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE)) 270 .setDeviceAdminLabel(deviceAdminLabel) 271 .setOrganizationName(organizationName) 272 .setSupportUrl(supportUrl) 273 .setDeviceAdminIconFilePath(deviceAdminIconFilePath); 274 } catch (ClassCastException e) { 275 throw new IllegalProvisioningArgumentException("Extra has invalid type", e); 276 } catch (IllegalArgumentException e) { 277 throw new IllegalProvisioningArgumentException("Invalid parameter found!", e); 278 } catch (NullPointerException e) { 279 throw new IllegalProvisioningArgumentException("Compulsory parameter not found!", e); 280 } 281 } 282 283 /** 284 * Parses an intent and return a corresponding {@link ProvisioningParams} object. 285 * 286 * @param intent intent to be parsed. 287 * @param context a context 288 */ parseAllSupportedProvisioningData(Intent intent, Context context)289 private ProvisioningParams parseAllSupportedProvisioningData(Intent intent, Context context) 290 throws IllegalProvisioningArgumentException { 291 try { 292 ProvisionLogger.logi("Processing all supported extras intent: " + intent.getAction()); 293 return parseMinimalistSupportedProvisioningDataInternal(intent, context) 294 // Parse time zone, local time and locale. 295 .setTimeZone(intent.getStringExtra(EXTRA_PROVISIONING_TIME_ZONE)) 296 .setLocalTime(intent.getLongExtra(EXTRA_PROVISIONING_LOCAL_TIME, 297 ProvisioningParams.DEFAULT_LOCAL_TIME)) 298 .setLocale(StoreUtils.stringToLocale( 299 intent.getStringExtra(EXTRA_PROVISIONING_LOCALE))) 300 // Parse WiFi configuration. 301 .setWifiInfo(parseWifiInfoFromExtras(intent)) 302 // Parse device admin package download info. 303 .setDeviceAdminDownloadInfo(parsePackageDownloadInfoFromExtras(intent)) 304 // Cases where startedByTrustedSource can be true are 305 // 1. We are reloading a stored provisioning intent, either Nfc bump or 306 // PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE, after encryption reboot, 307 // which is a self-originated intent. 308 // 2. the intent is from a trusted source, for example QR provisioning. 309 .setStartedByTrustedSource(ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE 310 .equals(intent.getAction())) 311 .build(); 312 } catch (IllegalArgumentException e) { 313 throw new IllegalProvisioningArgumentException("Invalid parameter found!", e); 314 } catch (NullPointerException e) { 315 throw new IllegalProvisioningArgumentException("Compulsory parameter not found!", e); 316 } 317 } 318 319 /** 320 * Parses Wifi configuration from an Intent and returns the result in {@link WifiInfo}. 321 */ 322 @Nullable parseWifiInfoFromExtras(Intent intent)323 private WifiInfo parseWifiInfoFromExtras(Intent intent) { 324 if (intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SSID) == null) { 325 return null; 326 } 327 return WifiInfo.Builder.builder() 328 .setSsid(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SSID)) 329 .setSecurityType(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE)) 330 .setPassword(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PASSWORD)) 331 .setProxyHost(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_HOST)) 332 .setProxyBypassHosts(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS)) 333 .setPacUrl(intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PAC_URL)) 334 .setProxyPort(intent.getIntExtra(EXTRA_PROVISIONING_WIFI_PROXY_PORT, 335 WifiInfo.DEFAULT_WIFI_PROXY_PORT)) 336 .setHidden(intent.getBooleanExtra(EXTRA_PROVISIONING_WIFI_HIDDEN, 337 WifiInfo.DEFAULT_WIFI_HIDDEN)) 338 .build(); 339 } 340 341 /** 342 * Parses device admin package download info configuration from an Intent and returns the result 343 * in {@link PackageDownloadInfo}. 344 */ 345 @Nullable parsePackageDownloadInfoFromExtras(Intent intent)346 private PackageDownloadInfo parsePackageDownloadInfoFromExtras(Intent intent) { 347 if (intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION) 348 == null) { 349 return null; 350 } 351 PackageDownloadInfo.Builder downloadInfoBuilder = PackageDownloadInfo.Builder.builder() 352 .setMinVersion(intent.getIntExtra( 353 EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE, 354 PackageDownloadInfo.DEFAULT_MINIMUM_VERSION)) 355 .setLocation(intent.getStringExtra( 356 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION)) 357 .setCookieHeader(intent.getStringExtra( 358 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER)); 359 String packageHash = 360 intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM); 361 if (packageHash != null) { 362 downloadInfoBuilder.setPackageChecksum(StoreUtils.stringToByteArray(packageHash)); 363 } 364 String sigHash = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM); 365 if (sigHash != null) { 366 downloadInfoBuilder.setSignatureChecksum(StoreUtils.stringToByteArray(sigHash)); 367 } 368 return downloadInfoBuilder.build(); 369 } 370 371 /** 372 * Parses the organization logo url from intent. 373 */ parseOrganizationLogoUrlFromExtras(Context context, Intent intent)374 private void parseOrganizationLogoUrlFromExtras(Context context, Intent intent) { 375 Uri logoUri = intent.getParcelableExtra(EXTRA_PROVISIONING_LOGO_URI); 376 if (logoUri != null) { 377 // If we go through encryption, and if the uri is a content uri: 378 // We'll lose the grant to this uri. So we need to save it to a local file. 379 LogoUtils.saveOrganisationLogo(context, logoUri); 380 } 381 } 382 } 383