1/* 2 * Copyright (C) 2017 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 17package com.android.server; 18 19import static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertTrue; 21import static org.junit.Assert.fail; 22 23import android.app.AppOpsManager; 24import android.content.Context; 25import android.content.res.AssetManager; 26import android.os.Handler; 27import android.os.HandlerThread; 28import android.support.test.InstrumentationRegistry; 29import android.support.test.filters.SmallTest; 30import android.support.test.runner.AndroidJUnit4; 31import android.util.Log; 32import android.util.SparseArray; 33import android.util.Xml; 34 35import org.junit.Before; 36import org.junit.Test; 37import org.junit.runner.RunWith; 38import org.xmlpull.v1.XmlPullParser; 39 40import java.io.File; 41import java.io.FileInputStream; 42import java.io.FileOutputStream; 43import java.io.IOException; 44import java.io.InputStream; 45import java.nio.charset.StandardCharsets; 46 47/** 48 * Tests app ops version upgrades 49 */ 50@SmallTest 51@RunWith(AndroidJUnit4.class) 52public class AppOpsUpgradeTest { 53 private static final String TAG = AppOpsUpgradeTest.class.getSimpleName(); 54 private static final String APP_OPS_UNVERSIONED_ASSET_PATH = 55 "AppOpsUpgradeTest/appops-unversioned.xml"; 56 private static final String APP_OPS_FILENAME = "appops-test.xml"; 57 private static final int NON_DEFAULT_OPS_IN_FILE = 4; 58 private static final int CURRENT_VERSION = 1; 59 60 private File mAppOpsFile; 61 private Context mContext; 62 private Handler mHandler; 63 64 private void extractAppOpsFile() { 65 mAppOpsFile.getParentFile().mkdirs(); 66 if (mAppOpsFile.exists()) { 67 mAppOpsFile.delete(); 68 } 69 try (FileOutputStream out = new FileOutputStream(mAppOpsFile); 70 InputStream in = mContext.getAssets().open(APP_OPS_UNVERSIONED_ASSET_PATH, 71 AssetManager.ACCESS_BUFFER)) { 72 byte[] buffer = new byte[4096]; 73 int bytesRead; 74 while ((bytesRead = in.read(buffer)) >= 0) { 75 out.write(buffer, 0, bytesRead); 76 } 77 out.flush(); 78 Log.d(TAG, "Successfully copied xml to " + mAppOpsFile.getAbsolutePath()); 79 } catch (IOException exc) { 80 Log.e(TAG, "Exception while copying appops xml", exc); 81 fail(); 82 } 83 } 84 85 private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates, int op1, int op2) { 86 int numberOfNonDefaultOps = 0; 87 final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1); 88 final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2); 89 for(int i = 0; i < uidStates.size(); i++) { 90 final AppOpsService.UidState uidState = uidStates.valueAt(i); 91 if (uidState.opModes != null) { 92 final int uidMode1 = uidState.opModes.get(op1, defaultModeOp1); 93 final int uidMode2 = uidState.opModes.get(op2, defaultModeOp2); 94 assertEquals(uidMode1, uidMode2); 95 if (uidMode1 != defaultModeOp1) { 96 numberOfNonDefaultOps++; 97 } 98 } 99 if (uidState.pkgOps == null) { 100 continue; 101 } 102 for (int j = 0; j < uidState.pkgOps.size(); j++) { 103 final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j); 104 if (ops == null) { 105 continue; 106 } 107 final AppOpsService.Op _op1 = ops.get(op1); 108 final AppOpsService.Op _op2 = ops.get(op2); 109 final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.mode; 110 final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.mode; 111 assertEquals(mode1, mode2); 112 if (mode1 != defaultModeOp1) { 113 numberOfNonDefaultOps++; 114 } 115 } 116 } 117 assertEquals(numberOfNonDefaultOps, NON_DEFAULT_OPS_IN_FILE); 118 } 119 120 @Before 121 public void setUp() { 122 mContext = InstrumentationRegistry.getTargetContext(); 123 mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME); 124 extractAppOpsFile(); 125 HandlerThread handlerThread = new HandlerThread(TAG); 126 handlerThread.start(); 127 mHandler = new Handler(handlerThread.getLooper()); 128 } 129 130 @Test 131 public void testUpgradeFromNoVersion() throws Exception { 132 AppOpsDataParser parser = new AppOpsDataParser(mAppOpsFile); 133 assertTrue(parser.parse()); 134 assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion); 135 AppOpsService testService = new AppOpsService(mAppOpsFile, mHandler); // trigger upgrade 136 assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND, 137 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); 138 testService.mContext = mContext; 139 mHandler.removeCallbacks(testService.mWriteRunner); 140 testService.writeState(); 141 assertTrue(parser.parse()); 142 assertEquals(CURRENT_VERSION, parser.mVersion); 143 } 144 145 /** 146 * Class to parse data from the appops xml. Currently only parses and holds the version number. 147 * Other fields may be added as and when required for testing. 148 */ 149 private static final class AppOpsDataParser { 150 static final int NO_VERSION = -1; 151 int mVersion; 152 private File mFile; 153 154 AppOpsDataParser(File file) { 155 mFile = file; 156 mVersion = NO_VERSION; 157 } 158 159 boolean parse() { 160 try (FileInputStream stream = new FileInputStream(mFile)) { 161 XmlPullParser parser = Xml.newPullParser(); 162 parser.setInput(stream, StandardCharsets.UTF_8.name()); 163 int type; 164 while ((type = parser.next()) != XmlPullParser.START_TAG 165 && type != XmlPullParser.END_DOCUMENT) { 166 ; 167 } 168 if (type != XmlPullParser.START_TAG) { 169 throw new IllegalStateException("no start tag found"); 170 } 171 final String versionString = parser.getAttributeValue(null, "v"); 172 if (versionString != null) { 173 mVersion = Integer.parseInt(versionString); 174 } 175 } catch (Exception e) { 176 Log.e(TAG, "Failed while parsing test appops xml", e); 177 return false; 178 } 179 return true; 180 } 181 } 182} 183