• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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