/* * Copyright 2014, 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.managedprovisioning.common; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.FLAG_SUPPORTED_MODES_DEVICE_OWNER; import static android.app.admin.DevicePolicyManager.FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED; import static android.content.pm.PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.TypedArray; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.wifi.WifiManager; import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewTreeObserver; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.managedprovisioning.R; import com.android.managedprovisioning.model.CustomizationParams; import com.android.managedprovisioning.model.PackageDownloadInfo; import com.android.managedprovisioning.model.ProvisioningParams; import com.android.managedprovisioning.preprovisioning.WebActivity; import com.android.managedprovisioning.util.LazyStringResource; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.template.FooterButton.ButtonType; import com.google.android.setupdesign.GlifLayout; import com.google.android.setupdesign.util.DeviceHelper; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; /** * Class containing various auxiliary methods. */ public class Utils { public static final String SHA256_TYPE = "SHA-256"; // value chosen to match UX designs; when updating check status bar icon colors private static final int THRESHOLD_BRIGHT_COLOR = 190; public Utils() {} /** * Returns the system apps currently available to a given user. * *
Calls the {@link IPackageManager} to retrieve all system apps available to a user and
* returns their package names.
*
* @param ipm an {@link IPackageManager} object
* @param userId the id of the user to check the apps for
*/
public Set This function returns {@code null} if no or multiple admin receivers were found, and if
* the package name does not match dpcPackageName. There are two cases where an update is required:
* 1. The package is not currently present on the device.
* 2. The package is present, but the version is below the minimum supported version.
*
* @param packageName the package to be checked for updates
* @param minSupportedVersion the minimum supported version
* @param context a {@link Context} object
*/
public boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
Context context) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
// Always download packages if no minimum version given.
if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION
&& packageInfo.versionCode >= minSupportedVersion) {
return false;
}
} catch (NameNotFoundException e) {
// Package not on device.
}
return true;
}
/**
* Returns the first existing managed profile if any present, null otherwise.
*
* Note that we currently only support one managed profile per device.
*/
// TODO: Add unit tests
@Nullable
public UserHandle getManagedProfile(Context context) {
DevicePolicyManager devicePolicyManager =
requireNonNull(
/* obj= */ context.getSystemService(DevicePolicyManager.class),
/* message= */ "Unable to obtain DevicePolicyManager");
int currentUserId = UserHandle.myUserId();
List This method must not be called before the admin app has been installed. If it has not
* yet been installed, consider using {@link
* #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}.
*
* To perform the admin-integrated flow, all of the following criteria must be fulfilled:
* This method can be called before the admin app has been installed. Returning {@code true}
* does not mean the admin-integrated flow should be performed (for that, use {@link
* #canPerformAdminIntegratedFlow(Context, ProvisioningParams, PolicyComplianceUtils,
* GetProvisioningModeUtils)}), but returning {@code false} can be used as an early indication
* that it should not be performed.
*
* The preconditions are:
* Encryption is required if the device is not currently encrypted and the persistent
* system flag {@code persist.sys.no_req_encrypt} is not set.
*/
public boolean isEncryptionRequired() {
return !isPhysicalDeviceEncrypted()
&& !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false);
}
/**
* Returns whether the device is currently encrypted.
*/
public boolean isPhysicalDeviceEncrypted() {
return StorageManager.isEncrypted();
}
/**
* Returns the wifi pick intent.
*/
// TODO: Move this intent into a Globals class.
public Intent getWifiPickIntent() {
Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
wifiIntent.putExtra("extra_prefs_show_button_bar", true);
wifiIntent.putExtra("wifi_enable_next_on_connect", true);
return wifiIntent;
}
/**
* Returns whether the device is in headless system user mode.
*/
public boolean isHeadlessSystemUserMode() {
return UserManager.isHeadlessSystemUserMode();
}
/**
* Returns whether the currently chosen launcher supports managed profiles.
*
* A launcher is deemed to support managed profiles when its target API version is at least
* {@link Build.VERSION_CODES#LOLLIPOP}.
*/
public boolean currentLauncherSupportsManagedProfiles(Context context) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
PackageManager pm = context.getPackageManager();
ResolveInfo launcherResolveInfo = pm.resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if (launcherResolveInfo == null) {
return false;
}
try {
// If the user has not chosen a default launcher, then launcherResolveInfo will be
// referring to the resolver activity. It is fine to create a managed profile in
// this case since there will always be at least one launcher on the device that
// supports managed profile feature.
ApplicationInfo launcherAppInfo = pm.getApplicationInfo(
launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
/**
* Returns whether the given version number is at least lollipop.
*
* @param versionNumber the version number to be verified.
*/
private boolean versionNumberAtLeastL(int versionNumber) {
return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
}
/**
* Computes the sha 256 hash of a byte array.
*/
@Nullable
public byte[] computeHashOfByteArray(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance(SHA256_TYPE);
md.update(bytes);
return md.digest();
} catch (NoSuchAlgorithmException e) {
ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e);
return null;
}
}
/**
* Computes a hash of a file with a spcific hash algorithm.
*/
// TODO: Add unit tests
@Nullable
public byte[] computeHashOfFile(String fileLocation, String hashType) {
InputStream fis = null;
MessageDigest md;
byte[] hash = null;
try {
md = MessageDigest.getInstance(hashType);
} catch (NoSuchAlgorithmException e) {
ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e);
return null;
}
try {
fis = new FileInputStream(fileLocation);
byte[] buffer = new byte[256];
int n = 0;
while (n != -1) {
n = fis.read(buffer);
if (n > 0) {
md.update(buffer, 0, n);
}
}
hash = md.digest();
} catch (IOException e) {
ProvisionLogger.loge("IO error.", e);
} finally {
// Close input stream quietly.
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
// Ignore.
}
}
return hash;
}
/**
* Returns whether given intent can be resolved for the user.
*/
public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
return intent != null
&& context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
}
public boolean isPackageDeviceOwner(DevicePolicyManager dpm, String packageName) {
final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnCallingUser();
return deviceOwner != null && deviceOwner.getPackageName().equals(packageName);
}
public int getAccentColor(Context context) {
return getAttrColor(context, android.R.attr.colorAccent);
}
/**
* Returns the theme's background color.
*/
public int getBackgroundColor(Context context) {
return getAttrColor(context, android.R.attr.colorBackground);
}
/**
* Returns the theme's text primary color.
*/
public int getTextPrimaryColor(Context context) {
return getAttrColor(context, android.R.attr.textColorPrimary);
}
/**
* Returns the theme's text secondary color.
*/
public int getTextSecondaryColor(Context context) {
return getAttrColor(context, android.R.attr.textColorSecondary);
}
private int getAttrColor(Context context, int attr) {
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
int attrColor = ta.getColor(0, 0);
ta.recycle();
return attrColor;
}
public void handleSupportUrl(Context context, CustomizationParams customizationParams,
AccessibilityContextMenuMaker contextMenuMaker, TextView textView,
String deviceProvider, String contactDeviceProvider,
Consumer
*
*/
public boolean canPerformAdminIntegratedFlow(Context context, ProvisioningParams params,
PolicyComplianceUtils policyComplianceUtils,
GetProvisioningModeUtils provisioningModeUtils) {
if (!checkAdminIntegratedFlowPreconditions(params)) {
return false;
}
boolean isPolicyComplianceScreenAvailable =
policyComplianceUtils.isPolicyComplianceActivityResolvableForUser(context, params,
this, UserHandle.SYSTEM);
if (!isPolicyComplianceScreenAvailable) {
ProvisionLogger.logi("Policy compliance DPC screen not available.");
return false;
}
boolean isGetProvisioningModeScreenAvailable =
provisioningModeUtils.isGetProvisioningModeActivityResolvable(context, params);
if (!isGetProvisioningModeScreenAvailable) {
ProvisionLogger.logi("Get provisioning mode DPC screen not available.");
return false;
}
return true;
}
/**
* Returns {@code true} if the admin-integrated flow preconditions are met.
*
*
*
*/
public boolean checkAdminIntegratedFlowPreconditions(ProvisioningParams params) {
if (isFinancedDeviceAction(params.provisioningAction)) {
ProvisionLogger.logi("Financed device provisioning");
return false;
}
if (!params.startedByTrustedSource) {
ProvisionLogger.logi("Provisioning not started by a trusted source");
return false;
}
return true;
}
/**
* Factory resets the device.
*/
public void factoryReset(Context context, String reason) {
context.getSystemService(DevicePolicyManager.class).wipeDevice(/* flags=*/ 0);
}
/**
* Returns whether the given provisioning action is a profile owner action.
*/
// TODO: Move the list of device owner actions into a Globals class.
public final boolean isProfileOwnerAction(String action) {
return ACTION_PROVISION_MANAGED_PROFILE.equals(action);
}
/**
* Returns whether the given provisioning action is a device owner action.
*/
// TODO: Move the list of device owner actions into a Globals class.
public final boolean isDeviceOwnerAction(String action) {
return ACTION_PROVISION_MANAGED_DEVICE.equals(action);
}
/**
* Returns whether the given provisioning action is a financed device action.
*/
public final boolean isFinancedDeviceAction(String action) {
return ACTION_PROVISION_FINANCED_DEVICE.equals(action);
}
/**
* Returns whether the device currently has connectivity.
*/
public boolean isConnectedToNetwork(Context context) {
NetworkInfo info = getActiveNetworkInfo(context);
return info != null && info.isConnected();
}
public boolean isMobileNetworkConnectedToInternet(Context context) {
final ConnectivityManager connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return Arrays.stream(connectivityManager.getAllNetworks())
.map(connectivityManager::getNetworkCapabilities)
.filter(Objects::nonNull)
.anyMatch(this::isNetworkConnectedToInternetViaCellular);
}
/**
* Returns whether the device is currently connected to specific network type, such as
* {@link ConnectivityManager#TYPE_WIFI} or {@link ConnectivityManager#TYPE_ETHERNET}
*
* {@see ConnectivityManager}
*
* @deprecated use one of
* {@link #isNetworkConnectedToInternetViaEthernet(NetworkCapabilities)},
* {@link #isNetworkConnectedToInternetViaWiFi(NetworkCapabilities)}
* {@link #isNetworkConnectedToInternetViaCellular(NetworkCapabilities)}
*/
@Deprecated
public boolean isNetworkTypeConnected(Context context, int... types) {
final NetworkInfo networkInfo = getActiveNetworkInfo(context);
if (networkInfo != null && networkInfo.isConnected()) {
final int activeNetworkType = networkInfo.getType();
for (int type : types) {
if (activeNetworkType == type) {
return true;
}
}
}
return false;
}
/**
* Checks if the network is active (can receive and send data)
*/
public boolean isNetworkActive(NetworkCapabilities network) {
return network.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& network.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
}
/**
* Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_ETHERNET} and
* {@link #isNetworkActive}
*/
public boolean isNetworkConnectedToInternetViaEthernet(NetworkCapabilities network) {
return network.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
&& isNetworkActive(network);
}
/**
* Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_WIFI} and
* {@link #isNetworkActive}
*/
public boolean isNetworkConnectedToInternetViaWiFi(NetworkCapabilities network) {
return network.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
&& isNetworkActive(network);
}
/**
* Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_CELLULAR} and
* {@link #isNetworkActive}
*/
public boolean isNetworkConnectedToInternetViaCellular(NetworkCapabilities network) {
return network.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
&& isNetworkActive(network);
}
/**
* Returns the active network info of the device.
*
* @deprecated use {@link #getActiveNetworkCapabilities(Context)}
*/
@Deprecated
public NetworkInfo getActiveNetworkInfo(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo();
}
/**
* Retrieves {@link NetworkCapabilities} of the currently active network
* or `null` if there is no active network
*/
@Nullable
public NetworkCapabilities getActiveNetworkCapabilities(Context context) {
var cn = requireNonNull(context.getSystemService(ConnectivityManager.class),
"Unable to obtain ConnectivityManager");
return cn.getNetworkCapabilities(cn.getActiveNetwork());
}
/**
* Returns whether encryption is required on this device.
*
*