SecurityPolicyTests.java revision aeee10e57ef4d931e7708fde218d590453a82aea
1345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler/* 2345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * Copyright (C) 2010 The Android Open Source Project 3345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * 4345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * Licensed under the Apache License, Version 2.0 (the "License"); 5345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * you may not use this file except in compliance with the License. 6345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * You may obtain a copy of the License at 7345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * 8345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * http://www.apache.org/licenses/LICENSE-2.0 9345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * 10345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * Unless required by applicable law or agreed to in writing, software 11345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * distributed under the License is distributed on an "AS IS" BASIS, 12345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * See the License for the specific language governing permissions and 14345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * limitations under the License. 15345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */ 16345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 17345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerpackage com.android.email; 18345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 191ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadlerimport com.android.email.provider.ContentCache; 209ba506c4dd498150555f6c59aa758f7467bf9236Marc Blankimport com.android.email.provider.EmailProvider; 219ba506c4dd498150555f6c59aa758f7467bf9236Marc Blankimport com.android.email.provider.ProviderTestUtils; 22a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent; 23a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Account; 24a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Mailbox; 25a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Message; 26aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blankimport com.android.emailcommon.provider.Policy; 27aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blankimport com.android.emailcommon.service.LegacyPolicySet; 28345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 29a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadlerimport android.app.admin.DevicePolicyManager; 30345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.content.Context; 31d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadlerimport android.content.ContextWrapper; 32345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.test.ProviderTestCase2; 33345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.test.suitebuilder.annotation.MediumTest; 34345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.test.suitebuilder.annotation.SmallTest; 35345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 36345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler/** 37345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * This is a series of unit tests for backup/restore of the SecurityPolicy class. 382b2b3448ec200f3d649e5f57309908d28ce3bfc7Marc Blank * 399b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank * You can run this entire test case with: 409b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank * runtest -c com.android.email.SecurityPolicyTests email 41345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */ 429b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank 43345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler@MediumTest 44345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerpublic class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> { 45345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 46345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler private Context mMockContext; 47aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private SecurityPolicy mSecurityPolicy; 48d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler 49345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler public SecurityPolicyTests() { 5031d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank super(EmailProvider.class, EmailContent.AUTHORITY); 51345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler } 52345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 53aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private static final Policy EMPTY_POLICY = new Policy(); 54aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank 55345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler @Override 56345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler protected void setUp() throws Exception { 57345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler super.setUp(); 58aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mMockContext = new MockContext2(getMockContext(), mContext); 591ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Invalidate all caches, since we reset the database for each test 601ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler ContentCache.invalidateAllCachesForTest(); 61345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler } 62345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 63345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler /** 64345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * Delete any dummy accounts we set up for this test 65345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */ 66345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler @Override 67345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler protected void tearDown() throws Exception { 68345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler super.tearDown(); 69345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler } 70345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 71345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler /** 720fb092b38912c7cff776a51872840bb2089ebe08Makoto Onuki * Private context wrapper used to add back getPackageName() for these tests. 730fb092b38912c7cff776a51872840bb2089ebe08Makoto Onuki * 74a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler * This class also implements {@link Context} method(s) that are called during tests. 75d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler */ 76d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler private static class MockContext2 extends ContextWrapper { 77d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler 78d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler private final Context mRealContext; 79d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler 80d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler public MockContext2(Context mockContext, Context realContext) { 81d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler super(mockContext); 82d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler mRealContext = realContext; 83d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler } 84d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler 85d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler @Override 860fb092b38912c7cff776a51872840bb2089ebe08Makoto Onuki public Context getApplicationContext() { 870fb092b38912c7cff776a51872840bb2089ebe08Makoto Onuki return this; 880fb092b38912c7cff776a51872840bb2089ebe08Makoto Onuki } 890fb092b38912c7cff776a51872840bb2089ebe08Makoto Onuki 900fb092b38912c7cff776a51872840bb2089ebe08Makoto Onuki @Override 91d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler public String getPackageName() { 92d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler return mRealContext.getPackageName(); 93d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler } 94a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler 95a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler @Override 96a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler public Object getSystemService(String name) { 97a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler return mRealContext.getSystemService(name); 98a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler } 99d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler } 100d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler 101d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler /** 102aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank * Create a Policy using the arguments formerly used to create a PolicySet; this minimizes the 103aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank * changes needed for re-using the PolicySet unit test logic 104345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */ 105aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private Policy setupPolicy(int minPasswordLength, int passwordMode, int maxPasswordFails, 106aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpirationDays, 107aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank int passwordHistory, int passwordComplexChars, boolean requireEncryption, 108aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank boolean requireEncryptionExternal) throws IllegalArgumentException { 109aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy policy = new Policy(); 110aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mPasswordMinLength = minPasswordLength; 111aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mPasswordMode = passwordMode; 112aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mPasswordMaxFails = maxPasswordFails; 113aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mMaxScreenLockTime = maxScreenLockTime; 114aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mRequireRemoteWipe = requireRemoteWipe; 115aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mPasswordExpirationDays = passwordExpirationDays; 116aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mPasswordHistory = passwordHistory; 117aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mPasswordComplexChars = passwordComplexChars; 118aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mRequireEncryption = requireEncryption; 119aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy.mRequireEncryptionExternal = requireEncryptionExternal; 120aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank return policy; 1211d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank } 1221d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank 123345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler /** 124345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * Test business logic of aggregating accounts with policies 125345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */ 126345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler public void testAggregator() { 127aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mSecurityPolicy = SecurityPolicy.getInstance(mMockContext); 128345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 129d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler // with no accounts, should return empty set 130aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(EMPTY_POLICY, mSecurityPolicy.computeAggregatePolicy()); 131345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 132d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler // with accounts having no security, empty set 133aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank ProviderTestUtils.setupAccount("no-sec-1", true, mMockContext); 134aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank ProviderTestUtils.setupAccount("no-sec-2", true, mMockContext); 135aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(EMPTY_POLICY, mSecurityPolicy.computeAggregatePolicy()); 136345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 137345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // with a single account in security mode, should return same security as in account 1383d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler // first test with partially-populated policies 139aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a3 = ProviderTestUtils.setupAccount("sec-3", true, mMockContext); 140aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3ain = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0, 1417fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 142aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p3ain.setAccountPolicy(mMockContext, a3, "0"); 143aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3aout = mSecurityPolicy.computeAggregatePolicy(); 1443d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler assertNotNull(p3aout); 1453d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler assertEquals(p3ain, p3aout); 1463d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler 1473d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler // Repeat that test with fully-populated policies 148aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3bin = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 15, 16, false, 6, 2, 3, 1497fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 150aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p3bin.setAccountPolicy(mMockContext, a3, "0"); 151aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3bout = mSecurityPolicy.computeAggregatePolicy(); 1523d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler assertNotNull(p3bout); 1533d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler assertEquals(p3bin, p3bout); 154345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 155345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // add another account which mixes it up (some fields will change, others will not) 156345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // pw length and pw mode - max logic - will change because larger #s here 157345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // fail count and lock timer - min logic - will *not* change because larger #s here 158345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // wipe required - OR logic - will *not* change here because false 1591ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // expiration - will not change because 0 (unspecified) 1601ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // max complex chars - max logic - will change 161469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler // encryption required - OR logic - will *not* change here because false 1627fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler // encryption external req'd - OR logic - will *not* change here because false 163aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p4in = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 5, 7, 1647fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 165aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a4 = ProviderTestUtils.setupAccount("sec-4", true, mMockContext); 166aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p4in.setAccountPolicy(mMockContext, a4, "0"); 167aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p4out = mSecurityPolicy.computeAggregatePolicy(); 168345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler assertNotNull(p4out); 169aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(20, p4out.mPasswordMinLength); 170aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(Policy.PASSWORD_MODE_STRONG, p4out.mPasswordMode); 171aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(15, p4out.mPasswordMaxFails); 172345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler assertEquals(16, p4out.mMaxScreenLockTime); 1731ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(6, p4out.mPasswordExpirationDays); 1749b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank assertEquals(5, p4out.mPasswordHistory); 1759b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank assertEquals(7, p4out.mPasswordComplexChars); 176345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler assertFalse(p4out.mRequireRemoteWipe); 177469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler assertFalse(p4out.mRequireEncryption); 1787fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler assertFalse(p4out.mRequireEncryptionExternal); 179345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 180345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // add another account which mixes it up (the remaining fields will change) 181345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // pw length and pw mode - max logic - will *not* change because smaller #s here 182345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // fail count and lock timer - min logic - will change because smaller #s here 183345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler // wipe required - OR logic - will change here because true 1841ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // expiration time - min logic - will change because lower here 1851ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // history & complex chars - will not change because 0 (unspecified) 186469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler // encryption required - OR logic - will change here because true 1877fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler // encryption external req'd - OR logic - will *not* change here because false 188aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p5in = setupPolicy(4, Policy.PASSWORD_MODE_SIMPLE, 5, 6, true, 1, 0, 0, 1897fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler true, false); 190aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a5 = ProviderTestUtils.setupAccount("sec-5", true, mMockContext); 191aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p5in.setAccountPolicy(mMockContext, a5, "0"); 192aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p5out = mSecurityPolicy.computeAggregatePolicy(); 193345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler assertNotNull(p5out); 194aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(20, p5out.mPasswordMinLength); 195aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(Policy.PASSWORD_MODE_STRONG, p5out.mPasswordMode); 196aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(5, p5out.mPasswordMaxFails); 197345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler assertEquals(6, p5out.mMaxScreenLockTime); 1981ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(1, p5out.mPasswordExpirationDays); 199469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler assertEquals(5, p5out.mPasswordHistory); 200469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler assertEquals(7, p5out.mPasswordComplexChars); 201345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler assertTrue(p5out.mRequireRemoteWipe); 2027fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler assertFalse(p5out.mRequireEncryptionExternal); 2037fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler 2047fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler // add another account that continues to mutate fields 2057fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler // encryption external req'd - OR logic - will change here because true 206aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p6in = setupPolicy(0, Policy.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0, 2077fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, true); 208aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a6 = ProviderTestUtils.setupAccount("sec-6", true, mMockContext); 209aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p6in.setAccountPolicy(mMockContext, a6, "0"); 210aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p6out = mSecurityPolicy.computeAggregatePolicy(); 2117fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler assertNotNull(p6out); 2127fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler assertTrue(p6out.mRequireEncryptionExternal); 213345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler } 214345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 215345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler /** 2169b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank * Test equality. Note, the tests for inequality are poor, as each field should 217345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * be tested individually. 218345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */ 219345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler @SmallTest 2209b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank public void testEquals() { 221aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p1 = 222aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false); 223aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p2 = 224aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false); 225aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3 = 226aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank setupPolicy(2, Policy.PASSWORD_MODE_SIMPLE, 5, 6, true, 7, 8, 9, false, false); 227345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler assertTrue(p1.equals(p2)); 228345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler assertFalse(p2.equals(p3)); 229345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler } 230345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler 2312a5eeea9213005060256054ec773e72406415ce4Andrew Stadler /** 2322a5eeea9213005060256054ec773e72406415ce4Andrew Stadler * Test the API to set/clear policy hold flags in an account 2332a5eeea9213005060256054ec773e72406415ce4Andrew Stadler */ 2342a5eeea9213005060256054ec773e72406415ce4Andrew Stadler public void testSetClearHoldFlag() { 2352a5eeea9213005060256054ec773e72406415ce4Andrew Stadler Account a1 = ProviderTestUtils.setupAccount("holdflag-1", false, mMockContext); 2362a5eeea9213005060256054ec773e72406415ce4Andrew Stadler a1.mFlags = Account.FLAGS_NOTIFY_NEW_MAIL; 2372a5eeea9213005060256054ec773e72406415ce4Andrew Stadler a1.save(mMockContext); 2382a5eeea9213005060256054ec773e72406415ce4Andrew Stadler Account a2 = ProviderTestUtils.setupAccount("holdflag-2", false, mMockContext); 2399e2ddca59d048fc9ac55278b193ee36b330a7981Jim Shuma a2.mFlags = Account.FLAGS_VIBRATE_ALWAYS | Account.FLAGS_SECURITY_HOLD; 2402a5eeea9213005060256054ec773e72406415ce4Andrew Stadler a2.save(mMockContext); 2412a5eeea9213005060256054ec773e72406415ce4Andrew Stadler 2422a5eeea9213005060256054ec773e72406415ce4Andrew Stadler // confirm clear until set 2432a5eeea9213005060256054ec773e72406415ce4Andrew Stadler Account a1a = Account.restoreAccountWithId(mMockContext, a1.mId); 2442a5eeea9213005060256054ec773e72406415ce4Andrew Stadler assertEquals(Account.FLAGS_NOTIFY_NEW_MAIL, a1a.mFlags); 245aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank SecurityPolicy.setAccountHoldFlag(mMockContext, a1, true); 2462a5eeea9213005060256054ec773e72406415ce4Andrew Stadler assertEquals(Account.FLAGS_NOTIFY_NEW_MAIL | Account.FLAGS_SECURITY_HOLD, a1.mFlags); 2472a5eeea9213005060256054ec773e72406415ce4Andrew Stadler Account a1b = Account.restoreAccountWithId(mMockContext, a1.mId); 2482a5eeea9213005060256054ec773e72406415ce4Andrew Stadler assertEquals(Account.FLAGS_NOTIFY_NEW_MAIL | Account.FLAGS_SECURITY_HOLD, a1b.mFlags); 2492a5eeea9213005060256054ec773e72406415ce4Andrew Stadler 2502a5eeea9213005060256054ec773e72406415ce4Andrew Stadler // confirm set until cleared 2512a5eeea9213005060256054ec773e72406415ce4Andrew Stadler Account a2a = Account.restoreAccountWithId(mMockContext, a2.mId); 2529e2ddca59d048fc9ac55278b193ee36b330a7981Jim Shuma assertEquals(Account.FLAGS_VIBRATE_ALWAYS | Account.FLAGS_SECURITY_HOLD, a2a.mFlags); 253aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank SecurityPolicy.setAccountHoldFlag(mMockContext, a2, false); 2549e2ddca59d048fc9ac55278b193ee36b330a7981Jim Shuma assertEquals(Account.FLAGS_VIBRATE_ALWAYS, a2.mFlags); 2552a5eeea9213005060256054ec773e72406415ce4Andrew Stadler Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId); 2569e2ddca59d048fc9ac55278b193ee36b330a7981Jim Shuma assertEquals(Account.FLAGS_VIBRATE_ALWAYS, a2b.mFlags); 2572a5eeea9213005060256054ec773e72406415ce4Andrew Stadler } 2582a5eeea9213005060256054ec773e72406415ce4Andrew Stadler 259aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private static class MockController extends Controller { 260aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank protected MockController(Context context) { 261aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank super(context); 262aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 263aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank 264aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank protected void backupAccounts(Context context) { 265aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank // For testing, we don't want to back up our accounts 266aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 267aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 26802d59d21949a77c60859b615312f02e6d8003490Marc Blank 2692a5eeea9213005060256054ec773e72406415ce4Andrew Stadler /** 270af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler * Test the response to disabling DeviceAdmin status 271af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler */ 272af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler public void testDisableAdmin() { 273aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a1 = ProviderTestUtils.setupAccount("disable-1", true, mMockContext); 274aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p1 = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0, 2757fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 276aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p1.setAccountPolicy(mMockContext, a1, "security-sync-key-1"); 277af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler 278aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a2 = ProviderTestUtils.setupAccount("disable-2", true, mMockContext); 279aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0, 2807fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 281aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p2.setAccountPolicy(mMockContext, a2, "security-sync-key-2"); 282af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler 283aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a3 = ProviderTestUtils.setupAccount("disable-3", true, mMockContext); 284aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy.clearAccountPolicy(mMockContext, a3); 285af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler 286aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mSecurityPolicy = SecurityPolicy.getInstance(mMockContext); 287af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler 288aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank // Confirm that "enabling" device admin does not change security status (policy & sync key) 289aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy before = mSecurityPolicy.getAggregatePolicy(); 290aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mSecurityPolicy.onAdminEnabled(true); // "enabled" should not change anything 291aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy after1 = mSecurityPolicy.getAggregatePolicy(); 292af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler assertEquals(before, after1); 293af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler Account a1a = Account.restoreAccountWithId(mMockContext, a1.mId); 294af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler assertNotNull(a1a.mSecuritySyncKey); 295aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertTrue(a1a.mPolicyKey > 0); 296af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler Account a2a = Account.restoreAccountWithId(mMockContext, a2.mId); 297af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler assertNotNull(a2a.mSecuritySyncKey); 298aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertTrue(a2a.mPolicyKey > 0); 299af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler Account a3a = Account.restoreAccountWithId(mMockContext, a3.mId); 300af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler assertNull(a3a.mSecuritySyncKey); 301aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertTrue(a3a.mPolicyKey == 0); 302af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler 30302d59d21949a77c60859b615312f02e6d8003490Marc Blank // Simulate revoke of device admin; directly call deleteSecuredAccounts, which is normally 30402d59d21949a77c60859b615312f02e6d8003490Marc Blank // called from a background thread 305aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank MockController mockController = new MockController(mMockContext); 306aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Controller.injectMockControllerForTest(mockController); 307aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank try { 308aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mSecurityPolicy.deleteSecuredAccounts(mMockContext); 309aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy after2 = mSecurityPolicy.getAggregatePolicy(); 310aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertEquals(EMPTY_POLICY, after2); 311aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a1b = Account.restoreAccountWithId(mMockContext, a1.mId); 312aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertNull(a1b); 313aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId); 314aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertNull(a2b); 315aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a3b = Account.restoreAccountWithId(mMockContext, a3.mId); 316aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertNull(a3b.mSecuritySyncKey); 317aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } finally { 318aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Controller.injectMockControllerForTest(null); 319aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 3201ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler } 3211ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3221ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler /** 3231ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler * Test the scanner that finds expiring accounts 3241ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler */ 3251ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler public void testFindExpiringAccount() { 326aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank ProviderTestUtils.setupAccount("expiring-1", true, mMockContext); 3271ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3281ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // With no expiring accounts, this should return null. 3297fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler long nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext); 3301ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(-1, nextExpiringAccountId); 3311ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3321ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Add a single expiring account 333aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a2 = 334aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank ProviderTestUtils.setupAccount("expiring-2", true, mMockContext); 335aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 30, 0, 0, 3367fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 337aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p2.setAccountPolicy(mMockContext, a2, "0"); 3381ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3391ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // The expiring account should be returned 3407fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext); 3411ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(a2.mId, nextExpiringAccountId); 3421ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3431ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Add an account with a longer expiration 344aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a3 = ProviderTestUtils.setupAccount("expiring-3", true, mMockContext); 345aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 60, 0, 0, 3467fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 347aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p3.setAccountPolicy(mMockContext, a3, "0"); 3481ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3491ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // The original expiring account (a2) should be returned 3507fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext); 3511ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(a2.mId, nextExpiringAccountId); 3521ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3531ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Add an account with a shorter expiration 354aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a4 = ProviderTestUtils.setupAccount("expiring-4", true, mMockContext); 355aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p4 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 15, 0, 0, 3567fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 357aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p4.setAccountPolicy(mMockContext, a4, "0"); 3581ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3591ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // The new expiring account (a4) should be returned 3607fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext); 3611ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(a4.mId, nextExpiringAccountId); 3621ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler } 3631ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3641ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler /** 3651ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler * Lightweight subclass of the Controller class allows injection of mock context 3661ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler */ 3671ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler public static class TestController extends Controller { 3681ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3691ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler protected TestController(Context providerContext, Context systemContext) { 3701ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler super(systemContext); 3711ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler setProviderContext(providerContext); 37202d59d21949a77c60859b615312f02e6d8003490Marc Blank } 373af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler } 3741ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3751ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler /** 3761ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler * Test the scanner that wipes expiring accounts 3771ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler */ 3781ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler public void testWipeExpiringAccounts() { 379aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mSecurityPolicy = SecurityPolicy.getInstance(mMockContext); 3801ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler TestController testController = new TestController(mMockContext, getContext()); 3811ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3821ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Two accounts - a1 is normal, a2 has security (but no expiration) 3831ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler Account a1 = ProviderTestUtils.setupAccount("expired-1", true, mMockContext); 384aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a2 = ProviderTestUtils.setupAccount("expired-2", true, mMockContext); 385aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0, 3867fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 387aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p2.setAccountPolicy(mMockContext, a2, "0"); 3881ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 3891ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Add a mailbox & messages to each account 3901ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler long account1Id = a1.mId; 3911ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler long account2Id = a2.mId; 3921ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler Mailbox box1 = ProviderTestUtils.setupMailbox("box1", account1Id, true, mMockContext); 3931ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler long box1Id = box1.mId; 3941ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler ProviderTestUtils.setupMessage("message1", account1Id, box1Id, false, true, mMockContext); 3951ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler ProviderTestUtils.setupMessage("message2", account1Id, box1Id, false, true, mMockContext); 3961ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler Mailbox box2 = ProviderTestUtils.setupMailbox("box2", account2Id, true, mMockContext); 3971ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler long box2Id = box2.mId; 3981ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler ProviderTestUtils.setupMessage("message3", account2Id, box2Id, false, true, mMockContext); 3991ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler ProviderTestUtils.setupMessage("message4", account2Id, box2Id, false, true, mMockContext); 4001ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 4011ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Run the expiration code - should do nothing 402aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank boolean wiped = SecurityPolicy.wipeExpiredAccounts(mMockContext, testController); 4031ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertFalse(wiped); 4041ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // check mailboxes & messages not wiped 4051ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI)); 4061ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(2, EmailContent.count(mMockContext, Mailbox.CONTENT_URI)); 4071ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(4, EmailContent.count(mMockContext, Message.CONTENT_URI)); 4081ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 4091ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Add 3rd account that really expires 410aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Account a3 = ProviderTestUtils.setupAccount("expired-3", true, mMockContext); 411aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 30, 0, 0, 4127fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler false, false); 413aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank p3.setAccountPolicy(mMockContext, a3, "0"); 4141ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 4151ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Add mailbox & messages to 3rd account 4161ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler long account3Id = a3.mId; 4171ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler Mailbox box3 = ProviderTestUtils.setupMailbox("box3", account3Id, true, mMockContext); 4181ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler long box3Id = box3.mId; 4191ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler ProviderTestUtils.setupMessage("message5", account3Id, box3Id, false, true, mMockContext); 4201ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler ProviderTestUtils.setupMessage("message6", account3Id, box3Id, false, true, mMockContext); 4211ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 4221ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // check new counts 4231ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(3, EmailContent.count(mMockContext, Account.CONTENT_URI)); 4241ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(3, EmailContent.count(mMockContext, Mailbox.CONTENT_URI)); 4251ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(6, EmailContent.count(mMockContext, Message.CONTENT_URI)); 4261ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 4271ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Run the expiration code - wipe acct #3 428aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank wiped = SecurityPolicy.wipeExpiredAccounts(mMockContext, testController); 4291ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertTrue(wiped); 4301ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // check new counts - account survives but data is wiped 4311ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(3, EmailContent.count(mMockContext, Account.CONTENT_URI)); 4321ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(2, EmailContent.count(mMockContext, Mailbox.CONTENT_URI)); 4331ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(4, EmailContent.count(mMockContext, Message.CONTENT_URI)); 4341ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 4351ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Check security hold states - only #3 should be in hold 4361ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler Account account = Account.restoreAccountWithId(mMockContext, account1Id); 4371ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(0, account.mFlags & Account.FLAGS_SECURITY_HOLD); 4381ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler account = Account.restoreAccountWithId(mMockContext, account2Id); 4391ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(0, account.mFlags & Account.FLAGS_SECURITY_HOLD); 4401ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler account = Account.restoreAccountWithId(mMockContext, account3Id); 4411ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler assertEquals(Account.FLAGS_SECURITY_HOLD, account.mFlags & Account.FLAGS_SECURITY_HOLD); 4421ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler } 443a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler 444a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler /** 445a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler * Test the code that clears unsupported policies 446a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler * TODO inject a mock DPM so we can directly control & test all cases, no matter what device 447a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler */ 448a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler public void testClearUnsupportedPolicies() { 449aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p1 = 450aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false); 451aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p2 = 452aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, true, false); 453aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3 = 454aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, true); 455aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank 456aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mSecurityPolicy = SecurityPolicy.getInstance(mMockContext); 457aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank DevicePolicyManager dpm = mSecurityPolicy.getDPM(); 458a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler boolean hasEncryption = 459a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler dpm.getStorageEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; 460a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler 461aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p1Result = mSecurityPolicy.clearUnsupportedPolicies(p1); 462aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p2Result = mSecurityPolicy.clearUnsupportedPolicies(p2); 463aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3Result = mSecurityPolicy.clearUnsupportedPolicies(p3); 464a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler 4657fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler // No changes expected when encryptionRequested bits were false 466a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler assertEquals(p1, p1Result); 467a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler if (hasEncryption) { 468a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler // No changes expected 4697fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler // NOTE: TODO: Modify to check for external encryption cleared on devices that 4707fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler // won't support it (e.g. having only unencrypted, removable storage.) 471a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler assertEquals(p2, p2Result); 4727fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler assertEquals(p3, p3Result); 473a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler } else { 4747fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler // If encryption is unsupported, encryption policy bits are cleared 475aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy policyExpect = 476aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, 477aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank false); 4787fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler assertEquals(policyExpect, p2Result); 4797fd14be80447de15bd5360321fa80e34f60fa251Andy Stadler assertEquals(policyExpect, p3Result); 480a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler } 481a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler } 48222759bacd95385d95d3d9321f490763df1aba89dAndy Stadler 48322759bacd95385d95d3d9321f490763df1aba89dAndy Stadler /** 48422759bacd95385d95d3d9321f490763df1aba89dAndy Stadler * Test the code that converts from exchange-style quality to DPM/Lockscreen style quality. 48522759bacd95385d95d3d9321f490763df1aba89dAndy Stadler */ 48622759bacd95385d95d3d9321f490763df1aba89dAndy Stadler public void testGetDPManagerPasswordQuality() { 487aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank // Policy.PASSWORD_MODE_NONE -> DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED 488aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p1 = setupPolicy(0, Policy.PASSWORD_MODE_NONE, 48922759bacd95385d95d3d9321f490763df1aba89dAndy Stadler 0, 0, false, 0, 0, 0, false, false); 49022759bacd95385d95d3d9321f490763df1aba89dAndy Stadler assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 49122759bacd95385d95d3d9321f490763df1aba89dAndy Stadler p1.getDPManagerPasswordQuality()); 49222759bacd95385d95d3d9321f490763df1aba89dAndy Stadler 49322759bacd95385d95d3d9321f490763df1aba89dAndy Stadler // PASSWORD_MODE_SIMPLE -> PASSWORD_QUALITY_NUMERIC 494aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p2 = setupPolicy(4, Policy.PASSWORD_MODE_SIMPLE, 49522759bacd95385d95d3d9321f490763df1aba89dAndy Stadler 0, 0, false, 0, 0, 0, false, false); 49622759bacd95385d95d3d9321f490763df1aba89dAndy Stadler assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, 49722759bacd95385d95d3d9321f490763df1aba89dAndy Stadler p2.getDPManagerPasswordQuality()); 49822759bacd95385d95d3d9321f490763df1aba89dAndy Stadler 49922759bacd95385d95d3d9321f490763df1aba89dAndy Stadler // PASSWORD_MODE_STRONG -> PASSWORD_QUALITY_ALPHANUMERIC 500aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p3 = setupPolicy(4, Policy.PASSWORD_MODE_STRONG, 50122759bacd95385d95d3d9321f490763df1aba89dAndy Stadler 0, 0, false, 0, 0, 0, false, false); 50222759bacd95385d95d3d9321f490763df1aba89dAndy Stadler assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, 50322759bacd95385d95d3d9321f490763df1aba89dAndy Stadler p3.getDPManagerPasswordQuality()); 50422759bacd95385d95d3d9321f490763df1aba89dAndy Stadler 50522759bacd95385d95d3d9321f490763df1aba89dAndy Stadler // PASSWORD_MODE_STRONG + complex chars -> PASSWORD_QUALITY_COMPLEX 506aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy p4 = setupPolicy(4, Policy.PASSWORD_MODE_STRONG, 50722759bacd95385d95d3d9321f490763df1aba89dAndy Stadler 0, 0, false, 0, 0 , 2, false, false); 50822759bacd95385d95d3d9321f490763df1aba89dAndy Stadler assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 50922759bacd95385d95d3d9321f490763df1aba89dAndy Stadler p4.getDPManagerPasswordQuality()); 51022759bacd95385d95d3d9321f490763df1aba89dAndy Stadler } 511aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank 512aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private boolean policySetEqualsPolicy(PolicySet ps, Policy policy) { 513aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if ((ps.mPasswordMode >> LegacyPolicySet.PASSWORD_MODE_SHIFT) != policy.mPasswordMode) { 514aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank return false; 515aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 516aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mMinPasswordLength != policy.mPasswordMinLength) return false; 517aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mPasswordComplexChars != policy.mPasswordComplexChars) return false; 518aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mPasswordHistory != policy.mPasswordHistory) return false; 519aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mPasswordExpirationDays != policy.mPasswordExpirationDays) return false; 520aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mMaxPasswordFails != policy.mPasswordMaxFails) return false; 521aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mMaxScreenLockTime != policy.mMaxScreenLockTime) return false; 522aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mRequireRemoteWipe != policy.mRequireRemoteWipe) return false; 523aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mRequireEncryption != policy.mRequireEncryption) return false; 524aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank if (ps.mRequireEncryptionExternal != policy.mRequireEncryptionExternal) return false; 525aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank return true; 526aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 527aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank 528aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank public void testPolicyFlagsToPolicy() { 529aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank // Policy flags; the three sets included here correspond to policies for three test 530aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank // accounts that, between them, use all of the possible policies 531aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank long flags = 67096612L; 532aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank PolicySet ps = new PolicySet(flags); 533aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank Policy policy = LegacyPolicySet.flagsToPolicy(flags); 534aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertTrue(policySetEqualsPolicy(ps, policy)); 535aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank flags = 52776591691846L; 536aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank ps = new PolicySet(flags); 537aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy = LegacyPolicySet.flagsToPolicy(flags); 538aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertTrue(policySetEqualsPolicy(ps, policy)); 539aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank flags = 1689605957029924L; 540aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank ps = new PolicySet(flags); 541aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank policy = LegacyPolicySet.flagsToPolicy(flags); 542aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank assertTrue(policySetEqualsPolicy(ps, policy)); 543aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 544aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank 545aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank /** 546aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank * The old PolicySet class fields and constructor; we use this to test conversion to the 547aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank * new Policy table scheme 548aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank */ 549aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private static class PolicySet { 550aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final int mMinPasswordLength; 551aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final int mPasswordMode; 552aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final int mMaxPasswordFails; 553aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final int mMaxScreenLockTime; 554aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final boolean mRequireRemoteWipe; 555aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final int mPasswordExpirationDays; 556aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final int mPasswordHistory; 557aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final int mPasswordComplexChars; 558aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final boolean mRequireEncryption; 559aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private final boolean mRequireEncryptionExternal; 560aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank 561aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank /** 562aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank * Create from values encoded in an account flags int 563aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank */ 564aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank private PolicySet(long flags) { 565aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mMinPasswordLength = (int) ((flags & LegacyPolicySet.PASSWORD_LENGTH_MASK) 566aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank >> LegacyPolicySet.PASSWORD_LENGTH_SHIFT); 567aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mPasswordMode = 568aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank (int) (flags & LegacyPolicySet.PASSWORD_MODE_MASK); 569aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mMaxPasswordFails = (int) ((flags & LegacyPolicySet.PASSWORD_MAX_FAILS_MASK) 570aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank >> LegacyPolicySet.PASSWORD_MAX_FAILS_SHIFT); 571aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mMaxScreenLockTime = (int) ((flags & LegacyPolicySet.SCREEN_LOCK_TIME_MASK) 572aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank >> LegacyPolicySet.SCREEN_LOCK_TIME_SHIFT); 573aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mRequireRemoteWipe = 0 != (flags & LegacyPolicySet.REQUIRE_REMOTE_WIPE); 574aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mPasswordExpirationDays = (int) ((flags & LegacyPolicySet.PASSWORD_EXPIRATION_MASK) 575aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank >> LegacyPolicySet.PASSWORD_EXPIRATION_SHIFT); 576aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mPasswordHistory = (int) ((flags & LegacyPolicySet.PASSWORD_HISTORY_MASK) 577aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank >> LegacyPolicySet.PASSWORD_HISTORY_SHIFT); 578aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mPasswordComplexChars = (int) ((flags & LegacyPolicySet.PASSWORD_COMPLEX_CHARS_MASK) 579aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank >> LegacyPolicySet.PASSWORD_COMPLEX_CHARS_SHIFT); 580aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mRequireEncryption = 0 != (flags & LegacyPolicySet.REQUIRE_ENCRYPTION); 581aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank mRequireEncryptionExternal = 0 != (flags & LegacyPolicySet.REQUIRE_ENCRYPTION_EXTERNAL); 582aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 583aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank } 584345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler} 585