CTS延長--マスター・ユーザーと管理対象ユーザー(Managed Profile)


CTS延長--マスター・ユーザーと管理対象ユーザー(Managed Profile)


0.起因


CtsVerifierテストを行ったところ、「Cross profile intent filters are set」の手動検証に失敗したことがわかりました.ログを見ると以下のようになります.
10:46:45.111 10694 10694 E IntentFiltersTestHelper: Intent { act=android.intent.action.DIAL dat=tel:xxx }  from managed profile should be forwarded to the primary profile but is not.
08-11 10:46:45.120 10694 10694 E IntentFiltersTestHelper: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=tel:xxx }  from managed profile should be forwarded to the primary profile but is not.
08-11 10:46:45.145 10694 10694 E IntentFiltersTestHelper: Intent { act=android.intent.action.DIAL dat=tel:xxx }  from managed profile should be forwarded to the primary profile but is not.
08-11 10:46:45.148 10694 10694 E IntentFiltersTestHelper: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=tel:xxx }  from managed profile should be forwarded to the primary profile but is not.
08-11 10:46:45.174 10694 10694 E IntentFiltersTestHelper: Intent { act=android.intent.action.DIAL dat=tel:xxx }  from managed profile should be forwarded to the primary profile but is not.
08-11 10:46:45.177 10694 10694 E IntentFiltersTestHelper: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=tel:xxx }  from managed profile should be forwarded to the primary profile but is not.

