1 /*
2  * Copyright (C) 2020 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.settings.security;
18 
19 import android.app.Dialog;
20 import android.app.admin.DevicePolicyEventLogger;
21 import android.app.settings.SettingsEnums;
22 import android.content.Context;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.RemoteException;
27 import android.security.IKeyChainService;
28 import android.security.KeyChain;
29 import android.stats.devicepolicy.DevicePolicyEnums;
30 import android.util.Log;
31 import android.view.View;
32 
33 import androidx.appcompat.app.AlertDialog;
34 import androidx.fragment.app.Fragment;
35 import androidx.preference.PreferenceScreen;
36 
37 import com.android.settings.R;
38 import com.android.settings.core.BasePreferenceController;
39 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
40 import com.android.settingslib.widget.ActionButtonsPreference;
41 
42 import java.util.concurrent.ExecutorService;
43 import java.util.concurrent.Executors;
44 
45 /**
46  * Controller that shows the remove button of the credential management app, which allows the user
47  * to remove the credential management app and its certificates.
48  */
49 public class CredentialManagementAppButtonsController extends BasePreferenceController {
50 
51     private static final String TAG = "CredentialManagementApp";
52 
53     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
54     private final Handler mHandler = new Handler(Looper.getMainLooper());
55     private boolean mHasCredentialManagerPackage;
56     private Fragment mFragment;
57     private final int mRemoveIcon;
58 
CredentialManagementAppButtonsController(Context context, String preferenceKey)59     public CredentialManagementAppButtonsController(Context context, String preferenceKey) {
60         super(context, preferenceKey);
61         if (context.getResources().getConfiguration().getLayoutDirection()
62                 == View.LAYOUT_DIRECTION_RTL) {
63             mRemoveIcon = R.drawable.ic_redo_24;
64         } else {
65             mRemoveIcon = R.drawable.ic_undo_24;
66         }
67     }
68 
setParentFragment(Fragment fragment)69     public void setParentFragment(Fragment fragment) {
70         mFragment = fragment;
71     }
72 
73     @Override
getAvailabilityStatus()74     public int getAvailabilityStatus() {
75         return AVAILABLE_UNSEARCHABLE;
76     }
77 
78     @Override
displayPreference(PreferenceScreen screen)79     public void displayPreference(PreferenceScreen screen) {
80         super.displayPreference(screen);
81 
82         mExecutor.execute(() -> {
83             try {
84                 IKeyChainService service = KeyChain.bind(mContext).getService();
85                 mHasCredentialManagerPackage = service.hasCredentialManagementApp();
86             } catch (InterruptedException | RemoteException e) {
87                 Log.e(TAG, "Unable to display credential management app buttons");
88             }
89             mHandler.post(() -> displayButtons(screen));
90         });
91     }
92 
displayButtons(PreferenceScreen screen)93     private void displayButtons(PreferenceScreen screen) {
94         if (mHasCredentialManagerPackage) {
95             ((ActionButtonsPreference) screen.findPreference(getPreferenceKey()))
96                     .setButton1Text(R.string.uninstall_certs_credential_management_app)
97                     .setButton1Icon(R.drawable.ic_upload)
98                     .setButton1OnClickListener(view -> uninstallCertificates())
99                     .setButton2Text(R.string.remove_credential_management_app)
100                     .setButton2Icon(mRemoveIcon)
101                     .setButton2OnClickListener(view -> showRemoveCredentialManagementAppDialog());
102         }
103     }
104 
uninstallCertificates()105     private void uninstallCertificates() {
106         mExecutor.execute(() -> {
107             try {
108                 IKeyChainService service = KeyChain.bind(mContext).getService();
109                 for (String existingAlias :
110                         service.getCredentialManagementAppPolicy().getAliases()) {
111                     service.removeKeyPair(existingAlias);
112                 }
113             } catch (InterruptedException | RemoteException e) {
114                 Log.e(TAG, "Unable to uninstall certificates");
115             }
116         });
117     }
118 
showRemoveCredentialManagementAppDialog()119     private void showRemoveCredentialManagementAppDialog() {
120         final RemoveCredentialManagementAppDialog dialog =
121                 RemoveCredentialManagementAppDialog.newInstance();
122         dialog.show(mFragment.getParentFragmentManager(),
123                 RemoveCredentialManagementAppDialog.class.getName());
124     }
125 
126     /**
127      * Implements an AlertDialog for confirming that a user wants to remove the credential
128      * management app. The app will no longer be able to manage certificates, but it will stay on
129      * the device. All certificates installed by the credential management app will be uninstalled.
130      */
131     public static class RemoveCredentialManagementAppDialog extends InstrumentedDialogFragment {
132 
newInstance()133         public static RemoveCredentialManagementAppDialog newInstance() {
134             return new RemoveCredentialManagementAppDialog();
135         }
136 
137         @Override
onCreateDialog(Bundle savedInstanceState)138         public Dialog onCreateDialog(Bundle savedInstanceState) {
139             return new AlertDialog.Builder(getContext(), R.style.Theme_AlertDialog)
140                     .setTitle(R.string.remove_credential_management_app_dialog_title)
141                     .setMessage(R.string.remove_credential_management_app_dialog_message)
142                     .setPositiveButton(R.string.remove_credential_management_app,
143                             (dialog, which) -> removeCredentialManagementApp())
144                     .setNegativeButton(R.string.cancel, (dialog, which) -> dismiss())
145                     .create();
146         }
147 
removeCredentialManagementApp()148         private void removeCredentialManagementApp() {
149             final ExecutorService executor = Executors.newSingleThreadExecutor();
150             executor.execute(() -> {
151                 try {
152                     IKeyChainService service = KeyChain.bind(getContext()).getService();
153                     service.removeCredentialManagementApp();
154                     DevicePolicyEventLogger
155                             .createEvent(DevicePolicyEnums.CREDENTIAL_MANAGEMENT_APP_REMOVED)
156                             .write();
157                     getParentFragment().getActivity().finish();
158                 } catch (InterruptedException | RemoteException e) {
159                     Log.e(TAG, "Unable to remove the credential management app");
160                 }
161             });
162         }
163 
164         @Override
getMetricsCategory()165         public int getMetricsCategory() {
166             return SettingsEnums.CREDENTIAL_MANAGEMENT_APP_REMOVE_APP;
167         }
168     }
169 }