Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb419eee authored by Pablo Soëtard's avatar Pablo Soëtard Committed by Antoan Angelov
Browse files

Transform NFC Intent to provisioning Intent

Generates a provisioning intent with the extra data from a nfc intent.

Test: CTS tests are included in the other CL in this topic
Bug: 192228539
Change-Id: I00ed374191bc3a48dcb336ed056395be59c445f5
parent 835c8181
Loading
Loading
Loading
Loading

core/api/system-current.txt

100644 → 100755
+2 −0
Original line number Diff line number Diff line
@@ -924,6 +924,7 @@ package android.app.admin {
  }
  public class DevicePolicyManager {
    method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -972,6 +973,7 @@ package android.app.admin {
    field public static final int FLAG_SUPPORTED_MODES_PERSONALLY_OWNED = 2; // 0x2
    field public static final int PROVISIONING_TRIGGER_CLOUD_ENROLLMENT = 1; // 0x1
    field public static final int PROVISIONING_TRIGGER_MANAGED_ACCOUNT = 4; // 0x4
    field public static final int PROVISIONING_TRIGGER_NFC = 5; // 0x5
    field @Deprecated public static final int PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER = 3; // 0x3
    field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2
    field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0
+49 −1
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import android.graphics.Bitmap;
import android.net.PrivateDnsConnectivityChecker;
import android.net.ProxyInfo;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -1216,7 +1217,8 @@ public class DevicePolicyManager {
            PROVISIONING_TRIGGER_CLOUD_ENROLLMENT,
            PROVISIONING_TRIGGER_QR_CODE,
            PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER,
            PROVISIONING_TRIGGER_MANAGED_ACCOUNT
            PROVISIONING_TRIGGER_MANAGED_ACCOUNT,
            PROVISIONING_TRIGGER_NFC
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ProvisioningTrigger {}
@@ -1254,6 +1256,7 @@ public class DevicePolicyManager {
     * @see #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT
     * @see #PROVISIONING_TRIGGER_QR_CODE
     * @see #PROVISIONING_TRIGGER_MANAGED_ACCOUNT
     * @see #PROVISIONING_TRIGGER_NFC
     * @hide
     */
    @SystemApi
@@ -1265,6 +1268,7 @@ public class DevicePolicyManager {
     * @see #PROVISIONING_TRIGGER_QR_CODE
     * @see #PROVISIONING_TRIGGER_MANAGED_ACCOUNT
     * @see #PROVISIONING_TRIGGER_UNSPECIFIED
     * @see #PROVISIONING_TRIGGER_NFC
     * @hide
     */
    @SystemApi
@@ -1276,6 +1280,7 @@ public class DevicePolicyManager {
     * @see #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT
     * @see #PROVISIONING_TRIGGER_MANAGED_ACCOUNT
     * @see #PROVISIONING_TRIGGER_UNSPECIFIED
     * @see #PROVISIONING_TRIGGER_NFC
     * @hide
     */
    @SystemApi
@@ -1295,6 +1300,7 @@ public class DevicePolicyManager {
     * @see #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT
     * @see #PROVISIONING_TRIGGER_QR_CODE
     * @see #PROVISIONING_TRIGGER_UNSPECIFIED
     * @see #PROVISIONING_TRIGGER_NFC
     * @hide
     */
    @SystemApi
@@ -1308,11 +1314,24 @@ public class DevicePolicyManager {
     * @see #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT
     * @see #PROVISIONING_TRIGGER_QR_CODE
     * @see #PROVISIONING_TRIGGER_UNSPECIFIED
     * @see #PROVISIONING_TRIGGER_NFC
     * @hide
     */
    @SystemApi
    public static final int PROVISIONING_TRIGGER_MANAGED_ACCOUNT = 4;
    /**
     * A value for {@link #EXTRA_PROVISIONING_TRIGGER} indicating that the provisioning is
     * triggered by tapping an NFC tag.
     * @see #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT
     * @see #PROVISIONING_TRIGGER_QR_CODE
     * @see #PROVISIONING_TRIGGER_UNSPECIFIED
     * @see #PROVISIONING_TRIGGER_MANAGED_ACCOUNT
     * @hide
     */
    @SystemApi
    public static final int PROVISIONING_TRIGGER_NFC = 5;
    /**
     * Flag for {@link #EXTRA_PROVISIONING_SUPPORTED_MODES} indicating that provisioning is
     * organization-owned.
@@ -14009,4 +14028,33 @@ public class DevicePolicyManager {
            throw e.rethrowFromSystemServer();
        }
    }
    /**
     * Creates a {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent
     * from the provided {@code nfcIntent}.
     *
     * <p>Prerequisites to create the provisioning intent:
     *
     * <ul>
     * <li>{@code nfcIntent}'s action is {@link NfcAdapter#ACTION_NDEF_DISCOVERED}</li>
     * <li>{@code nfcIntent}'s NFC properties contain either
     * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} or
     * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} </li>
     * </ul>
     *
     * This method returns {@code null} if the prerequisites are not met or if an error occurs
     * when reading the NFC properties.
     *
     * @param nfcIntent the nfc intent generated from scanning a NFC tag
     * @return a {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent with
     * intent extras as read by {@code nfcIntent}'s NFC properties or {@code null} if the
     * prerequisites are not met or if an error occurs when reading the NFC properties.
     *
     * @hide
     */
    @Nullable
    @SystemApi
    public Intent createProvisioningIntentFromNfcIntent(@NonNull Intent nfcIntent) {
        return ProvisioningIntentHelper.createProvisioningIntentFromNfcIntent(nfcIntent);
    }
}
+178 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.app.admin;

import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER;
import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_NFC;
import static android.nfc.NfcAdapter.EXTRA_NDEF_MESSAGES;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;

import java.io.IOException;
import java.io.StringReader;
import java.util.Enumeration;
import java.util.Properties;

/**
 * Utility class that provides functionality to create provisioning intents from nfc intents.
 */
final class ProvisioningIntentHelper {

    private static final String TAG = "ProvisioningIntentHelper";

    /**
     * This class is never instantiated
     */
    private ProvisioningIntentHelper() { }

    @Nullable
    public static Intent createProvisioningIntentFromNfcIntent(@NonNull Intent nfcIntent) {
        requireNonNull(nfcIntent);

        if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(nfcIntent.getAction())) {
            Log.e(TAG, "Wrong Nfc action: " + nfcIntent.getAction());
            return null;
        }

        NdefRecord firstRecord = getFirstNdefRecord(nfcIntent);

        if (firstRecord != null) {
            return createProvisioningIntentFromNdefRecord(firstRecord);
        }

        return null;
    }


    private static Intent createProvisioningIntentFromNdefRecord(NdefRecord firstRecord) {
        requireNonNull(firstRecord);

        Properties properties = loadPropertiesFromPayload(firstRecord.getPayload());

        if (properties == null) {
            Log.e(TAG, "Failed to load NdefRecord properties.");
            return null;
        }

        Bundle bundle = createBundleFromProperties(properties);

        if (!containsRequiredProvisioningExtras(bundle)) {
            Log.e(TAG, "Bundle does not contain the required provisioning extras.");
            return null;
        }

        return createProvisioningIntentFromBundle(bundle);
    }

    private static Properties loadPropertiesFromPayload(byte[] payload) {
        Properties properties = new Properties();

        try {
            properties.load(new StringReader(new String(payload, UTF_8)));
        } catch (IOException e) {
            Log.e(TAG, "NFC Intent properties loading failed.");
            return null;
        }

        return properties;
    }

    private static Bundle createBundleFromProperties(Properties properties) {
        Enumeration propertyNames = properties.propertyNames();
        Bundle bundle = new Bundle();

        while (propertyNames.hasMoreElements()) {
            String propertyName = (String) propertyNames.nextElement();
            addPropertyToBundle(propertyName, properties, bundle);
        }
        return bundle;
    }

    private static void addPropertyToBundle(
            String propertyName, Properties properties, Bundle bundle) {
        if(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME.equals(propertyName)) {
            ComponentName componentName = ComponentName.unflattenFromString(
                    properties.getProperty(propertyName));
            bundle.putParcelable(propertyName, componentName);
        }
        else {
            bundle.putString(propertyName, properties.getProperty(propertyName));
        }
    }

    private static Intent createProvisioningIntentFromBundle(Bundle bundle) {
        requireNonNull(bundle);

        Intent provisioningIntent = new Intent(ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE);

        provisioningIntent.putExtras(bundle);

        provisioningIntent.putExtra(EXTRA_PROVISIONING_TRIGGER, PROVISIONING_TRIGGER_NFC);

        return provisioningIntent;
    }

    private static boolean containsRequiredProvisioningExtras(Bundle bundle) {
        return bundle.containsKey(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME) ||
                bundle.containsKey(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME);
    }

    /**
     * Returns the first {@link NdefRecord} found with a recognized MIME-type
     */
    private static NdefRecord getFirstNdefRecord(Intent nfcIntent) {
        Parcelable[] ndefMessages = nfcIntent.getParcelableArrayExtra(EXTRA_NDEF_MESSAGES);
        if (ndefMessages == null) {
            Log.i(TAG, "No EXTRA_NDEF_MESSAGES from nfcIntent");
            return null;
        }

        for (Parcelable rawMsg : ndefMessages) {
            NdefMessage msg = (NdefMessage) rawMsg;
            for (NdefRecord record : msg.getRecords()) {
                String mimeType = new String(record.getType(), UTF_8);

                // Only one first message with NFC_MIME_TYPE is used.
                if (MIME_TYPE_PROVISIONING_NFC.equals(mimeType)) {
                    return record;
                }

                // Assume only first record of message is used.
                break;
            }
        }

        Log.i(TAG, "No compatible records found on nfcIntent");
        return null;
    }
}