Skip to content

Commit 9f7fc32

Browse files
committed
use device admin for power-off & lockscreen when root access is not available
1 parent 310743b commit 9f7fc32

File tree

7 files changed

+296
-7
lines changed

7 files changed

+296
-7
lines changed

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ android {
99
applicationId "it.pgp.currenttoggles"
1010
minSdkVersion 21 // for sdk 19 and lower, just use the great PowerToggles :)
1111
targetSdkVersion 28 // do not raise this, otherwise wifi toggle won't work without root on api 29+
12-
versionCode 125230305
13-
versionName "1.2.5"
12+
versionCode 126240109
13+
versionName "1.2.6"
1414
}
1515

1616
buildTypes {

app/src/main/AndroidManifest.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@
4141
android:name="android.appwidget.provider"
4242
android:resource="@xml/mainwidget_info" />
4343
</receiver>
44+
45+
<receiver
46+
android:name=".utils.deviceadmin.AdminReceiver"
47+
android:label="@string/device_admin_lockscreen"
48+
android:permission="android.permission.BIND_DEVICE_ADMIN">
49+
<meta-data
50+
android:name="android.app.device_admin"
51+
android:resource="@xml/device_admin_lockscreen" />
52+
<intent-filter>
53+
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
54+
</intent-filter>
55+
</receiver>
4456
</application>
4557

4658
</manifest>

app/src/main/java/it/pgp/currenttoggles/MainActivity.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import android.app.Activity;
44
import android.app.NotificationManager;
5+
import android.app.admin.DevicePolicyManager;
56
import android.bluetooth.BluetoothAdapter;
67
import android.content.ComponentName;
78
import android.content.ContentResolver;
@@ -15,6 +16,7 @@
1516
import android.os.Bundle;
1617
import android.os.Handler;
1718
import android.os.Looper;
19+
import android.os.PowerManager;
1820
import android.provider.Settings;
1921
import android.text.Html;
2022
import android.util.Log;
@@ -28,6 +30,7 @@
2830

2931
import it.pgp.currenttoggles.utils.Misc;
3032
import it.pgp.currenttoggles.utils.RootHandler;
33+
import it.pgp.currenttoggles.utils.deviceadmin.AdminReceiver;
3134
import it.pgp.currenttoggles.utils.oreoap.MyOnStartTetheringCallback;
3235
import it.pgp.currenttoggles.utils.oreoap.MyOreoWifiManager;
3336
import it.pgp.currenttoggles.utils.oreoap.PreOreoWifiManager;
@@ -65,7 +68,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
6568
else Toast.makeText(this, "Please grant notifications permissions on Android 13 in order to show toast messages from widget", Toast.LENGTH_SHORT).show();
6669
finishAffinity();
6770
}
68-
else if(requestCode == 1235) finishAffinity();
71+
else if(requestCode == 1235 || requestCode == 1236) finishAffinity();
6972
}
7073

7174
@Override
@@ -88,6 +91,13 @@ else if(extras.getString("HOTSPOT_OPTIONS") != null) {
8891
startActivityForResult(options, 1235);
8992
return;
9093
}
94+
else if(extras.getString("DEVICEADMIN_OPTIONS") != null) {
95+
ComponentName admin = new ComponentName(getApplicationContext(), AdminReceiver.class);
96+
Intent options = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
97+
options.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
98+
startActivityForResult(options, 1236);
99+
return;
100+
}
91101
}
92102
setContentView(R.layout.activity_main);
93103
}
@@ -347,15 +357,30 @@ public static void toggleHotspot(Context context) {
347357
}
348358
}
349359

