Is it possible to consistently detect if an Activity has hardware acceleration enabled when it is created? I'm worried that users of my library will enable it through the manifest when they shouldn't, by not specifically disabling it for my Activity (as I instruct them to do.)
The only solid information I can find (http://android-developers.blogspot.com/2011/03/android-30-hardware-acceleration.html) says that I can query View.isHardwareAccelerated() and Canvas.isHardwareAccelerated(). However, for my purposes, I would like to ensure it is off when my library's Activity is shown. So far, I can't get anything to report a consistent yes/no when it is on or off. I tried hacking in a dummy view, setting it to my activity and then testing it, but it always returns false. Also, I tried testing Window.getAttributes( ).flags, but they aren't showing it either.
I am testing this because the hardware accelerated draw path for my library doesn't function correctly, and there doesn't seem like there is any way to fix it.
Try FLAG_HARDWARE_ACCELERATED in flags in ActivityInfo for the activity, which you would get from PackageManager via getActivityInfo().
I'm new in Android so I was stuck even with the clues given in the answer above.. went to search around and found this code somewhere in the sea of Google. Hope it helps someone.
/**
* Returns true if the given Activity has hardware acceleration enabled
* in its manifest, or in its foreground window.
*
* TODO(husky): Remove when initialize() is refactored (see TODO there)
* TODO(dtrainor) This is still used by other classes. Make sure to pull some version of this
* out before removing it.
*/
public static boolean hasHardwareAcceleration(Activity activity) {
// Has HW acceleration been enabled manually in the current window?
Window window = activity.getWindow();
if (window != null) {
if ((window.getAttributes().flags
& WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0) {
return true;
}
}
// Has HW acceleration been enabled in the manifest?
try {
ActivityInfo info = activity.getPackageManager().getActivityInfo(
activity.getComponentName(), 0);
if ((info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("Chrome", "getActivityInfo(self) should not fail");
}
return false;
}
Related
I've found out that Android 9 now shows info if accessibility service stopped working.
That was always a pain for developers who try to leverage accessibility API.
Accessibility looks like enabled, but service is stopped. And to get it back to work it is required to turn accessibility off and back on.
I would be glad if Google fixes that completely, but now they just show a hint that it's good to disable-enable it manually.
Not the best stuff, but at least something.
So, I've tried to find out how the system gets to know if the service is crashed. There happened to be a class called AccessibilityUtil and it contains hasServiceCrashed method.
Unfortunately, it checks a hidden field crashed from AccessibilityNodeInfo, which is not available for third-party developers (because of reflection denial) as well as on previous android versions.
So I'm wondering if there is an alternative way to get the info from the system which clarifies that my accessibility service is crashed/stopped working and user's action is required. Starting from Lollipop. Hints appreciated.
I came up with an idea to use a static boolean indicating the status of Accessibility Service and compare it with Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES. I've tested on multiple devices, haven't found any issue with this method.
1.Declare a static boolean in Accessibility Service.
private static boolean bServiceRunning = false;
2.In Accessibility Service, set the boolean value in onServiceConnected and onUnbind
#Override
protected void onServiceConnected() {
super.onServiceConnected();
bServiceRunning = true; //put this at the very beginning to reduce time gap
}
#Override
public boolean onUnbind(Intent intent) {
bServiceRunning = false;
return super.onUnbind(intent);
}
3.Create a static function in Accessibility Service
public static boolean bGetServiceStatus(){
return bServiceRunning;
}
With the boolean flag, I can know if the accessibility service is running in the desired state. When the service is being forced to stop, onUnbind will be called so the boolean value turns into false.
4.We use Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES to get accessibility service switch status
public static boolean bIsAccessibilityServiceEnabled(Context context, Class<?> accessibilityService) {
ComponentName expectedComponentName = new ComponentName(context, accessibilityService);
String strServicesSettingResult = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (strServicesSettingResult == null){
return false;
}
TextUtils.SimpleStringSplitter colonSplitter = new TextUtils.SimpleStringSplitter(':');
colonSplitter.setString(strServicesSettingResult);
while (colonSplitter.hasNext()) {
String strComponentName = colonSplitter.next();
ComponentName enabledService = ComponentName.unflattenFromString(strComponentName);
if (enabledService != null && enabledService.equals(expectedComponentName))
return true;
}
return false;
}
5.And this is what we want, we check with the above two methods the determine the real state of accessibility service.
public static int intIsAccessibilityServiceEnabled_WithCrashCheck(Context context, Class<?> accessibilityService){
//return 0 if Accessibility Service enabled and running
//return -1 if Accessibility Service disabled and not running
//return -2 if Accessibility Service enabled but stopped working or crashed
//first check Accessibility Service boolean
if(bGetServiceStatus()){
//service is running
return 0;
}else{
//service not running, now double check with Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
boolean bResult = bIsAccessibilityServiceEnabled(context, accessibilityService);
if(!bResult){
//Accessibility Service is disabled
return -1;
}else{
//Accessibility Service is enabled, but service is not actually running, Accessibility Service is crashed
return -2;
}
}
}
Using "AccessibilityManager" also works the same, but I prefer a more "lightweight" version with static boolean for better performance.
Note: Using Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES without another doublecheck will cause a bug. The value is not synced. The result doesn't always represent the real service status. The following steps can create such a case :
Start an accessibility service then go to App settings force stop the App.
Now you can see the accessibility service switch is turned off. (Seems enough currently, but the next step will create a problem)
Start the accessibility service again,on Android 9.0+ devices you'll find out the switch is switched on, but the service is actually not running, and now it displays "Not
working. Tap for info."
2022/11/29 Edit: This bug is fixed according to this issue tracker. I can no longer reproduce this bug on my new Android 12 & 13 devices. However, devices with old Android firmware still has this bug. (The patch is also applied to the newest AVD images. To test this bug with AVD, now you must download old revisions of the AVD images.)
Android generally prevents apps from running if they crash repeatedly. This behavior for an accessibility service can obviously affect users who depend on the service, but since these services can effectively control the UI, having one that crashes repeatedly could also make the device unusable.
It hadn't occurred to me that anyone else would be interested in the crashed field in AccessibilityServiceInfo. I populated that field using data only available to the system unfortunately. I compare the list of services that are enabled with the list of those that are bound.
If you're interested if your service is prevented from running, you could probably do something similar by keeping track of when onBind and onUnbind is called and looking at the list of enabled services from AccessibilityManager.
I don't know if this is a solution. But I did find when it doesn't work: if I use "dumpsys accessibility", the services part is empty, looks like:
User state[attributes:{id=0,
currentUser=true,
touchExplorationEnabled=false,
displayMagnificationEnabled=false,
navBarMagnificationEnabled=false,
autoclickEnabled=false}
services:{}]
Maybe you can check if the services is empty.
For my instrumentation tests I am using Robotium. Mostly I am able to test everything but offline cases.
As soon as I disable data (using adb, F8 shortcut in emulator, etc. ...) the test disconnects. It goes on in the device/emulator but no results are reported.
So, I have got an idea to put just the app in offline mode and not the whole device. The problem is I don't know how...
Using iptablesApi I would need to root my device. I have read that Mobiwol app uses some kind of a VPN to restrict apps internet access without the need of rooting a device.
Question
How does Mobiwol app blocks the internet connection per application? Or is there another way how to test apks offline?
EDIT 12/30/2014
I forgot to say that I am able to run tests offline but I have to start tests when the device is in offline state. Currently, I divided my tests into OFFLINE and ONLINE ones. After running ONLINEs I execute the famous adb kill-server and adb start-server. After that I execute OFFLINEs.
Just making a few suggestions since there seem to be different questions here.
1) If all you want to do is turn off the data before running the OFFLINE test case you might want to simply try using robotium itself to do so..
Example:
For WiFi:
WifiManager wifi=(WifiManager)solo.getCurrentActivity().getSystemService(Context.WIFI_SERVICE);
wifi.setWifiEnabled(false);
For Mobile Data(using reflections):
ConnectivityManager dataManager=(ConnectivityManager)solo.getCurrentActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
Method dataClass = ConnectivityManager.class.getDeclaredMethod(“setMobileDataEnabled”, boolean.class);
dataClass.setAccessible(true);
dataClass.invoke(dataManager, true);
You can do the two above calls in the setup() method before running the individual test case in the OFFLINE suite.
Once all the test case in the OFFLINE suite are done with you can enable the WiFi/DATA back on in the teardown() method at the very end.
2) Looking at the app that you posted in the OP, it seems pretty much that it:
Uses the ipTables based on the OS version
Creates a script header based on the UID's for all the applications
that need WiFi/Data
Should be getting the list of installed apps on the device along with
any hidden apps etc from the package manager.
And again executes scripts based on user selection for black list and
overrides the existing rules in the ipTable with the user desired
rules.
Pretty sure though must have been quite hard to code all of that..Sounds much easier in the form of bullet points.
Hope this helps you somewhat.
P.S: If you do figure out something please post an updated answer, would like to know how did you make it work
Update: Make sure you have the neccessary permissions for setting the WiFi/Data on/off in your application manifest. NOT the test apk manifest. IT HAS TO BE THE APPLICATION MANIFEST ITSELF.
There is this library which might help you. Its an extension to solo. http://adventuresinqa.com/2014/02/17/extsolo-library-to-extend-your-robotium-test-automation/
After spending hours trying to do it similar to user2511882s solution, I still had an exception, because of missing permissions (yes the "modify system settings" permission was activated).
I ended up doing it with UI automator:
public static void setAirplaneMode(boolean enable)
{
if ((enable ? 1 : 0) == Settings.System.getInt(getInstrumentation().getContext().getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0))
{
return;
}
UiDevice device = UiDevice.getInstance(getInstrumentation());
device.openQuickSettings();
// Find the text of your language
BySelector description = By.desc("Airplane mode");
// Need to wait for the button, as the opening of quick settings is animated.
device.wait(Until.hasObject(description), 500);
device.findObject(description).click();
getInstrumentation().getContext().sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
You will need the ACCESS_NETWORK_STATE in your androidTest manifest file:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Don't forget to disable it after the test.
If you have other languages than english, you need to change "Airplane mode" to the text of your language. As I have several translations, I read it from a ressource string.
There is great library from LinkedIn Test Butler, you can enable, disable both WiFi and mobile data by simply calling:
TestButler.setGsmState(false);
TestButler.setWifiState(false);
The main advantage of this library is that it does not require any permission in your manifest, for more details please refer to project website:
https://github.com/linkedin/test-butler
Sorry if I'm oversimplifying this, but what about just putting the phone/emulator in airplane mode? Through the actual user interface. That's what I do to test offline cases.
Here is a solution that uses UiAutomator to enable or disable "Aeroplane mode" from the drop-down status bar, which turns off all networking if enabled. It works on most Android OS versions, and it uses parts of the answer from user Aorlinn (12 October 2019).
app/build.gradle
dependencies {
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
}
AndroidManifest.xml
No extra permissions are needed, not even <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> , because UiAutomator is only clicking on a button in the status bar UI. Therefore the app does not need direct access to modify the device's Wifi or mobile data settings.
Kotlin
import android.os.Build
import android.provider.Settings
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.Until
import org.hamcrest.Matchers.`is`
import org.junit.Assert
import org.junit.Assume.assumeNoException
import org.junit.Assume.assumeThat
import java.io.IOException
private var airplaneModeOn = 0
private val OFF = 0
private val ON = 1
private var airplaneModeButtonPosition: Point? = null
/**
* Turn off the Internet connectivity by switching "Aeroplane mode" (flight mode) on. From:
* https://stackoverflow.com/questions/27620976/android-instrumentation-test-offline-cases#68956544
*/
#Test
fun checkAppWillWork_withNoInternetConnection() {
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: Exception) {
e.printStackTrace()
assumeNoException("Cannot retrieve device setting of 'Airplane Mode'. Aborting the test.", e)
}
// Check that Airplane mode is off
assertThat(airplaneModeOn, `is`(OFF))
// Press the "Aeroplane mode" button on the 'Quick Settings' panel
toggleAeroplaneModeButton()
// Verify Airplane mode is on
assumeThat("Cannot change the 'Aeroplane Mode' setting. Test aborted.", airplaneModeOn, `is`(ON))
assertThat(airplaneModeOn, `is`(ON))
// Do some tests when the Internet is down
// Note: Switch the Internet back on in cleanup()
}
/**
* Turn the Internet connectivity on or off by pressing the "Aeroplane mode" button. It opens the
* status bar at the top, then drags it down to reveal the buttons on the 'Quick Settings' panel.
*/
#Throws(UiObjectNotFoundException::class)
private fun toggleAeroplaneModeButton() {
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: SecurityException) {
e.printStackTrace()
Assert.fail()
} catch (e: IOException) {
e.printStackTrace()
Assert.fail()
}
// Open the status bar at the top; drag it down to reveal the buttons
val device = UiDevice.getInstance(getInstrumentation())
device.openQuickSettings()
// Wait for the button to be visible, because opening the Quick Settings is animated.
// You can use any string here; You only need a time delay wait here.
val description = By.desc("AeroplaneMode")
device.wait(Until.hasObject(description), 2000)
// Search for and click the button
var buttonClicked = clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
if (!buttonClicked) {
// Swipe the Quick Panel window to the LEFT, if possible
val screenWidth = device.displayWidth
val screenHeight = device.displayHeight
device.swipe((screenWidth * 0.80).toInt(), (screenHeight * 0.30).toInt(),
(screenWidth * 0.20).toInt(), (screenHeight * 0.30).toInt(), 50)
buttonClicked = clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
}
if (!buttonClicked) {
// Swipe the Quick Panel window to the RIGHT, if possible
val screenWidth = device.displayWidth
val screenHeight = device.displayHeight
device.swipe((screenWidth * 0.20).toInt(), (screenHeight * 0.30).toInt(),
(screenWidth * 0.80).toInt(), (screenHeight * 0.30).toInt(), 50)
clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
}
// Wait for the Internet to disconnect or re-connect
device.wait(Until.hasObject(description), 6000)
// Close the Quick Settings panel
getInstrumentation().context
.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
// Verify change in device settings
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: SecurityException) {
e.printStackTrace()
Assert.fail()
} catch (e: IOException) {
e.printStackTrace()
Assert.fail()
}
}
/**
* On Android 8/9, use an 'adb shell' command to retrieve the "Airplane Mode" setting, like this:
*
*
* `adb shell settings list global | grep airplane_mode_on ==> "airplane_mode_on=0"`
*
*
* (But note that grep is not available on the Android shell. See guidance URLs below)
*
*
* * https://www.reddit.com/r/tasker/comments/fbi5ai/psa_you_can_use_adb_to_find_all_the_settings_that/
* * https://stackoverflow.com/questions/33970956/test-if-soft-keyboard-is-visible-using-espresso
*
*
* On all other Android OS versions, use `Settings.System.getInt()` to retrieve the "Airplane Mode" setting.
* It sets `airplaneModeOn = 1 (true)` or `airplaneModeOn = 0 (false)`
*
* #throws IOException if `executeShellCommand()` didn't work
* #throws SecurityException if `Settings.System.getInt()` didn't work
*/
#Throws(IOException::class, SecurityException::class)
private fun getCurrentAirplaneModeSetting(): Int {
if (Build.VERSION.SDK_INT in 26..28 /*Android 8-9*/) {
val shellResponse = UiDevice
.getInstance(getInstrumentation())
.executeShellCommand("settings list global")
airplaneModeOn = when {
shellResponse.contains("airplane_mode_on=1") -> 1
shellResponse.contains("airplane_mode_on=0") -> 0
else -> throw IOException("Unsuitable response from adb shell command 'settings list global'")
}
} else {
// Oddly this causes a SecurityException on Android 8,9 devices
airplaneModeOn = Settings.System.getInt(
getInstrumentation().context.contentResolver,
Settings.Global.AIRPLANE_MODE_ON,
0)
}
return airplaneModeOn
}
/**
* Make UiAutomator search for and click a single button, based on its text label.
*
* Sometimes multiple buttons will match the required text label. For example,
* when Airplane mode is switched on, "Mobile data Aeroplane mode" and "Aeroplane mode"
* are 2 separate buttons on the Quick Settings panel, on Android 10+.
* For Aeroplane mode, always click on the last matched item.
*
* #param textLabels You must supply the language variants of the button that you want to click,
* for example "Cancel" (English), "Cancelar" (Spanish), "취소" (Korean)
* #return True if a button was found and clicked, otherwise return false
*/
private fun clickObjectIfFound(device: UiDevice, vararg textLabels: String): Boolean {
for (languageVariant in textLabels) {
val availableButtons = device.findObjects(By.text(languageVariant))
if (availableButtons.size >= 1) {
if (airplaneModeButtonPosition == null) {
availableButtons[availableButtons.size - 1].click()
airplaneModeButtonPosition = availableButtons[availableButtons.size - 1].visibleCenter
return true
} else {
// Use the stored position to avoid clicking on the wrong button
for (button in availableButtons) {
if (button.visibleCenter == airplaneModeButtonPosition) {
button.click()
airplaneModeButtonPosition = null
return true
}
}
}
}
}
return false
}
#After
fun cleanup() {
// Switch the Internet connectivity back on, for other tests.
if (airplaneModeOn == ON) {
toggleAeroplaneModeButton()
assertThat(airplaneModeOn, `is`(OFF))
}
}
You will have to adjust the string "Aeroplane mode" if your device is in a different language to English. For example, you could check for about 70 language translations here: https://github.com/aosp-mirror/platform_frameworks_base/search?q=global_actions_toggle_airplane_mode
Due to #user2511882 answer you can use Application context instead of Activity in Android X Test via:
internal fun switchWifi(value: Boolean) {
val wifiManager = ApplicationProvider.getApplicationContext<YourApplicationClass>().getSystemService(Context.WIFI_SERVICE) as WifiManager
wifiManager.isWifiEnabled = value}
Consider that this approach only works on API <= 28. There are other approaches like using UI Automator or API > 28
I created a TYPE_SYSTEM_ALERT view, set the flags FLAG_NOT_TOUCH_MODAL and FLAG_WATCH_OUTSIDE_TOUCH, and added it with WindowManager.addView().
When I touch outside of the view onto my own activity, everything works and MotionEvent.getY() returns the correct value.
However, if I exit my activity and and touch onto another application, MotionEvent.getY() always returns 0.
I'm not sure if this only happens on 4.2 or not.
Any help would be appreciated!
It's unfortunate that this question has remained unanswered for 1.5 years, but I ran into the same thing you did and found out why!
After scouring the source code, I found the source of the issue:
https://github.com/android/platform_frameworks_base/blob/79e0206ef3203a1842949242e58fa8f3c25eb129/services/input/InputDispatcher.cpp#L1417
// Check whether windows listening for outside touches are owned by the same UID. If it is
// set the policy flag that we will not reveal coordinate information to this window.
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
sp<InputWindowHandle> foregroundWindowHandle =
mTempTouchState.getFirstForegroundWindowHandle();
const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
sp<InputWindowHandle> inputWindowHandle = touchedWindow.windowHandle;
if (inputWindowHandle->getInfo()->ownerUid != foregroundWindowUid) {
mTempTouchState.addOrUpdateWindow(inputWindowHandle,
InputTarget::FLAG_ZERO_COORDS, BitSet32(0));
}
}
}
}
If the "outside touch" lands in a view that doesn't share its UID (read about it here) with the view that's listening for outside touches, the event dispatcher sets its coordinates to 0,0. This was definitely done for security purposes, but I'm not sure I see the full scope of the threat it's designed to mitigate. And this gentleman here (SO) reports that you can retrieve location data on 2.3.6, but it seems that at least 4.x won't reveal it to you (I tried 4.1.2, it didn't work).
I opened up a bug ticket about this if you'd like to follow it. At the very least, the documentation needs to include this information... I would also like to know if this security feature is really necessary.
Issue 72746: FLAG_WATCH_OUTSIDE_TOUCH doesn't return location for ACTION_OUTSIDE events on 4.2+
I noticed that some running services have a Settings rather than Stop button in the details page of Running services in the system Settings app. I want to setup my own service to work like this.
After digging into the source code of Settings app, I found a clue:
ActivityManager.getRunningServiceControlPanel()
Returns a PendingIntent you can start to show a control panel for the given running service. If the service does not have a control panel, null is returned.
My question is: how can I set a control panel for my own service?
In case anyone's curious, I found the code behind this feature. The service's description and configuration intent can be set during a service binding, if and only if the caller is running as SYSTEM.
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2.1_r1/com/android/server/am/ActivityManagerService.java#11651
int clientLabel = 0;
PendingIntent clientIntent = null;
if (callerApp.info.uid == Process.SYSTEM_UID) {
// Hacky kind of thing -- allow system stuff to tell us
// what they are, so we can report this elsewhere for
// others to know why certain services are running.
try {
clientIntent = (PendingIntent)service.getParcelableExtra(
Intent.EXTRA_CLIENT_INTENT);
} catch (RuntimeException e) {
}
if (clientIntent != null) {
clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0);
if (clientLabel != 0) {
// There are no useful extras in the intent, trash them.
// System code calling with this stuff just needs to know
// this will happen.
service = service.cloneFilter();
}
}
}
This code was moved at some point, but still exists in KitKat, unchanged.
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/com/android/server/am/ActiveServices.java#665
My app allows the user to access their corporate voice mail. Normally, durring a phone call when the user holds the device up to their ear, the screen shuts off so they wont accidentally push buttons with their face. I would like to make my app do the same thing when the user is listening to their voice mail.
anyone know how to do this?
If you are allowed to look at open source code without causing yourself problems, check the source of the Android Phone Application. Specifically src/com/android/phone/PhoneApp.java and src/com/android/phone/InCallScreen.java.
From src/com/android/phone/PhoneApp.java:
//Around line 519
// Wake lock used to control proximity sensor behavior.
if ((pm.getSupportedWakeLockFlags()
& PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) != 0x0) {
mProximityWakeLock = pm.newWakeLock(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
LOG_TAG);
}
....
// Around line 1334
if (((state == Phone.State.OFFHOOK) || mBeginningCall)&& !screenOnImmediately) {
// Phone is in use! Arrange for the screen to turn off
// automatically when the sensor detects a close object.
if (!mProximityWakeLock.isHeld()) {
if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: acquiring...");
mProximityWakeLock.acquire();
} else {
if (VDBG) Log.d(LOG_TAG, "updateProximitySensorMode: lock already held.");
}
} else {
// Phone is either idle, or ringing. We don't want any
// special proximity sensor behavior in either case.
if (mProximityWakeLock.isHeld()) {
if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: releasing...");
// Wait until user has moved the phone away from his head if we are
// releasing due to the phone call ending.
// Qtherwise, turn screen on immediately
int flags =
(screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
mProximityWakeLock.release(flags);
}
}
Additionally, if you look at the code for the PowerManager class, PROXIMITY_SCREEN_OFF_WAKE_LOCK is documented (but hidden) and should do what you want ( I am not sure which API level this works for, however ) -- but not in the table for some reason.
/**
* Wake lock that turns the screen off when the proximity sensor activates.
* Since not all devices have proximity sensors, use
* {#link #getSupportedWakeLockFlags() getSupportedWakeLockFlags()} to determine if
* this wake lock mode is supported.
*
* {#hide}
*/
public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = WAKE_BIT_PROXIMITY_SCREEN_OFF;
If you aren't afraid of using a potential undocumented feature, it should do exactly what you need.
as of API level 21 (Lollipop) you can get proximity wake lock this just like that:
if(powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
wakeLock.setReferenceCounted(false);
return wakeLock;
} else {
return null;
}
}
then it is up to you to acquire and release the lock.
PS: PowerManager#getSupportedWakeLockFlags was hidden, but now exists nomore. They have invented isWakeLockLevelSupported instead.
Probably you don't need it anymore but for the ones that are interested in code you could have a look at my SpeakerProximity project at http://code.google.com/p/speakerproximity/
What you are seeing is the use of a proximity sensor. For devices that have one, you access it through SensorManager.