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

Commit 6f94335b authored by Sandy Pan's avatar Sandy Pan Committed by Android (Google) Code Review
Browse files

Merge "Update SupervisionRecoveryInfo" into main

parents 42954a53 eed90e49
Loading
Loading
Loading
Loading
+2 −10
Original line number Diff line number Diff line
@@ -18,14 +18,6 @@ package android.app.supervision;

/**
 * A parcelable of the supervision recovery information. This stores information for recovery
 * purposes.
 *
 * <p>Email: The email for recovery. ID: The account id for recovery.
 *
 * @hide
 * purposes for device supervision pin.
 */
@JavaDerive(equals = true, toString = true)
parcelable SupervisionRecoveryInfo {
    @nullable String email;
    @nullable String id;
}
parcelable SupervisionRecoveryInfo;
+178 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.supervision;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;

import androidx.annotation.Keep;

import java.util.Objects;

/**
 * Contains the information needed for recovering the device supervision pin.
 *
 * <p>Returned by {@link SupervisionManager#getSupervisionRecoveryInfo}.
 *
 * @hide
 */
public final class SupervisionRecoveryInfo implements Parcelable {
    /**
     * Extra key used to pass supervision recovery information within an intent.
     *
     * <p>The associated value should be a {@link android.app.supervision.SupervisionRecoveryInfo}
     * object.
     *
     * <p>* This extra is intended for use when launching the PIN recovery activity via {@link
     * com.android.settingslib.supervision.SupervisionIntentProvider#getPinRecoveryIntent }
     */
    public static final String EXTRA_SUPERVISION_RECOVERY_INFO =
            "android.app.supervision.extra.SUPERVISION_RECOVERY_INFO";

    @NonNull
    public static final Creator<SupervisionRecoveryInfo> CREATOR =
            new Creator<SupervisionRecoveryInfo>() {
                @Override
                public SupervisionRecoveryInfo createFromParcel(@NonNull Parcel source) {
                    String accountName = source.readString();
                    String accountType = source.readString();
                    PersistableBundle accountData =
                            source.readPersistableBundle(getClass().getClassLoader());
                    int state = source.readInt();
                    if (accountName != null && accountType != null) {
                        return new SupervisionRecoveryInfo(
                                accountName, accountType, state, accountData);
                    }
                    return null;
                }

                @Override
                public SupervisionRecoveryInfo[] newArray(int size) {
                    return new SupervisionRecoveryInfo[size];
                }
            };

    /** An IntDef which describes the various states of the recovery information. */
    @Keep
    @IntDef({STATE_PENDING, STATE_VERIFIED})
    public @interface State {}

    /** Indicates that the recovery information is pending verification. */
    public static final int STATE_PENDING = 0;

    /** Indicates that the recovery information has been verified. */
    public static final int STATE_VERIFIED = 1;

    @NonNull private String mAccountName;
    @NonNull private String mAccountType;
    @Nullable private PersistableBundle mAccountData;
    @State private int mState;

    public SupervisionRecoveryInfo(
            @NonNull String accountName,
            @NonNull String accountType,
            @State int state,
            @Nullable PersistableBundle accountData) {
        this.mAccountName = accountName;
        this.mAccountType = accountType;
        this.mAccountData = accountData;
        this.mState = state;
    }

    /** Gets the recovery account name. */
    @NonNull
    public String getAccountName() {
        return mAccountName;
    }

    /** Gets the recovery account type. */
    @NonNull
    public String getAccountType() {
        return mAccountType;
    }

    /** Gets the recovery account data. */
    @NonNull
    public PersistableBundle getAccountData() {
        return mAccountData == null ? new PersistableBundle() : mAccountData;
    }

    /**
     * Gets the state of the recovery information.
     *
     * @return One of {@link #STATE_PENDING}, {@link #STATE_VERIFIED}.
     */
    @State
    public int getState() {
        return mState;
    }

