1 /*
2  * Copyright (C) 2020 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 android.location.cts.common;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.net.ConnectivityManager;
22 import android.net.NetworkInfo;
23 import android.util.Log;
24 
25 import com.android.compatibility.common.util.SystemUtil;
26 
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.CountDownLatch;
29 import java.util.concurrent.TimeUnit;
30 
31 public class TestUtils {
32     private static final String TAG = "LocationTestUtils";
33 
34     private static final long STANDARD_WAIT_TIME_MS = 50;
35     private static final long STANDARD_SLEEP_TIME_MS = 50;
36 
37     private static final int DATA_CONNECTION_CHECK_INTERVAL_MS = 500;
38     private static final int DATA_CONNECTION_CHECK_COUNT = 10; // 500 * 10 - Roughly 5 secs wait
39 
waitFor(CountDownLatch latch, int timeInSec)40     public static boolean waitFor(CountDownLatch latch, int timeInSec) throws InterruptedException {
41         // Since late 2014, if the main thread has been occupied for long enough, Android will
42         // increase its priority. Such new behavior can causes starvation to the background thread -
43         // even if the main thread has called await() to yield its execution, the background thread
44         // still can't get scheduled.
45         //
46         // Here we're trying to wait on the main thread for a PendingIntent from a background
47         // thread. Because of the starvation problem, the background thread may take up to 5 minutes
48         // to deliver the PendingIntent if we simply call await() on the main thread. In order to
49         // give the background thread a chance to run, we call Thread.sleep() in a loop. Such dirty
50         // hack isn't ideal, but at least it can work.
51         //
52         // See also: b/17423027
53         long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) /
54                 (STANDARD_WAIT_TIME_MS + STANDARD_SLEEP_TIME_MS);
55         for (int i = 0; i < waitTimeRounds; ++i) {
56             Thread.sleep(STANDARD_SLEEP_TIME_MS);
57             if (latch.await(STANDARD_WAIT_TIME_MS, TimeUnit.MILLISECONDS)) {
58                 return true;
59             }
60         }
61         return false;
62     }
63 
waitForWithCondition(int timeInSec, Callable<Boolean> callback)64     public static boolean waitForWithCondition(int timeInSec, Callable<Boolean> callback)
65         throws Exception {
66         long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) / STANDARD_SLEEP_TIME_MS;
67         for (int i = 0; i < waitTimeRounds; ++i) {
68             Thread.sleep(STANDARD_SLEEP_TIME_MS);
69             if(callback.call()) return true;
70         }
71         return false;
72     }
73 
deviceHasGpsFeature(Context context)74     public static boolean deviceHasGpsFeature(Context context) {
75         // If device does not have a GPS, skip the test.
76         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)) {
77             return true;
78         }
79         Log.w(TAG, "GPS feature not present on device, skipping GPS test.");
80         return false;
81     }
82 
83     /**
84      * Returns whether the device is currently connected to a wifi or cellular.
85      *
86      * @param context {@link Context} object
87      * @return {@code true} if connected to Wifi or Cellular; {@code false} otherwise
88      */
isConnectedToWifiOrCellular(Context context)89     public static boolean isConnectedToWifiOrCellular(Context context) {
90         NetworkInfo info = getActiveNetworkInfo(context);
91         return info != null
92                 && info.isConnected()
93                 && (info.getType() == ConnectivityManager.TYPE_WIFI
94                 || info.getType() == ConnectivityManager.TYPE_MOBILE);
95     }
96 
97     /**
98      * Gets the active network info.
99      *
100      * @param context {@link Context} object
101      * @return {@link NetworkInfo}
102      */
getActiveNetworkInfo(Context context)103     private static NetworkInfo getActiveNetworkInfo(Context context) {
104         ConnectivityManager cm = getConnectivityManager(context);
105         if (cm != null) {
106             return cm.getActiveNetworkInfo();
107         }
108         return null;
109     }
110 
111     /**
112      * Gets the connectivity manager.
113      *
114      * @param context {@link Context} object
115      * @return {@link ConnectivityManager}
116      */
getConnectivityManager(Context context)117     public static ConnectivityManager getConnectivityManager(Context context) {
118         return (ConnectivityManager) context.getApplicationContext()
119                 .getSystemService(Context.CONNECTIVITY_SERVICE);
120     }
121 
122     /**
123      * Returns {@code true} if the setting {@code airplane_mode_on} is set to 1.
124      */
isAirplaneModeOn()125     public static boolean isAirplaneModeOn() {
126         return SystemUtil.runShellCommand("settings get global airplane_mode_on")
127                 .trim().equals("1");
128     }
129 
130     /**
131      * Changes the setting {@code airplane_mode_on} to 1 if {@code enableAirplaneMode}
132      * is {@code true}. Otherwise, it is set to 0.
133      *
134      * <p>Waits for a certain time duration for network connections to turn on/off based on
135      * {@code enableAirplaneMode}.
136      */
setAirplaneModeOn(Context context, boolean enableAirplaneMode)137     public static void setAirplaneModeOn(Context context,
138             boolean enableAirplaneMode) throws InterruptedException {
139         Log.i(TAG, "Setting airplane_mode_on to " + enableAirplaneMode);
140         SystemUtil.runShellCommand("cmd connectivity airplane-mode "
141                 + (enableAirplaneMode ? "enable" : "disable"));
142 
143         // Wait for a few seconds until the airplane mode changes take effect. The airplane mode on
144         // state and the WiFi/cell connected state are opposite. So, we wait while they are the
145         // same or until the specified time interval expires.
146         //
147         // Note that in unusual cases where the WiFi/cell are not in a connected state before
148         // turning on airplane mode, then turning off airplane mode won't restore either of
149         // these connections, and then the wait time below will be wasteful.
150         int dataConnectionCheckCount = DATA_CONNECTION_CHECK_COUNT;
151         while (enableAirplaneMode == isConnectedToWifiOrCellular(context)) {
152             if (--dataConnectionCheckCount <= 0) {
153                 Log.w(TAG, "Airplane mode " + (enableAirplaneMode ? "on" : "off")
154                         + " setting did not take effect on WiFi/cell connected state.");
155                 return;
156             }
157             Thread.sleep(DATA_CONNECTION_CHECK_INTERVAL_MS);
158         }
159     }
160 }
161