1 /* 2 * Copyright (C) 2010 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.cts.verifier; 18 19 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; 20 21 import android.Manifest; 22 import android.app.AlertDialog; 23 import android.app.ListActivity; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.SharedPreferences; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.provider.Settings; 33 import android.util.Log; 34 import android.view.Menu; 35 import android.view.MenuInflater; 36 import android.view.MenuItem; 37 import android.view.View; 38 import android.view.Window; 39 import android.widget.CompoundButton; 40 import android.widget.Switch; 41 import android.widget.Toast; 42 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.Objects; 46 47 /** Top-level {@link ListActivity} for launching tests and managing results. */ 48 public class TestListActivity extends AbstractTestListActivity implements View.OnClickListener { 49 private static final int CTS_VERIFIER_PERMISSION_REQUEST = 1; 50 private static final int CTS_VERIFIER_BACKGROUND_LOCATION_PERMISSION_REQUEST = 2; 51 52 private static final String TAG = TestListActivity.class.getSimpleName(); 53 // Records the current display mode. 54 // Default is unfolded mode, and it will be changed when clicking the switch button. 55 public static volatile String sCurrentDisplayMode = DisplayMode.UNFOLDED.toString(); 56 // Flag of launch app to fetch the unfolded/folded tests in main view from AndroidManifest.xml. 57 protected static boolean sInitialLaunch; 58 59 private String[] mRequestedPermissions; 60 61 // Enumerates the display modes, including unfolded and folded. 62 protected enum DisplayMode { 63 UNFOLDED, 64 FOLDED; 65 66 @Override toString()67 public String toString() { 68 return name().toLowerCase(); 69 } 70 71 /** 72 * Coverts the mode as suffix with brackets for test name. 73 * 74 * @return A string containing mode with brackets for folded mode; empty string for unfolded 75 * mode. 76 */ asSuffix()77 public String asSuffix() { 78 if (name().equals(FOLDED.name())) { 79 return String.format("[%s]", toString()); 80 } 81 return ""; 82 } 83 } 84 85 @Override onClick(View v)86 public void onClick(View v) { 87 handleMenuItemSelected(v.getId()); 88 } 89 90 @Override onCreate(Bundle savedInstanceState)91 protected void onCreate(Bundle savedInstanceState) { 92 super.onCreate(savedInstanceState); 93 94 try { 95 PackageManager pm = getPackageManager(); 96 PackageInfo packageInfo = 97 pm.getPackageInfo( 98 getApplicationInfo().packageName, PackageManager.GET_PERMISSIONS); 99 mRequestedPermissions = packageInfo.requestedPermissions; 100 101 if (mRequestedPermissions != null) { 102 String[] permissionsToRequest = 103 removeString( 104 mRequestedPermissions, 105 Manifest.permission.ACCESS_BACKGROUND_LOCATION); 106 permissionsToRequest = 107 Arrays.stream(permissionsToRequest) 108 .filter( 109 s -> { 110 try { 111 return (pm.getPermissionInfo(s, 0).getProtection() 112 & PROTECTION_DANGEROUS) 113 != 0; 114 } catch (NameNotFoundException e) { 115 return false; 116 } 117 }) 118 .toArray(String[]::new); 119 requestPermissions(permissionsToRequest, CTS_VERIFIER_PERMISSION_REQUEST); 120 } 121 createContinue(); 122 } catch (NameNotFoundException e) { 123 Log.e(TAG, "Unable to load package's permissions", e); 124 Toast.makeText(this, R.string.runtime_permissions_error, Toast.LENGTH_SHORT).show(); 125 } 126 } 127 createContinue()128 private void createContinue() { 129 if (!isTaskRoot()) { 130 finish(); 131 } 132 sInitialLaunch = true; 133 134 // Restores the last display mode when launching the app after killing the process. 135 if (getCurrentDisplayMode().equals(DisplayMode.FOLDED.toString())) { 136 sCurrentDisplayMode = DisplayMode.FOLDED.toString(); 137 } 138 139 setTitle(getString(R.string.title_version, Version.getVersionName(this))); 140 141 if (!getWindow().hasFeature(Window.FEATURE_ACTION_BAR)) { 142 View footer = getLayoutInflater().inflate(R.layout.test_list_footer, null); 143 144 footer.findViewById(R.id.clear).setOnClickListener(this); 145 footer.findViewById(R.id.export).setOnClickListener(this); 146 147 getListView().addFooterView(footer); 148 } 149 150 setTestListAdapter(new ManifestTestListAdapter(this, null)); 151 } 152 153 @Override onRequestPermissionsResult( int requestCode, String permissions[], int[] grantResults)154 public void onRequestPermissionsResult( 155 int requestCode, String permissions[], int[] grantResults) { 156 if (requestCode == CTS_VERIFIER_PERMISSION_REQUEST) { 157 if (arrayContains(grantResults, PackageManager.PERMISSION_DENIED)) { 158 Log.v(TAG, "Didn't grant all permissions."); 159 // If we're sending them to settings we don't need to request background location 160 // since they can just grant in settings. 161 sendUserToSettings(); 162 } else if (new ArrayList<>(Arrays.asList(mRequestedPermissions)) 163 .contains(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { 164 requestPermissions( 165 new String[] {Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 166 CTS_VERIFIER_BACKGROUND_LOCATION_PERMISSION_REQUEST); 167 } 168 return; 169 } 170 if (requestCode == CTS_VERIFIER_BACKGROUND_LOCATION_PERMISSION_REQUEST) { 171 if (grantResults[0] == PackageManager.PERMISSION_DENIED) { 172 Log.v(TAG, "Didn't grant background permission."); 173 sendUserToSettings(); 174 } 175 return; 176 } 177 } 178 sendUserToSettings()179 private AlertDialog sendUserToSettings() { 180 return new AlertDialog.Builder(this) 181 .setTitle("Please grant all permissions") 182 .setPositiveButton( 183 "Ok", 184 (dialog, which) -> { 185 if (which == AlertDialog.BUTTON_POSITIVE) { 186 startActivity( 187 new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 188 .setData( 189 Uri.fromParts( 190 "package", 191 getPackageName(), 192 null))); 193 } 194 }) 195 .show(); 196 } 197 198 @Override 199 public boolean onCreateOptionsMenu(Menu menu) { 200 MenuInflater inflater = getMenuInflater(); 201 inflater.inflate(R.menu.test_list_menu, menu); 202 203 // Switch button for unfolded and folded tests. 204 MenuItem item = (MenuItem) menu.findItem(R.id.switch_item); 205 item.setActionView(R.layout.display_mode_switch); 206 Switch displayModeSwitch = item.getActionView().findViewById(R.id.switch_button); 207 208 // Get the current display mode to show switch status. 209 boolean isFoldedMode = sCurrentDisplayMode.equals(DisplayMode.FOLDED.toString()); 210 displayModeSwitch.setChecked(isFoldedMode); 211 212 displayModeSwitch.setOnCheckedChangeListener( 213 new CompoundButton.OnCheckedChangeListener() { 214 @Override 215 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 216 if (isChecked) { 217 sCurrentDisplayMode = DisplayMode.FOLDED.toString(); 218 } else { 219 sCurrentDisplayMode = DisplayMode.UNFOLDED.toString(); 220 } 221 handleSwitchItemSelected(); 222 } 223 }); 224 return true; 225 } 226 227 @Override 228 public boolean onOptionsItemSelected(MenuItem item) { 229 return handleMenuItemSelected(item.getItemId()) ? true : super.onOptionsItemSelected(item); 230 } 231 232 private void handleClearItemSelected() { 233 new AlertDialog.Builder(this) 234 .setMessage(R.string.test_results_clear_title) 235 .setPositiveButton( 236 R.string.test_results_clear_yes, 237 new DialogInterface.OnClickListener() { 238 public void onClick(DialogInterface dialog, int id) { 239 mAdapter.clearTestResults(); 240 Toast.makeText( 241 TestListActivity.this, 242 R.string.test_results_cleared, 243 Toast.LENGTH_SHORT) 244 .show(); 245 } 246 }) 247 .setNegativeButton(R.string.test_results_clear_cancel, null) 248 .show(); 249 } 250 251 private void handleExportItemSelected() { 252 new ReportExporter(this, mAdapter).execute(); 253 } 254 255 // Sets up the flags after switching display mode and reloads tests on UI. 256 private void handleSwitchItemSelected() { 257 setCurrentDisplayMode(sCurrentDisplayMode); 258 mAdapter.loadTestResults(); 259 } 260 261 private boolean handleMenuItemSelected(int id) { 262 if (id == R.id.clear) { 263 handleClearItemSelected(); 264 } else if (id == R.id.export) { 265 handleExportItemSelected(); 266 } else { 267 return false; 268 } 269 270 return true; 271 } 272 273 /** 274 * Sets current display mode to sharedpreferences. 275 * 276 * @param mode A string of current display mode. 277 */ 278 private void setCurrentDisplayMode(String mode) { 279 SharedPreferences pref = getSharedPreferences(DisplayMode.class.getName(), MODE_PRIVATE); 280 pref.edit().putString(DisplayMode.class.getName(), mode).commit(); 281 } 282 283 /** 284 * Gets current display mode from sharedpreferences. 285 * 286 * @return A string of current display mode. 287 */ 288 private String getCurrentDisplayMode() { 289 String mode = 290 getSharedPreferences(DisplayMode.class.getName(), MODE_PRIVATE) 291 .getString(DisplayMode.class.getName(), ""); 292 return mode; 293 } 294 295 private static boolean arrayContains(int[] array, int value) { 296 if (array == null) return false; 297 for (int element : array) { 298 if (element == value) { 299 return true; 300 } 301 } 302 return false; 303 } 304 305 private static String[] removeString(String[] cur, String val) { 306 if (cur == null) { 307 return null; 308 } 309 final int n = cur.length; 310 for (int i = 0; i < n; i++) { 311 if (Objects.equals(cur[i], val)) { 312 String[] ret = new String[n - 1]; 313 if (i > 0) { 314 System.arraycopy(cur, 0, ret, 0, i); 315 } 316 if (i < (n - 1)) { 317 System.arraycopy(cur, i + 1, ret, i, n - i - 1); 318 } 319 return ret; 320 } 321 } 322 return cur; 323 } 324 } 325