    @Override
    public void writeToParcel(@NonNull Parcel parcel, int flag) {
        parcel.writeString(mAccountName);
        parcel.writeString(mAccountType);
        parcel.writePersistableBundle(mAccountData);
        parcel.writeInt(mState);
    }

    /**
     * Reads the SupervisionRecoveryInfo object from the given {@link Parcel}.
     *
     * @param parcel The {@link Parcel} to read from.
     */
    public void readFromParcel(@NonNull Parcel parcel) {
        mAccountName = Objects.requireNonNull(parcel.readString());
        mAccountType = Objects.requireNonNull(parcel.readString());
        mAccountData = parcel.readPersistableBundle(getClass().getClassLoader());
        mState = parcel.readInt();
    }

    @Override
    public String toString() {
        java.util.StringJoiner joiner = new java.util.StringJoiner(", ", "{", "}");
        joiner.add("accountName: " + mAccountName);
        joiner.add("accountType: " + mAccountType);
        joiner.add("accountData: " + mAccountData);
        joiner.add("state: " + mState);
        return "SupervisionRecoveryInfo" + joiner;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof SupervisionRecoveryInfo)) return false;
        SupervisionRecoveryInfo that = (SupervisionRecoveryInfo) other;
        return Objects.equals(mAccountName, that.mAccountName)
                && Objects.equals(mAccountType, that.mAccountType)
                && Objects.equals(mAccountData, that.mAccountData)
                && mState == that.mState;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mAccountName, mAccountType, mAccountData, mState);
    }

    @Override
    public int describeContents() {
        return 0;
    }
}
+66 −15
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.supervision.SupervisionRecoveryInfo;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.os.PersistableBundle;
import android.util.Log;

import java.io.File;
@@ -38,13 +39,13 @@ import java.io.File;
public class SupervisionRecoveryInfoStorage {
    private static final String LOG_TAG = "RecoveryInfoStorage";
    private static final String PREF_NAME = "supervision_recovery_info";
    private static final String KEY_EMAIL = "email";
    private static final String KEY_ID = "id";
    private static final String KEY_ACCOUNT_TYPE = "account_type";
    private static final String KEY_ACCOUNT_NAME = "account_name";
    private static final String KEY_ACCOUNT_DATA = "account_data";
    private static final String KEY_STATE = "state";

    private final SharedPreferences mSharedPreferences;

    private static SupervisionRecoveryInfoStorage sInstance;

    private static final Object sLock = new Object();