360+
public static void powerOffScreenViaDeviceAdmin(Context context) {
361+
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
362+
if (pm.isScreenOn()) {
363+
DevicePolicyManager policy = (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
364+
try {
365+
policy.lockNow();
366+
}
367+
catch(Exception ex) {
368+
postToast(context, "Device administrator capability is needed for powering off screen without root access");
369+
Intent options = new Intent(context, MainActivity.class);
370+
options.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
371+
options.putExtra("DEVICEADMIN_OPTIONS", "");
372+
context.startActivity(options);
373+
}
374+
}
375+
}
376+
350377
public static void turnOffAndLockScreen(Context context) {
351378
int exitCode = -1;
352379
try {
353380
exitCode = RootHandler.executeCommandAndWaitFor("input keyevent KEYCODE_POWER",null,true,null);
354381
}
355-
catch(IOException e) {
356-
e.printStackTrace();
357-
}
358-
if(exitCode != 0) postToast(context, "Unable to turn off screen");
382+
catch(IOException ignored) {}
383+
if(exitCode != 0) powerOffScreenViaDeviceAdmin(context);
359384
}
360385

361386
public void toggle(View v) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package it.pgp.currenttoggles.utils.deviceadmin;
2+
3+
import android.app.admin.DeviceAdminReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
7+
public class AdminReceiver extends DeviceAdminReceiver {
8+
public static final String ACTION_DISABLED = "device_admin_disabled";
9+
public static final String ACTION_ENABLED = "device_admin_enabled";
10+
11+
@Override
12+
public void onDisabled(Context context, Intent intent) {
13+
super.onDisabled(context, intent);
14+
LocalBroadcastManager.getInstance(context).sendBroadcast(
15+
new Intent(ACTION_DISABLED));
16+
}
17+
@Override
18+
public void onEnabled(Context context, Intent intent) {
19+
super.onEnabled(context, intent);
20+
LocalBroadcastManager.getInstance(context).sendBroadcast(
21+
new Intent(ACTION_ENABLED));
22+
}
23+
}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package it.pgp.currenttoggles.utils.deviceadmin;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.content.IntentFilter;
7+
import android.net.Uri;
8+
import android.os.Handler;
9+
import android.os.Message;
10+
import android.util.Log;
11+
12+
13+
import java.util.ArrayList;
14+
import java.util.HashMap;
15+
import java.util.Set;
16+
17+
public final class LocalBroadcastManager {
18+
private static final class ReceiverRecord {
19+
final IntentFilter filter;
20+
final BroadcastReceiver receiver;
21+
boolean broadcasting;
22+
boolean dead;
23+
24+
ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
25+
filter = _filter;
26+
receiver = _receiver;
27+
}
28+
29+
@Override
30+
public String toString() {
31+
StringBuilder builder = new StringBuilder(128);
32+
builder.append("Receiver{");
33+
builder.append(receiver);
34+
builder.append(" filter=");
35+
builder.append(filter);
36+
if (dead) builder.append(" DEAD");
37+
builder.append("}");
38+
return builder.toString();
39+
}
40+
}
41+
42+
private static final class BroadcastRecord {
43+
final Intent intent;
44+
final ArrayList<ReceiverRecord> receivers;
45+
46+
BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
47+
intent = _intent;
48+
receivers = _receivers;
49+
}
50+
}
51+
52+
private static final String TAG = "LocalBroadcastManager";
53+
private static final boolean DEBUG = false;
54+
55+
private final Context mAppContext;
56+
57+
private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers = new HashMap<>();
58+
private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();
59+
60+
private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
61+
62+
static final int MSG_EXEC_PENDING_BROADCASTS = 1;
63+
64+
private final Handler mHandler;
65+
66+
private static final Object mLock = new Object();
67+
private static LocalBroadcastManager mInstance;
68+
69+
public static LocalBroadcastManager getInstance(Context context) {
70+
synchronized(mLock) {
71+
if(mInstance == null) mInstance = new LocalBroadcastManager(context.getApplicationContext());
72+
return mInstance;
73+
}
74+
}
75+
76+
private LocalBroadcastManager(Context context) {
77+
mAppContext = context;
78+
mHandler = new Handler(context.getMainLooper()) {
79+
@Override
80+
public void handleMessage(Message msg) {
81+
switch(msg.what) {
82+
case MSG_EXEC_PENDING_BROADCASTS:
83+
executePendingBroadcasts();
84+
break;
85+
default:
86+
super.handleMessage(msg);
87+
}
88+
}
89+
};
90+
}
91+
92+
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
93+
synchronized (mReceivers) {
94+
ReceiverRecord entry = new ReceiverRecord(filter, receiver);
95+
ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
96+
if(filters == null) {
97+
filters = new ArrayList<>(1);
98+
mReceivers.put(receiver, filters);
99+
}
100+
filters.add(entry);
101+
for(int i=0; i<filter.countActions(); i++) {
102+
String action = filter.getAction(i);
103+
ArrayList<ReceiverRecord> entries = mActions.get(action);
104+
if(entries == null) {
105+
entries = new ArrayList<>(1);
106+
mActions.put(action, entries);
107+
}
108+
entries.add(entry);
109+
}
110+
}
111+
}
112+
113+
public void unregisterReceiver(BroadcastReceiver receiver) {
114+
synchronized(mReceivers) {
115+
ArrayList<ReceiverRecord> filters = mReceivers.remove(receiver);
116+
if(filters == null) return;
117+
for(int i=filters.size()-1; i>=0; i--) {
118+
final ReceiverRecord filter = filters.get(i);
119+
filter.dead = true;
120+
for(int j=0; j<filter.filter.countActions(); j++) {
121+
final String action = filter.filter.getAction(j);
122+
final ArrayList<ReceiverRecord> receivers = mActions.get(action);
123+
if(receivers != null) {
124+
for(int k=receivers.size()-1; k>=0; k--) {
125+
ReceiverRecord rec = receivers.get(k);
126+
if(rec.receiver == receiver) {
127+
rec.dead = true;
128+
receivers.remove(k);
129+
}
130+
}
131+
if(receivers.size() <= 0) mActions.remove(action);
132+
}
133+
}
134+
}
135+
}
136+
}
137+
138+
public boolean sendBroadcast(Intent intent) {
139+
synchronized (mReceivers) {
140+
final String action = intent.getAction();
141+
final String type = intent.resolveTypeIfNeeded(mAppContext.getContentResolver());
142+
final Uri data = intent.getData();
143+
final String scheme = intent.getScheme();
144+
final Set<String> categories = intent.getCategories();
145+
146+
boolean debug = DEBUG || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
147+
if(debug) Log.v(TAG, "Resolving type " + type + " scheme " + scheme + " of intent " + intent);
148+
149+
ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
150+
if(entries != null) {
151+
if(debug) Log.v(TAG, "Action list: " + entries);
152+
153+
ArrayList<ReceiverRecord> receivers = null;
154+
for(int i=0; i<entries.size(); i++) {
155+
ReceiverRecord receiver = entries.get(i);
156+
if(debug) Log.v(TAG, "Matching against filter " + receiver.filter);
157+
158+
if(receiver.broadcasting) {
159+
if(debug) Log.v(TAG, " Filter's target already added");
160+
continue;
161+
}
162+
163+
int match = receiver.filter.match(action, type, scheme, data, categories, "LocalBroadcastManager");
164+
if(match >= 0) {
165+
if(debug) Log.v(TAG, " Filter matched! match=0x" + Integer.toHexString(match));
166+
if(receivers == null) receivers = new ArrayList<>();
167+
receivers.add(receiver);
168+
receiver.broadcasting = true;
169+
}
170+
else {
171+
if(debug) {
172+
String reason;
173+
switch(match) {
174+
case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
175+
case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
176+
case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
177+
case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
178+
default: reason = "unknown reason"; break;
179+
}
180+
Log.v(TAG, " Filter did not match: " + reason);
181+
}
182+
}
183+
}
184+
185+
if(receivers != null) {
186+
for(int i=0; i<receivers.size(); i++) receivers.get(i).broadcasting = false;
187+
mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
188+
if(!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS))
189+
mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
190+
return true;
191+
}
192+
}
193+
}
194+
return false;
195+
}
196+
197+
public void sendBroadcastSync(Intent intent) {
198+
if(sendBroadcast(intent)) executePendingBroadcasts();
199+
}
200+
201+
private void executePendingBroadcasts() {
202+
for(;;) {
203+
final BroadcastRecord[] brs;
204+
synchronized(mReceivers) {
205+
final int N = mPendingBroadcasts.size();
206+
if(N <= 0) return;
207+
brs = new BroadcastRecord[N];
208+
mPendingBroadcasts.toArray(brs);
209+
mPendingBroadcasts.clear();
210+
}
211+
for(int i=0; i<brs.length; i++) {
212+
final BroadcastRecord br = brs[i];
213+
final int nbr = br.receivers.size();
214+
for(int j=0; j<nbr; j++) {
215+
ReceiverRecord rec = br.receivers.get(j);
216+
if(!rec.dead) rec.receiver.onReceive(mAppContext, br.intent);
217+
}
218+
}
219+
}
220+
}
221+
}
222+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<resources>
22
<string name="app_name">CurrentToggles</string>
3+
<string name="device_admin_lockscreen">Device admin for lockscreen</string>
34
</resources>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
3+
<uses-policies>
4+
<force-lock />
5+
</uses-policies>
6+
</device-admin>

0 commit comments

Comments
 (0)