Skip to content

Commit 5379b25

Browse files
committed
Ability to set password requirements
Bug: 26224891 Change-Id: Ie273fcc7d556c03d13d20558ad9fc6a1c1a103b9
1 parent 2b27d62 commit 5379b25

File tree

7 files changed

+394
-0
lines changed

7 files changed

+394
-0
lines changed

app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import com.afwsamples.testdpc.policy.blockuninstallation.BlockUninstallationInfoArrayAdapter;
7272
import com.afwsamples.testdpc.policy.certificate.DelegatedCertInstallerFragment;
7373
import com.afwsamples.testdpc.policy.datausage.NetworkUsageStatsFragment;
74+
import com.afwsamples.testdpc.policy.keyguard.PasswordConstraintsFragment;
7475
import com.afwsamples.testdpc.policy.locktask.KioskModeActivity;
7576
import com.afwsamples.testdpc.policy.locktask.LockTaskAppInfoArrayAdapter;
7677
import com.afwsamples.testdpc.policy.systemupdatepolicy.SystemUpdatePolicyFragment;
@@ -216,6 +217,7 @@ public class PolicyManagementFragment extends PreferenceFragment implements
216217
private static final String MANAGED_PROFILE_SPECIFIC_POLICIES_KEY = "managed_profile_policies";
217218
private static final String MANAGE_LOCK_TASK_LIST_KEY = "manage_lock_task";
218219
private static final String NETWORK_STATS_KEY = "network_stats";
220+
private static final String PASSWORD_CONSTRAINTS_KEY = "password_constraints";
219221
private static final String REENABLE_KEYGUARD = "reenable_keyguard";
220222
private static final String REENABLE_STATUS_BAR = "reenable_status_bar";
221223
private static final String REMOVE_ALL_CERTIFICATES_KEY = "remove_all_ca_certificates";
@@ -358,6 +360,7 @@ public void onCreate(Bundle savedInstanceState) {
358360
mDisableScreenCaptureSwitchPreference = (SwitchPreference) findPreference(
359361
DISABLE_SCREEN_CAPTURE_KEY);
360362
mDisableScreenCaptureSwitchPreference.setOnPreferenceChangeListener(this);
363+
findPreference(PASSWORD_CONSTRAINTS_KEY).setOnPreferenceClickListener(this);
361364
findPreference(SYSTEM_UPDATE_POLICY_KEY).setOnPreferenceClickListener(this);
362365
findPreference(NETWORK_STATS_KEY).setOnPreferenceClickListener(this);
363366
findPreference(DELEGATED_CERT_INSTALLER_KEY).setOnPreferenceClickListener(this);
@@ -537,6 +540,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
537540
case MANAGED_PROFILE_SPECIFIC_POLICIES_KEY:
538541
showFragment(new ProfilePolicyManagementFragment());
539542
return true;
543+
case PASSWORD_CONSTRAINTS_KEY:
544+
showFragment(new PasswordConstraintsFragment());
545+
return true;
540546
case SYSTEM_UPDATE_POLICY_KEY:
541547
showFragment(new SystemUpdatePolicyFragment());
542548
return true;
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright (C) 2016 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.afwsamples.testdpc.policy.keyguard;
18+
19+
import android.app.Fragment;
20+
import android.app.admin.DevicePolicyManager;
21+
import android.content.ComponentName;
22+
import android.content.Context;
23+
import android.os.Bundle;
24+
import android.text.Editable;
25+
import android.text.TextUtils;
26+
import android.text.TextWatcher;
27+
import android.view.LayoutInflater;
28+
import android.view.View;
29+
import android.view.ViewGroup;
30+
import android.widget.Button;
31+
import android.widget.EditText;
32+
import android.widget.RadioButton;
33+
import android.widget.RadioGroup;
34+
import android.widget.Toast;
35+
36+
import com.afwsamples.testdpc.DeviceAdminReceiver;
37+
import com.afwsamples.testdpc.R;
38+
39+
import java.util.LinkedHashMap;
40+
import java.util.Map;
41+
42+
/**
43+
* This fragment provides functionalities to set password constraint policies as a profile
44+
* or device owner.
45+
*
46+
* <p>These include:
47+
* <ul>
48+
* <li>{@link DevicePolicyManager#setPasswordMinimumLength(ComponentName, String)}</li>
49+
* <li>{@link DevicePolicyManager#setPasswordMinimumLetters(ComponentName, String)}</li>
50+
* <li>{@link DevicePolicyManager#setPasswordMinimumNumeric(ComponentName, String)}</li>
51+
* <li>{@link DevicePolicyManager#setPasswordMinimumLowerCase(ComponentName, String)}</li>
52+
* <li>{@link DevicePolicyManager#setPasswordMinimumUpperCase(ComponentName, String)}</li>
53+
* <li>{@link DevicePolicyManager#setPasswordMinimumSymbols(ComponentName, String)}</li>
54+
* <li>{@link DevicePolicyManager#setPasswordMinimumNonLetter(ComponentName, String)}</li>
55+
* </ul>
56+
*/
57+
public final class PasswordConstraintsFragment extends Fragment implements
58+
RadioGroup.OnCheckedChangeListener, TextWatcher {
59+
60+
private static final Map<Integer, Integer> PASSWORD_QUALITIES = new LinkedHashMap<>(7);
61+
static {
62+
// IDs of settings for {@link DevicePolicyManager#setPasswordQuality(ComponentName, int)}.
63+
final int[] policyIds = new int[] {
64+
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
65+
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
66+
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
67+
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
68+
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
69+
DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
70+
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
71+
};
72+
// Strings to show for each password quality setting.
73+
final int[] policyNames = new int[] {
74+
R.string.password_quality_unspecified,
75+
R.string.password_quality_something,
76+
R.string.password_quality_numeric,
77+
R.string.password_quality_numeric_complex,
78+
R.string.password_quality_alphabetic,
79+
R.string.password_quality_alphanumeric,
80+
R.string.password_quality_complex
81+
};
82+
if (policyIds.length != policyNames.length) {
83+
throw new AssertionError("Number of items in policyIds and policyNames do not match");
84+
}
85+
for (int i = 0; i < policyIds.length; i++) {
86+
PASSWORD_QUALITIES.put(policyIds[i], policyNames[i]);
87+
}
88+
};
89+
90+
// Radio list of all complexity settings, as defined above.
91+
private RadioGroup mQualityGroup;
92+
93+
// Individual minimum password attribute requirements.
94+
private EditText mMinLength;
95+
private EditText mMinLetters;
96+
private EditText mMinNumeric;
97+
private EditText mMinLowerCase;
98+
private EditText mMinUpperCase;
99+
private EditText mMinSymbols;
100+
private EditText mMinNonLetter;
101+
102+
private DevicePolicyManager mDpm;
103+
private ComponentName mAdminComponent;
104+
105+
@Override
106+
public void onCreate(Bundle savedInstanceState) {
107+
super.onCreate(savedInstanceState);
108+
getActivity().getActionBar().setTitle(R.string.password_constraints);
109+
110+
mDpm = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
111+
mAdminComponent = DeviceAdminReceiver.getComponentName(getActivity());
112+
}
113+
114+
@Override
115+
public View onCreateView(LayoutInflater layoutInflater, ViewGroup container,
116+
Bundle savedInstanceState) {
117+
final View root = layoutInflater.inflate(R.layout.password_quality, null);
118+
119+
// Create numeric text fields
120+
mMinLength = findAndPrepareField(root, R.id.password_min_length);
121+
mMinLetters = findAndPrepareField(root, R.id.password_min_letters);
122+
mMinNumeric = findAndPrepareField(root, R.id.password_min_numeric);
123+
mMinLowerCase = findAndPrepareField(root, R.id.password_min_lowercase);
124+
mMinUpperCase = findAndPrepareField(root, R.id.password_min_uppercase);
125+
mMinSymbols = findAndPrepareField(root, R.id.password_min_symbols);
126+
mMinNonLetter = findAndPrepareField(root, R.id.password_min_nonletter);
127+
128+
// Create radio group for password quality
129+
mQualityGroup = (RadioGroup) root.findViewById(R.id.password_quality);
130+
for (Map.Entry<Integer, Integer> entry : PASSWORD_QUALITIES.entrySet()) {
131+
final RadioButton choice = new RadioButton(getContext());
132+
choice.setId(entry.getKey());
133+
choice.setText(entry.getValue());
134+
mQualityGroup.addView(choice);
135+
}
136+
mQualityGroup.setOnCheckedChangeListener(this);
137+
138+
return root;
139+
}
140+
141+
@Override
142+
public void onResume() {
143+
super.onResume();
144+
145+
// Set the password quality radio group to show the requirement, if there is one.
146+
mQualityGroup.check(mDpm.getPasswordQuality(mAdminComponent));
147+
148+
// Update all of our minimum requirement fields via getPasswordMinimum(.*)
149+
mMinLength.setText(Integer.toString(mDpm.getPasswordMinimumLength(mAdminComponent)));
150+
mMinLetters.setText(Integer.toString(mDpm.getPasswordMinimumLetters(mAdminComponent)));
151+
mMinNumeric.setText(Integer.toString(mDpm.getPasswordMinimumNumeric(mAdminComponent)));
152+
mMinLowerCase.setText(Integer.toString(mDpm.getPasswordMinimumLowerCase(mAdminComponent)));
153+
mMinUpperCase.setText(Integer.toString(mDpm.getPasswordMinimumUpperCase(mAdminComponent)));
154+
mMinSymbols.setText(Integer.toString(mDpm.getPasswordMinimumSymbols(mAdminComponent)));
155+
mMinNonLetter.setText(Integer.toString(mDpm.getPasswordMinimumNonLetter(mAdminComponent)));
156+
}
157+
158+
@Override
159+
public void onCheckedChanged(RadioGroup view, int checkedId) {
160+
if (view == mQualityGroup) {
161+
mDpm.setPasswordQuality(mAdminComponent, checkedId);
162+
}
163+
}
164+
165+
@Override
166+
public void afterTextChanged(Editable editable) {
167+
if (TextUtils.isEmpty(editable.toString())) {
168+
return;
169+
}
170+
171+
final int value;
172+
try {
173+
value = Integer.parseInt(editable.toString());
174+
} catch (NumberFormatException e) {
175+
Toast.makeText(getActivity(), R.string.not_valid_input, Toast.LENGTH_SHORT).show();
176+
return;
177+
}
178+
179+
if (editable == mMinLength.getEditableText()) {
180+
mDpm.setPasswordMinimumLength(mAdminComponent, value);
181+
} else if (editable == mMinLetters.getEditableText()) {
182+
mDpm.setPasswordMinimumLetters(mAdminComponent, value);
183+
} else if (editable == mMinNumeric.getEditableText()) {
184+
mDpm.setPasswordMinimumNumeric(mAdminComponent, value);
185+
} else if (editable == mMinLowerCase.getEditableText()) {
186+
mDpm.setPasswordMinimumLowerCase(mAdminComponent, value);
187+
} else if (editable == mMinUpperCase.getEditableText()) {
188+
mDpm.setPasswordMinimumUpperCase(mAdminComponent, value);
189+
} else if (editable == mMinSymbols.getEditableText()) {
190+
mDpm.setPasswordMinimumSymbols(mAdminComponent, value);
191+
} else if (editable == mMinNonLetter.getEditableText()) {
192+
mDpm.setPasswordMinimumNonLetter(mAdminComponent, value);
193+
}
194+
}
195+
196+
@Override
197+
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
198+
}
199+
200+
@Override
201+
public void onTextChanged(CharSequence s, int start, int count, int after) {
202+
}
203+
204+
private EditText findAndPrepareField(View root, final int id) {
205+
EditText field = (EditText) root.findViewById(id);
206+
field.addTextChangedListener(this);
207+
return field;
208+
}
209+
210+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2016 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
19+
android:layout_width="match_parent"
20+
android:layout_height="wrap_content">
21+
22+
<LinearLayout style="@style/password_screen">
23+
24+
<!-- Password quality -->
25+
<LinearLayout
26+
android:layout_width="match_parent"
27+
android:layout_height="wrap_content"
28+
android:orientation="vertical">
29+
<TextView
30+
android:text="@string/minimum_password_quality"
31+
style="@style/password_item_label"/>
32+
33+
<!-- Radio items to be inserted by PasswordConstraintsFragment -->
34+
<RadioGroup
35+
android:id="@+id/password_quality"
36+
style="@style/radio_group"/>
37+
</LinearLayout>
38+
39+
<TableLayout
40+
android:layout_width="fill_parent"
41+
android:layout_height="wrap_content"
42+
android:shrinkColumns="0">
43+
44+
<!-- Minimum password length -->
45+
<TableRow>
46+
<TextView
47+
android:text="@string/password_min_length"
48+
style="@style/password_item_label"/>
49+
<EditText
50+
android:id="@+id/password_min_length"
51+
style="@style/password_item_content_numeric"/>
52+
</TableRow>
53+
54+
<!-- Minimum number of letters -->
55+
<TableRow>
56+
<TextView
57+
android:text="@string/password_min_letters"
58+
style="@style/password_item_label"/>
59+
<EditText
60+
android:id="@+id/password_min_letters"
61+
style="@style/password_item_content_numeric"/>
62+
</TableRow>
63+
64+
<!-- Minimum number of digits -->
65+
<TableRow>
66+
<TextView
67+
android:text="@string/password_min_numeric"
68+
style="@style/password_item_label"/>
69+
<EditText
70+
android:id="@+id/password_min_numeric"
71+
style="@style/password_item_content_numeric"/>
72+
</TableRow>
73+
74+
<!-- Minimum number of lowercase chars -->
75+
<TableRow>
76+
<TextView
77+
android:text="@string/password_min_lowercase"
78+
style="@style/password_item_label"/>
79+
<EditText
80+
android:id="@+id/password_min_lowercase"
81+
style="@style/password_item_content_numeric"/>
82+
</TableRow>
83+
84+
<!-- Minimum number of uppercase chars -->
85+
<TableRow>
86+
<TextView
87+
android:text="@string/password_min_uppercase"
88+
style="@style/password_item_label"/>
89+
<EditText
90+
android:id="@+id/password_min_uppercase"
91+
style="@style/password_item_content_numeric"/>
92+
</TableRow>
93+
94+
<!-- Minimum symbols -->
95+
<TableRow>
96+
<TextView
97+
android:text="@string/password_min_symbols"
98+
style="@style/password_item_label"/>
99+
<EditText
100+
android:id="@+id/password_min_symbols"
101+
style="@style/password_item_content_numeric"/>
102+
</TableRow>
103+
104+
<!-- Minimum non-letter characters -->
105+
<TableRow>
106+
<TextView
107+
android:text="@string/password_min_nonletter"
108+
style="@style/password_item_label"/>
109+
<EditText
110+
android:id="@+id/password_min_nonletter"
111+
style="@style/password_item_content_numeric"/>
112+
</TableRow>
113+
</TableLayout>
114+
</LinearLayout>
115+
</ScrollView>

app/src/main/res/values/dimens.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,10 @@
3030
<!-- Dimens used for edit text in alertdialog. -->
3131
<dimen name="edit_text_margin_top_bottom">8dp</dimen>
3232
<dimen name="edit_text_margin_left_right">24dp</dimen>
33+
34+
<!-- Dimens used for showing some content inset below a description. -->
35+
<dimen name="inset_padding">16dp</dimen>
36+
37+
<!-- Spacing between an EditText and its TextView label. -->
38+
<dimen name="label_edittext_padding">8dp</dimen>
3339
</resources>

app/src/main/res/values/strings.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,26 @@
456456
<string name="maximum_password_fails">Max password failures for local wipe</string>
457457
<string name="not_valid_input">Failed to update: Not a valid input</string>
458458

459+
<!-- Strings for password policies -->
460+
<string name="password_constraints">Password constraints</string>
461+
462+
<string name="minimum_password_quality">Minimum password quality:</string>
463+
<string name="password_quality_unspecified">Unspecified</string>
464+
<string name="password_quality_something">Something</string>
465+
<string name="password_quality_numeric">Numeric</string>
466+
<string name="password_quality_numeric_complex">Numeric (Complex)</string>
467+
<string name="password_quality_alphabetic">Alphabetic</string>
468+
<string name="password_quality_alphanumeric">Alphanumeric</string>
469+
<string name="password_quality_complex">Complex</string>
470+
471+
<string name="password_min_length">Minimum password length:</string>
472+
<string name="password_min_letters">Minimum number of letters:</string>
473+
<string name="password_min_numeric">Minimum number of digits:</string>
474+
<string name="password_min_lowercase">Minimum number of lower-case characters:</string>
475+
<string name="password_min_uppercase">Minimum number of upper-case characters:</string>
476+
<string name="password_min_symbols">Minimum number of symbols:</string>
477+
<string name="password_min_nonletter">Minimum number of non-letters:</string>
478+
459479
<!-- Strings for managing Settings -->
460480
<string name="settings_management_title">Settings management</string>
461481
<string name="stay_on_while_plugged_in">Keep the device on while plugged in</string>

0 commit comments

Comments
 (0)