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