managed profileからprimary profileに転送されるはずのIntent{act=android.intent.action.DIAL dat=tel:xxx}が送信されますが、転送されません.
このとき、managed profileとprimary profileがそれぞれ何なのか分からないので、原因を特定できません.
ソースコードを読んでログを追加し、CTS-Vを再編成した後、検証手順は大体以下の通りであることが分かった.
  • CTS-Vは、「com.android.cts.verifier.managedprovisioning.BYOD_STATUS」のintentを構築し、queryIntentActivitiesによってIntentForwarderActivityを得る.
  • CTS-Vは再びIntent{act=android.intent.action.DIAL dat=tel:xxx}を構築し、queryIntentActivityによってActivityリストを得、このActivityリストには上記のIntentForwarderActivityが含まれなければならない.

  • Intent{act=android.intent.action.DIAL dat=tel:xxx}は私たちのDialtactsActivityに一致する位置しか特定できないため、CTS検証は失敗で終了します.失敗の原因は分かっていたのですが、なぜこのように検証するのか分からず、この文章ができました.

    1.ユーザー(Primary profile)と管理対象ユーザー(Managed profile)


    特定のユーザのタイプは、ソースコードのUserInfoのFLAG_を表示することによってXXXXXの解釈は、ここに必要ないくつかをリストします.
  • FLAG_PRIMARYプライマリユーザー、デバイス上の最初のユーザー、1つのデバイスは1つしか存在しません.
  • FLAG_ADMIN管理者、削除ユーザーを追加できます.
  • FLAG_MANAGED_PROFILEは、現在のユーザが他のユーザのProfileであることを示す.すなわち、現在のユーザは他のユーザの一部の機能しか持っていない.例えば、BユーザはAユーザの企業アプリケーションデータしか持っていない.Androidが提供する企業管理の手段のように見えます.
  • FLAG_DISABLEDは、ユーザが削除されている間にこの識別ビットが設定され、ユーザが再Enabledできないことを示すために使用される.

  • アプリケーションとユーザの関係は,uid,userId,appIdの3つの関係であり,ソースコードのProcessとUserHandleを表示することで得られる.
  • マルチユーザがサポートする場合、ユーザは1つのuserIdのみが0であり、uid==appIdである.
  • マルチユーザをサポートする場合uid=userId*100000+appId.すなわちuidは,ユーザごとにアプリケーションごとに異なる.

  • 2.設備管理権限の申請


    2ステップでデバイス管理権限を取得できます.
    1.デバイス管理権限の申請要求を送信します.
    private void provisionManagedProfile() {
        Activity activity = getActivity();
        if (null == activity) {
            return;
        }
        //  
        Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
    
        // Use a different intent extra below M to configure the admin component.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            //noinspection deprecation
            //  M , , DeviceReceiver,
            //  DeviceReceiver 
            intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
                    activity.getApplicationContext().getPackageName());
        } else {
            //  M , 
            final ComponentName component = new ComponentName(activity,
                    BasicDeviceAdminReceiver.class.getName());
            intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
                    component);
        }
    
        if (intent.resolveActivity(activity.getPackageManager()) != null) {
            //  onActivityResult / 。
            startActivityForResult(intent, REQUEST_PROVISION_MANAGED_PROFILE);
            activity.finish();
        } else {
            Toast.makeText(activity, "Device provisioning is not enabled. Stopping.",
                           Toast.LENGTH_SHORT).show();
        }
    }

    2.権限の受信およびプロファイルの設定が可能です.
    受信権限はDeviceAdminReceiverの宣言を引き継いだManifestのReceiverである必要があります.
    <receiver
        android:name=".BasicDeviceAdminReceiver"
        android:description="@string/app_name"
        android:label="@string/app_name"
        <!-- android.permission.BIND_DEVICE_ADMIN -->
        android:permission="android.permission.BIND_DEVICE_ADMIN">
        <meta-data
            android:name="android.app.device_admin"
            android:resource="@xml/basic_device_admin_receiver"/>
        <intent-filter>
            <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
        </intent-filter>
    </receiver>

    basic_device_admin_receiver.xmlファイルの内容
    <device-admin>
        <!-- -->
        <uses-policies>
            <limit-password/>
            <watch-login/>
            <reset-password/>
            <force-lock/>
            <wipe-data/>
            <expire-password/>
            <encrypted-storage/>
            <disable-camera/>
            <disable-keyguard-features/>
        </uses-policies>
    </device-admin>
    public class BasicDeviceAdminReceiver extends DeviceAdminReceiver {
    
        //  ,onProfileProvisioningComplete 。
        //  Receiver ManagedProfile.
        //  Profile disable 。
        @Override
        public void onProfileProvisioningComplete(Context context, Intent intent) {
    
            Intent launch = new Intent(context, EnableProfileActivity.class);
            launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(launch);
        }
    }

    現在のプロファイルをEnableProfileActivityでEnabled状態に設定し、ユーザー名を設定します.
    private void enableProfile() {
        DevicePolicyManager manager =
            (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
        ComponentName componentName = BasicDeviceAdminReceiver.getComponentName(this);
        // This is the name for the newly created managed profile.
        manager.setProfileName(componentName, getString(R.string.profile_name));
        // We enable the profile here.
        manager.setProfileEnabled(componentName);
    }

    これで、デバイス管理権限を申請する手順が完了し、セットアップ->スクリーンロック、指紋、セキュリティ->より多くのセキュリティ設定->デバイスマネージャでアプリケーションを表示できます.設定->アカウントには、作業アカウントが表示されます.
    デスクトップに戻ると、Profileユーザーに属する右下にパケット付きのアプリケーションが追加されています.デフォルトでは、Googleは基本的なユーザー体験を保証するアプリケーションを追加します.

    3.マスターユーザと管理されたユーザとの間の通信


    現在、我々のデバイスでは2人のユーザが実行されており、現在実行されているユーザ情報をadb shell pm list usersでリストすることができます.
    Users:
    	UserInfo{0: :13} running
    	UserInfo{12: :30} running

    安全のため、Androidはデフォルトでプライマリ・ユーザーとManagedProfile間の通信を完全に分離しています.ただし、デバイス管理者は、特定のIntentFilterチャネルを開くこともできます.Androidコンポーネント間の通信は主にIntentによって行われ、マッチングルールは主にintent-filter項目に定義されていることが知られています.次に、IntentFilterチャネルを開く例を示します.
    private void enableForwarding() {
        Activity activity = getActivity();
        if (null == activity || activity.isFinishing()) {
            return;
        }
        DevicePolicyManager manager =
                (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
        try {
            IntentFilter filter = new IntentFilter(Intent.ACTION_SEND);
            filter.addDataType("text/plain");
            filter.addDataType("image/jpeg");
            // This is how you can register an IntentFilter as allowed pattern of Intent forwarding
            manager.addCrossProfileIntentFilter(BasicDeviceAdminReceiver.getComponentName(activity),
                    filter, FLAG_MANAGED_CAN_ACCESS_PARENT | FLAG_PARENT_CAN_ACCESS_MANAGED);
        } catch (IntentFilter.MalformedMimeTypeException e) {
            e.printStackTrace();
        }
    }

    ACTION_をSENDというActionはCrossProfile IntentFilterに追加され、Profileユーザー環境下でこのActionを送信するのは応答しません.
    ProfileユーザーにもレスポンスACTIONがインストールされていない限りSENDの応用.
  • FLAG_MANAGED_CAN_ACCESS_PARENTは、ManagedProfileユーザーが発行したIntentがプライマリユーザー空間に到達できることを示しています.つまり、プライマリユーザーのアプリがIntentに鳴ることができます.
  • FLAG_PARENT_CAN_ACCESS_MANAGEDは、プライマリ・ユーザーが発行したIntentがManagedProfileユーザー・スペースに到達できることを識別します.
  • :Profileユーザー間で送信できるIntentのターゲットコンポーネントはActivityである必要があります.

    4.マスター・ユーザーの下にあるAPPをManagedProfileユーザーにインストールし、一部のコンポーネントを無効にします。

    private void setAppEnabled(String packageName, boolean enabled) {
        Activity activity = getActivity();
        if (null == activity) {
            return;
        }
        PackageManager packageManager = activity.getPackageManager();
        DevicePolicyManager devicePolicyManager =
                (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
        try {
            int packageFlags;
            if(Build.VERSION.SDK_INT < 24){
                //noinspection deprecation
                packageFlags = PackageManager.GET_UNINSTALLED_PACKAGES;
            }else{
                packageFlags = PackageManager.MATCH_UNINSTALLED_PACKAGES;
            }
            ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName,
                    packageFlags);
            // Here, we check the ApplicationInfo of the target app, and see if the flags have
            // ApplicationInfo.FLAG_INSTALLED turned on using bitwise operation.
            //  
            if (0 == (applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)) {
                // If the app is not installed in this profile, we can enable it by
                // DPM.enableSystemApp
                if (enabled) {
                    //  APP 。
                    devicePolicyManager.enableSystemApp(
                            BasicDeviceAdminReceiver.getComponentName(activity), packageName);
                } else {
                    // But we cannot disable the app since it is already disabled
                    Log.e(TAG, "Cannot disable this app: " + packageName);
                    return;
                }
            } else {
                // If the app is already installed, we can enable or disable it by
                // DPM.setApplicationHidden
                //  APP, APP 。
                devicePolicyManager.setApplicationHidden(
                        BasicDeviceAdminReceiver.getComponentName(activity), packageName, !enabled);
            }
            Toast.makeText(activity, enabled ? R.string.enabled : R.string.disabled,
                    Toast.LENGTH_SHORT).show();
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "The app cannot be found: " + packageName, e);
        }
    }

    場合によっては、ManagedProfileユーザーで一部のAPPを禁止したり、コンポーネントを禁止したりする必要があります.
    //  
    context.getPackageManager().setComponentEnabledSetting(componentName,
                                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
    //  
    context.getPackageManager().setApplicationEnabledSetting(context.getPackageName(),
                                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);

    コンポーネントが無効になると、明示的であれ暗黙的であれ、コンポーネントに一致しません.

    5.以上のCTSテストの原理及び解決方案


    「Cross profile intent filters are set」は、実際にaddCross Profile IntentFilterで対応するIntent Filtersが設定されているかどうかをテストすることであることがわかります.
    上記の失敗した情報は、実際にはManaged profileからIntentを発行した後、ユーザー間で応答できるはずだったが、そうではなかった.これは、対応するIntent Filterがユーザー間で応答できるように設定されていないことをさらに示している.
    ただし、addCrossProfileIntentFilterは管理者権限を持つDeviceAdminReceiverコンポーネントを提供する必要があるため、アプリケーションで直接設定することはできません.
    テストの結果、アプリケーションパッケージ名がpackages/apps/ManagedProvisioning/res/values/vendor_required_apps_managed_profile.xmlに追加された場合、対応するアプリケーションはProfileの設定が完了した後、Managed profileユーザースペースにデフォルトでインストールされることが分かった.これにより、アプリケーションのIntent Filterがユーザー間で設定されなくなります.
    従って、ユーザが初期化されたときに、ユーザFLAGSに基づいてアプリケーションが利用可能か否かを設定することができる.私たちの目的は、電話アプリケーションをManged Profileの下で使用できないようにすることです.CTSテストもManged Profileユーザーを作成してテストするため、完全に正確ではないことを確認します.その後、具体的なFLAGSに基づいてCTSテストとCTS-Vテストを区別し、最終的にテストに合格した.具体コード
    権限(システムアプリケーションのみが申請できます)
    <uses-permission android:name="android.permission.MANAGE_USERS"/>
    //  Receiver
    <receiver android:name="com.tplink.dialer.BootCompletedReceiver">
        <intent-filter>
            <action android:name="android.intent.action.USER_INITIALIZE" />
        </intent-filter>
    </receiver>

    UserInfoという原生は隠れていてSDKがオープンしているのでそのまま使えます.
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if(action.equals(Intent.ACTION_USER_INITIALIZE)) {
    
            if (UserHandle.myUserId() != 0) {
                /* , UserInfo.isEnable(),
                 * Cts-V isEnable() false, CTS true.
                 * Cts-V , CTS 。
                 */
                UserManager um = UserManager.get(context);
                UserInfo currentUserInfo = um.getUserInfo(UserHandle.myUserId());
                log("current user " + currentUserInfo);
    
                if (currentUserInfo.isEnabled()) {
                    //  
                    context.getPackageManager().setApplicationEnabledSetting(context.getPackageName(),
                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
                } else {
                    //  
                    context.getPackageManager().setApplicationEnabledSetting(context.getPackageName(),
                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
                }
            }
        }
    }

    本明細書で適用されるコードの大部分は、次のとおりです.https://github.com/googlesamples/android-BasicManagedProfile
    転載先:https://github.com/shiwen500/jenkinsTest