    private SupervisionRecoveryInfoStorage(Context context) {
@@ -75,14 +76,22 @@ public class SupervisionRecoveryInfoStorage {
     */
    public SupervisionRecoveryInfo loadRecoveryInfo() {
        synchronized (sLock) {
            String email = mSharedPreferences.getString(KEY_EMAIL, null);
            String id = mSharedPreferences.getString(KEY_ID, null);
            String accountType = mSharedPreferences.getString(KEY_ACCOUNT_TYPE, null);
            String accountName = mSharedPreferences.getString(KEY_ACCOUNT_NAME, null);
            String accountDataString = mSharedPreferences.getString(KEY_ACCOUNT_DATA, null);
            int state = mSharedPreferences.getInt(KEY_STATE, SupervisionRecoveryInfo.STATE_PENDING);

            if (email != null || id != null) {
                SupervisionRecoveryInfo info = new SupervisionRecoveryInfo();
                info.email = email;
                info.id = id;
                return info;
            if (accountType != null && accountName != null) {
                PersistableBundle accountData = null;
                if (accountDataString != null) {
                    try {
                        accountData = PersistableBundleUtils.fromString(accountDataString);
                    } catch (Exception e) {
                        Log.e(LOG_TAG, "Failed to load account data from SharedPreferences", e);
                        // If failed to load accountData, just return other info.
                    }
                }
                return new SupervisionRecoveryInfo(accountName, accountType, state, accountData);
            }
        }
        return null;
@@ -99,11 +108,18 @@ public class SupervisionRecoveryInfoStorage {
            SharedPreferences.Editor editor = mSharedPreferences.edit();

            if (recoveryInfo == null) {
                editor.remove(KEY_EMAIL);
                editor.remove(KEY_ID);
                editor.remove(KEY_ACCOUNT_TYPE);
                editor.remove(KEY_ACCOUNT_NAME);
                editor.remove(KEY_ACCOUNT_DATA);
                editor.remove(KEY_STATE);
            } else {
                editor.putString(KEY_EMAIL, recoveryInfo.email);
                editor.putString(KEY_ID, recoveryInfo.id);
                editor.putString(KEY_ACCOUNT_TYPE, recoveryInfo.getAccountType());
                editor.putString(KEY_ACCOUNT_NAME, recoveryInfo.getAccountName());
                PersistableBundle accountData = recoveryInfo.getAccountData();
                String accountDataString =
                        accountData != null ? PersistableBundleUtils.toString(accountData) : null;
                editor.putString(KEY_ACCOUNT_DATA, accountDataString);
                editor.putInt(KEY_STATE, recoveryInfo.getState());
            }
            editor.apply();
            if (!editor.commit()) {
@@ -111,4 +127,39 @@ public class SupervisionRecoveryInfoStorage {
            }
        }
    }

    private static class PersistableBundleUtils {
        private static final String SEPARATOR = ";";
        private static final String KEY_VALUE_SEPARATOR = ":";

        public static String toString(PersistableBundle bundle) {
            if (bundle == null) {
                return null;
            }
            StringBuilder sb = new StringBuilder();
            for (String key : bundle.keySet()) {
                String value = String.valueOf(bundle.get(key));
                sb.append(key).append(KEY_VALUE_SEPARATOR).append(value).append(SEPARATOR);
            }
            return sb.toString();
        }

        public static PersistableBundle fromString(String str) {
            if (str == null || str.isEmpty()) {
                return null;
            }
            PersistableBundle bundle = new PersistableBundle();
            String[] pairs = str.split(SEPARATOR);
            for (String pair : pairs) {
                if (pair.isEmpty()) {
                    continue;
                }
                String[] keyValue = pair.split(KEY_VALUE_SEPARATOR);
                if (keyValue.length == 2) {
                    bundle.putString(keyValue[0], keyValue[1]);
                }
            }
            return bundle;
        }
    }
}
+12 −6
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManagerInternal
import android.app.supervision.SupervisionRecoveryInfo
import android.app.supervision.SupervisionRecoveryInfo.STATE_PENDING
import android.app.supervision.flags.Flags
import android.content.BroadcastReceiver
import android.content.ComponentName
@@ -403,15 +404,20 @@ class SupervisionServiceTest {
        assertThat(service.supervisionRecoveryInfo).isNull()

        val recoveryInfo =
            SupervisionRecoveryInfo().apply {
                email = "test_email"
                id = "test_id"
            }
            SupervisionRecoveryInfo(
                "email",
                "default",
                STATE_PENDING,
                PersistableBundle().apply { putString("id", "id") },
            )
        service.setSupervisionRecoveryInfo(recoveryInfo)

        assertThat(service.supervisionRecoveryInfo).isNotNull()
        assertThat(service.supervisionRecoveryInfo.email).isEqualTo(recoveryInfo.email)
        assertThat(service.supervisionRecoveryInfo.id).isEqualTo(recoveryInfo.id)
        assertThat(service.supervisionRecoveryInfo.accountType).isEqualTo(recoveryInfo.accountType)
        assertThat(service.supervisionRecoveryInfo.accountName).isEqualTo(recoveryInfo.accountName)
        assertThat(service.supervisionRecoveryInfo.accountData.getString("id"))
            .isEqualTo(recoveryInfo.accountData.getString("id"))
        assertThat(service.supervisionRecoveryInfo.state).isEqualTo(recoveryInfo.state)
    }

    private val systemSupervisionPackage: String