xref: /aosp_15_r20/cts/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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.SearchView;
41 import android.widget.Switch;
42 import android.widget.Toast;
43 
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Objects;
47 
48 /** Top-level {@link ListActivity} for launching tests and managing results. */
49 public class TestListActivity extends AbstractTestListActivity implements View.OnClickListener {
50     private static final int CTS_VERIFIER_PERMISSION_REQUEST = 1;
51     private static final int CTS_VERIFIER_BACKGROUND_LOCATION_PERMISSION_REQUEST = 2;
52 
53     private static final String TAG = TestListActivity.class.getSimpleName();
54     // Records the current display mode.
55     // Default is unfolded mode, and it will be changed when clicking the switch button.
56     public static volatile String sCurrentDisplayMode = DisplayMode.UNFOLDED.toString();
57 
58     // Whether the verifier-system plan is enabled.
59     private static boolean sIsSystemEnabled = false;
60     // Whether the system switch has been toggled.
61     private static boolean sHasSystemToggled = false;
62 
63     private String[] mRequestedPermissions;
64 
65     // Enumerates the display modes, including unfolded and folded.
66     protected enum DisplayMode {
67         UNFOLDED,
68         FOLDED;
69 
70         @Override
toString()71         public String toString() {
72             return name().toLowerCase();
73         }
74 
75         /**
76          * Converts the mode as suffix with brackets for test name.
77          *
78          * @return a string containing mode with brackets for folded mode; empty string for unfolded
79          *     mode
80          */
asSuffix()81         public String asSuffix() {
82             if (name().equals(FOLDED.name())) {
83                 return String.format("[%s]", toString());
84             }
85             return "";
86         }
87     }
88 
89     @Override
onClick(View v)90     public void onClick(View v) {
91         handleMenuItemSelected(v.getId());
92     }
93 
94     @Override
onCreate(Bundle savedInstanceState)95     protected void onCreate(Bundle savedInstanceState) {
96         super.onCreate(savedInstanceState);
97 
98         try {
99             PackageManager pm = getPackageManager();
100             PackageInfo packageInfo =
101                     pm.getPackageInfo(
102                             getApplicationInfo().packageName, PackageManager.GET_PERMISSIONS);
103             mRequestedPermissions = packageInfo.requestedPermissions;
104 
105             if (mRequestedPermissions != null) {
106                 String[] permissionsToRequest =
107                         removeString(
108                                 mRequestedPermissions,
109                                 Manifest.permission.ACCESS_BACKGROUND_LOCATION);
110                 permissionsToRequest =
111                         Arrays.stream(permissionsToRequest)
112                                 .filter(
113                                         s -> {
114                                             try {
115                                                 return (pm.getPermissionInfo(s, 0).getProtection()
116                                                                 & PROTECTION_DANGEROUS)
117                                                         != 0;
118                                             } catch (NameNotFoundException e) {
119                                                 return false;
120                                             }
121                                         })
122                                 .toArray(String[]::new);
123                 requestPermissions(permissionsToRequest, CTS_VERIFIER_PERMISSION_REQUEST);
124             }
125             createContinue();
126         } catch (NameNotFoundException e) {
127             Log.e(TAG, "Unable to load package's permissions", e);
128             Toast.makeText(this, R.string.runtime_permissions_error, Toast.LENGTH_SHORT).show();
129         }
130     }
131 
132     @Override
onRequestPermissionsResult( int requestCode, String permissions[], int[] grantResults)133     public void onRequestPermissionsResult(
134             int requestCode, String permissions[], int[] grantResults) {
135         if (requestCode == CTS_VERIFIER_PERMISSION_REQUEST) {
136             if (arrayContains(grantResults, PackageManager.PERMISSION_DENIED)) {
137                 Log.v(TAG, "Didn't grant all permissions.");
138                 // If we're sending them to settings we don't need to request background location
139                 // since they can just grant in settings.
140                 sendUserToSettings();
141             } else if (new ArrayList<>(Arrays.asList(mRequestedPermissions))
142                     .contains(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
143                 requestPermissions(
144                         new String[] {Manifest.permission.ACCESS_BACKGROUND_LOCATION},
145                         CTS_VERIFIER_BACKGROUND_LOCATION_PERMISSION_REQUEST);
146             }
147             return;
148         }
149         if (grantResults.length == 0) {
150             return;
151         }
152         if (requestCode == CTS_VERIFIER_BACKGROUND_LOCATION_PERMISSION_REQUEST) {
153             if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
154                 Log.v(TAG, "Didn't grant background permission.");
155                 sendUserToSettings();
156             }
157             return;
158         }
159     }
160 
161     @Override
onCreateOptionsMenu(Menu menu)162     public boolean onCreateOptionsMenu(Menu menu) {
163         MenuInflater inflater = getMenuInflater();
164         inflater.inflate(R.menu.test_list_menu, menu);
165 
166         // Switch button for unfolded and folded tests.
167         MenuItem item = (MenuItem) menu.findItem(R.id.switch_item);
168         item.setActionView(R.layout.display_mode_switch);
169         Switch displayModeSwitch = item.getActionView().findViewById(R.id.switch_button);
170 
171         // Get the current display mode to show switch status.
172         boolean isFoldedMode = sCurrentDisplayMode.equals(DisplayMode.FOLDED.toString());
173         displayModeSwitch.setChecked(isFoldedMode);
174 
175         displayModeSwitch.setOnCheckedChangeListener(
176                 new CompoundButton.OnCheckedChangeListener() {
177                     @Override
178                     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
179                         if (isChecked) {
180                             sCurrentDisplayMode = DisplayMode.FOLDED.toString();
181                         } else {
182                             sCurrentDisplayMode = DisplayMode.UNFOLDED.toString();
183                         }
184                         handleSwitchItemSelected();
185                     }
186                 });
187 
188         // Switch button for verifier-system plan.
189         item = (MenuItem) menu.findItem(R.id.system_switch_item);
190         if (item != null) {
191             item.setActionView(R.layout.system_switch);
192             final Switch systemSwitch = item.getActionView().findViewById(
193                     R.id.system_switch_button);
194 
195             systemSwitch.setChecked(sIsSystemEnabled);
196             systemSwitch.setOnCheckedChangeListener(
197                     new CompoundButton.OnCheckedChangeListener() {
198 
199                         @Override
200                         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
201                             if (!sHasSystemToggled && isChecked) {
202                                 sHasSystemToggled = true;
203                                 notifyUserSystemPlan(systemSwitch);
204                             } else {
205                                 updateIsSystemEnabled(isChecked);
206                             }
207                         }
208                     });
209         }
210 
211         SearchView searchView = (SearchView) menu.findItem(R.id.search_test).getActionView();
212         searchView.setOnQueryTextListener(
213                 new SearchView.OnQueryTextListener() {
214 
215                     public boolean onQueryTextSubmit(String query) {
216                         Log.i(TAG, "Got submitted query: " + query);
217                         handleQueryUpdated(query);
218                         return true;
219                     }
220 
221                     public boolean onQueryTextChange(String newText) {
222                         if (newText == null || newText.isEmpty()) {
223                             Log.i(TAG, "Clear filter");
224                             handleQueryUpdated(newText);
225                             return true;
226                         } else {
227                             return false;
228                         }
229                     }
230                 });
231 
232         return true;
233     }
234 
235     @Override
onOptionsItemSelected(MenuItem item)236     public boolean onOptionsItemSelected(MenuItem item) {
237         return handleMenuItemSelected(item.getItemId()) ? true : super.onOptionsItemSelected(item);
238     }
239 
240     /** Gets the verifier-system plan enabled status. */
getIsSystemEnabled()241     static boolean getIsSystemEnabled() {
242         return sIsSystemEnabled;
243     }
244 
245     /** Checks if a list of int array contains a given int value. */
arrayContains(int[] array, int value)246     private static boolean arrayContains(int[] array, int value) {
247         if (array == null) {
248             return false;
249         }
250         for (int element : array) {
251             if (element == value) {
252                 return true;
253             }
254         }
255         return false;
256     }
257 
258     /** Removes the first occurrence of a string from a given string array. */
removeString(String[] cur, String val)259     private static String[] removeString(String[] cur, String val) {
260         if (cur == null) {
261             return null;
262         }
263         final int n = cur.length;
264         for (int i = 0; i < n; i++) {
265             if (Objects.equals(cur[i], val)) {
266                 String[] ret = new String[n - 1];
267                 if (i > 0) {
268                     System.arraycopy(cur, 0, ret, 0, i);
269                 }
270                 if (i < (n - 1)) {
271                     System.arraycopy(cur, i + 1, ret, i, n - i - 1);
272                 }
273                 return ret;
274             }
275         }
276         return cur;
277     }
278 
createContinue()279     private void createContinue() {
280         if (!isTaskRoot()) {
281             finish();
282         }
283 
284         // Restores the last display mode when launching the app after killing the process.
285         if (getCurrentDisplayMode().equals(DisplayMode.FOLDED.toString())) {
286             sCurrentDisplayMode = DisplayMode.FOLDED.toString();
287         }
288 
289         setTitle(getString(R.string.title_version, Version.getVersionName(this)));
290 
291         if (!getWindow().hasFeature(Window.FEATURE_ACTION_BAR)) {
292             View footer = getLayoutInflater().inflate(R.layout.test_list_footer, null);
293 
294             footer.findViewById(R.id.clear).setOnClickListener(this);
295             footer.findViewById(R.id.export).setOnClickListener(this);
296 
297             getListView().addFooterView(footer);
298         }
299 
300         setTestListAdapter(
301                 new ManifestTestListAdapter(/* context= */ this, /* testParent= */ null));
302     }
303 
sendUserToSettings()304     private AlertDialog sendUserToSettings() {
305         return new AlertDialog.Builder(this)
306                 .setTitle("Please grant all permissions")
307                 .setPositiveButton(
308                         "Ok",
309                         (dialog, which) -> {
310                             if (which == AlertDialog.BUTTON_POSITIVE) {
311                                 startActivity(
312                                         new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
313                                                 .setData(
314                                                         Uri.fromParts(
315                                                                 "package",
316                                                                 getPackageName(),
317                                                                 null)));
318                             }
319                         })
320                 .show();
321     }
322 
323     /** Notifies the user about the verifier-system plan. */
324     private AlertDialog notifyUserSystemPlan(Switch systemSwitch) {
325         return new AlertDialog.Builder(this)
326                 .setTitle("Verifier System Plan")
327                 .setMessage(
328                         "This is a feature to execute verifier tests for the system layer"
329                             + " partitions. Click \"Yes\" to proceed or \"No\" to run all of the"
330                             + " verifier tests.")
331                 .setPositiveButton(
332                         "Yes",
333                         new DialogInterface.OnClickListener() {
334 
335                             public void onClick(DialogInterface dialog, int whichButton) {
336                                 updateIsSystemEnabled(true);
337                             }
338                         })
339                 .setNegativeButton(
340                         "No",
341                         new DialogInterface.OnClickListener() {
342 
343                             public void onClick(DialogInterface dialog, int whichButton) {
344                                 systemSwitch.setChecked(false);
345                             }
346                         })
347                 .show();
348     }
349 
350     /** Updates the verifier-system plan enabled status and refreshes the test list. */
351     private void updateIsSystemEnabled(boolean isChecked) {
352         Log.i(TAG, "verifier-system plan enabled: " + isChecked);
353         sIsSystemEnabled = isChecked;
354         handleSwitchItemSelected();
355     }
356 
357     /** Sets up the flags after switching display mode and reloads tests on UI. */
358     private void handleSwitchItemSelected() {
359         setCurrentDisplayMode(sCurrentDisplayMode);
360         mAdapter.loadTestResults();
361     }
362 
363     private void handleClearItemSelected() {
364         new AlertDialog.Builder(this)
365                 .setMessage(R.string.test_results_clear_title)
366                 .setPositiveButton(
367                         R.string.test_results_clear_yes,
368                         new DialogInterface.OnClickListener() {
369                             public void onClick(DialogInterface dialog, int id) {
370                                 mAdapter.clearTestResults();
371                                 Toast.makeText(
372                                                 TestListActivity.this,
373                                                 R.string.test_results_cleared,
374                                                 Toast.LENGTH_SHORT)
375                                         .show();
376                             }
377                         })
378                 .setNegativeButton(R.string.test_results_clear_cancel, null)
379                 .show();
380     }
381 
382     private void handleExportItemSelected() {
383         new ReportExporter(this, mAdapter).execute();
384     }
385 
386     private boolean handleMenuItemSelected(int id) {
387         if (id == R.id.clear) {
388             handleClearItemSelected();
389         } else if (id == R.id.export) {
390             handleExportItemSelected();
391         } else {
392             return false;
393         }
394 
395         return true;
396     }
397 
398     /** Triggered when a new query is input. */
399     private void handleQueryUpdated(String query) {
400         if (query != null && !query.isEmpty()) {
401             mAdapter.setTestFilter(query);
402         } else {
403             // Reset the filter as null to show all tests.
404             mAdapter.setTestFilter(/* testFilter= */ null);
405         }
406         mAdapter.loadTestResults();
407     }
408 
409     /**
410      * Sets current display mode to sharedpreferences.
411      *
412      * @param mode a string of current display mode
413      */
414     private void setCurrentDisplayMode(String mode) {
415         SharedPreferences pref = getSharedPreferences(DisplayMode.class.getName(), MODE_PRIVATE);
416         pref.edit().putString(DisplayMode.class.getName(), mode).commit();
417     }
418 
419     /**
420      * Gets current display mode from sharedpreferences.
421      *
422      * @return a string of current display mode
423      */
424     private String getCurrentDisplayMode() {
425         String mode =
426                 getSharedPreferences(DisplayMode.class.getName(), MODE_PRIVATE)
427                         .getString(DisplayMode.class.getName(), "");
428         return mode;
429     }
430 }
431