Merge pull request #52 from BinTianqi/dev

v5.6
This commit is contained in:
BinTianqi
2024-07-19 10:24:40 +08:00
committed by GitHub
32 changed files with 1719 additions and 258 deletions

View File

@@ -4,6 +4,10 @@ on:
push:
paths-ignore:
- '**.md'
tags-ignore:
- '**'
branches-ignore:
- 'master'
jobs:
build:

View File

@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.cc)
}
var keyPassword: String? = null
@@ -26,8 +27,8 @@ android {
applicationId = "com.bintianqi.owndroid"
minSdk = 21
targetSdk = 34
versionCode = 30
versionName = "5.5"
versionCode = 31
versionName = "5.6"
multiDexEnabled = false
}
@@ -56,9 +57,6 @@ android {
compose = true
aidl = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.13"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_KEYGUARD"/>
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_WIPE_DATA"/>
@@ -51,13 +52,24 @@
android:windowSoftInputMode="adjustResize|stateHidden"
android:theme="@style/Theme.OwnDroid">
</activity>
<activity
android:name=".AutomationActivity"
android:exported="true"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:windowSoftInputMode="adjustResize|stateHidden"
android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<activity
android:name=".InstallAppActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize|stateHidden"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:theme="@style/Theme.OwnDroidAppInstaller">
android:theme="@style/Theme.Transparent">
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.intent.action.VIEW"/>
@@ -86,9 +98,20 @@
</intent-filter>
</receiver>
<receiver
android:name=".PackageInstallerReceiver"
android:description="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN">
android:name=".PackageInstallerReceiver"
android:description="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN">
</receiver>
<receiver
android:name=".StopLockTaskModeReceiver"
android:description="@string/app_name">
</receiver>
<receiver
android:name=".AutomationReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</receiver>
<provider
android:name="rikka.shizuku.ShizukuProvider"

View File

@@ -0,0 +1,635 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.apksig.internal.apk;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unused")
public class AndroidBinXmlParser {
public static final int EVENT_START_DOCUMENT = 1;
public static final int EVENT_END_DOCUMENT = 2;
public static final int EVENT_START_ELEMENT = 3;
public static final int EVENT_END_ELEMENT = 4;
public static final int VALUE_TYPE_UNSUPPORTED = 0;
public static final int VALUE_TYPE_STRING = 1;
public static final int VALUE_TYPE_INT = 2;
public static final int VALUE_TYPE_REFERENCE = 3;
public static final int VALUE_TYPE_BOOLEAN = 4;
private static final long NO_NAMESPACE = 0xffffffffL;
private final ByteBuffer mXml;
private StringPool mStringPool;
private ResourceMap mResourceMap;
private int mDepth;
private int mCurrentEvent = EVENT_START_DOCUMENT;
private String mCurrentElementName;
private String mCurrentElementNamespace;
private int mCurrentElementAttributeCount;
private List<Attribute> mCurrentElementAttributes;
private ByteBuffer mCurrentElementAttributesContents;
private int mCurrentElementAttrSizeBytes;
public AndroidBinXmlParser(ByteBuffer xml) throws XmlParserException {
xml.order(ByteOrder.LITTLE_ENDIAN);
Chunk resXmlChunk = null;
while (xml.hasRemaining()) {
Chunk chunk = Chunk.get(xml);
if (chunk == null) {
break;
}
if (chunk.getType() == Chunk.TYPE_RES_XML) {
resXmlChunk = chunk;
break;
}
}
if (resXmlChunk == null) {
throw new XmlParserException("No XML chunk in file");
}
mXml = resXmlChunk.getContents();
}
public int getDepth() {
return mDepth;
}
public int getEventType() {
return mCurrentEvent;
}
public String getName() {
if ((mCurrentEvent != EVENT_START_ELEMENT) && (mCurrentEvent != EVENT_END_ELEMENT)) {
return null;
}
return mCurrentElementName;
}
public String getNamespace() {
if ((mCurrentEvent != EVENT_START_ELEMENT) && (mCurrentEvent != EVENT_END_ELEMENT)) {
return null;
}
return mCurrentElementNamespace;
}
public int getAttributeCount() {
if (mCurrentEvent != EVENT_START_ELEMENT) {
return -1;
}
return mCurrentElementAttributeCount;
}
public int getAttributeNameResourceId(int index) throws XmlParserException {
return getAttribute(index).getNameResourceId();
}
public String getAttributeName(int index) throws XmlParserException {
return getAttribute(index).getName();
}
public String getAttributeNamespace(int index) throws XmlParserException {
return getAttribute(index).getNamespace();
}
public int getAttributeValueType(int index) throws XmlParserException {
int type = getAttribute(index).getValueType();
switch (type) {
case Attribute.TYPE_STRING:
return VALUE_TYPE_STRING;
case Attribute.TYPE_INT_DEC:
case Attribute.TYPE_INT_HEX:
return VALUE_TYPE_INT;
case Attribute.TYPE_REFERENCE:
return VALUE_TYPE_REFERENCE;
case Attribute.TYPE_INT_BOOLEAN:
return VALUE_TYPE_BOOLEAN;
default:
return VALUE_TYPE_UNSUPPORTED;
}
}
public int getAttributeIntValue(int index) throws XmlParserException {
return getAttribute(index).getIntValue();
}
public boolean getAttributeBooleanValue(int index) throws XmlParserException {
return getAttribute(index).getBooleanValue();
}
public String getAttributeStringValue(int index) throws XmlParserException {
return getAttribute(index).getStringValue();
}
private Attribute getAttribute(int index) {
if (mCurrentEvent != EVENT_START_ELEMENT) {
throw new IndexOutOfBoundsException("Current event not a START_ELEMENT");
}
if (index < 0) {
throw new IndexOutOfBoundsException("index must be >= 0");
}
if (index >= mCurrentElementAttributeCount) {
throw new IndexOutOfBoundsException(
"index must be <= attr count (" + mCurrentElementAttributeCount + ")");
}
parseCurrentElementAttributesIfNotParsed();
return mCurrentElementAttributes.get(index);
}
public int next() throws XmlParserException {
if (mCurrentEvent == EVENT_END_ELEMENT) {
mDepth--;
}
while (mXml.hasRemaining()) {
Chunk chunk = Chunk.get(mXml);
if (chunk == null) {
break;
}
switch (chunk.getType()) {
case Chunk.TYPE_STRING_POOL:
if (mStringPool != null) {
throw new XmlParserException("Multiple string pools not supported");
}
mStringPool = new StringPool(chunk);
break;
case Chunk.RES_XML_TYPE_START_ELEMENT:
{
if (mStringPool == null) {
throw new XmlParserException(
"Named element encountered before string pool");
}
ByteBuffer contents = chunk.getContents();
if (contents.remaining() < 20) {
throw new XmlParserException(
"Start element chunk too short. Need at least 20 bytes. Available: "
+ contents.remaining() + " bytes");
}
long nsId = getUnsignedInt32(contents);
long nameId = getUnsignedInt32(contents);
int attrStartOffset = getUnsignedInt16(contents);
int attrSizeBytes = getUnsignedInt16(contents);
int attrCount = getUnsignedInt16(contents);
long attrEndOffset = attrStartOffset + ((long) attrCount) * attrSizeBytes;
contents.position(0);
if (attrStartOffset > contents.remaining()) {
throw new XmlParserException(
"Attributes start offset out of bounds: " + attrStartOffset
+ ", max: " + contents.remaining());
}
if (attrEndOffset > contents.remaining()) {
throw new XmlParserException(
"Attributes end offset out of bounds: " + attrEndOffset
+ ", max: " + contents.remaining());
}
mCurrentElementName = mStringPool.getString(nameId);
mCurrentElementNamespace =
(nsId == NO_NAMESPACE) ? "" : mStringPool.getString(nsId);
mCurrentElementAttributeCount = attrCount;
mCurrentElementAttributes = null;
mCurrentElementAttrSizeBytes = attrSizeBytes;
mCurrentElementAttributesContents =
sliceFromTo(contents, attrStartOffset, attrEndOffset);
mDepth++;
mCurrentEvent = EVENT_START_ELEMENT;
return mCurrentEvent;
}
case Chunk.RES_XML_TYPE_END_ELEMENT:
{
if (mStringPool == null) {
throw new XmlParserException(
"Named element encountered before string pool");
}
ByteBuffer contents = chunk.getContents();
if (contents.remaining() < 8) {
throw new XmlParserException(
"End element chunk too short. Need at least 8 bytes. Available: "
+ contents.remaining() + " bytes");
}
long nsId = getUnsignedInt32(contents);
long nameId = getUnsignedInt32(contents);
mCurrentElementName = mStringPool.getString(nameId);
mCurrentElementNamespace =
(nsId == NO_NAMESPACE) ? "" : mStringPool.getString(nsId);
mCurrentEvent = EVENT_END_ELEMENT;
mCurrentElementAttributes = null;
mCurrentElementAttributesContents = null;
return mCurrentEvent;
}
case Chunk.RES_XML_TYPE_RESOURCE_MAP:
if (mResourceMap != null) {
throw new XmlParserException("Multiple resource maps not supported");
}
mResourceMap = new ResourceMap(chunk);
break;
default:
break;
}
}
mCurrentEvent = EVENT_END_DOCUMENT;
return mCurrentEvent;
}
private void parseCurrentElementAttributesIfNotParsed() {
if (mCurrentElementAttributes != null) {
return;
}
mCurrentElementAttributes = new ArrayList<>(mCurrentElementAttributeCount);
for (int i = 0; i < mCurrentElementAttributeCount; i++) {
int startPosition = i * mCurrentElementAttrSizeBytes;
ByteBuffer attr =
sliceFromTo(
mCurrentElementAttributesContents,
startPosition,
startPosition + mCurrentElementAttrSizeBytes);
long nsId = getUnsignedInt32(attr);
long nameId = getUnsignedInt32(attr);
attr.position(attr.position() + 7); // skip ignored fields
int valueType = getUnsignedInt8(attr);
long valueData = getUnsignedInt32(attr);
mCurrentElementAttributes.add(
new Attribute(
nsId,
nameId,
valueType,
(int) valueData,
mStringPool,
mResourceMap));
}
}
private static class Attribute {
private static final int TYPE_REFERENCE = 1;
private static final int TYPE_STRING = 3;
private static final int TYPE_INT_DEC = 0x10;
private static final int TYPE_INT_HEX = 0x11;
private static final int TYPE_INT_BOOLEAN = 0x12;
private final long mNsId;
private final long mNameId;
private final int mValueType;
private final int mValueData;
private final StringPool mStringPool;
private final ResourceMap mResourceMap;
private Attribute(
long nsId,
long nameId,
int valueType,
int valueData,
StringPool stringPool,
ResourceMap resourceMap) {
mNsId = nsId;
mNameId = nameId;
mValueType = valueType;
mValueData = valueData;
mStringPool = stringPool;
mResourceMap = resourceMap;
}
public int getNameResourceId() {
return (mResourceMap != null) ? mResourceMap.getResourceId(mNameId) : 0;
}
public String getName() throws XmlParserException {
return mStringPool.getString(mNameId);
}
public String getNamespace() throws XmlParserException {
return (mNsId != NO_NAMESPACE) ? mStringPool.getString(mNsId) : "";
}
public int getValueType() {
return mValueType;
}
public int getIntValue() throws XmlParserException {
switch (mValueType) {
case TYPE_REFERENCE:
case TYPE_INT_DEC:
case TYPE_INT_HEX:
case TYPE_INT_BOOLEAN:
return mValueData;
default:
throw new XmlParserException("Cannot coerce to int: value type " + mValueType);
}
}
public boolean getBooleanValue() throws XmlParserException {
//noinspection SwitchStatementWithTooFewBranches
switch (mValueType) {
case TYPE_INT_BOOLEAN:
return mValueData != 0;
default:
throw new XmlParserException(
"Cannot coerce to boolean: value type " + mValueType);
}
}
public String getStringValue() throws XmlParserException {
switch (mValueType) {
case TYPE_STRING:
return mStringPool.getString(mValueData & 0xffffffffL);
case TYPE_INT_DEC:
return Integer.toString(mValueData);
case TYPE_INT_HEX:
return "0x" + Integer.toHexString(mValueData);
case TYPE_INT_BOOLEAN:
return Boolean.toString(mValueData != 0);
case TYPE_REFERENCE:
return "@" + Integer.toHexString(mValueData);
default:
throw new XmlParserException(
"Cannot coerce to string: value type " + mValueType);
}
}
}
private static class Chunk {
public static final int TYPE_STRING_POOL = 1;
public static final int TYPE_RES_XML = 3;
public static final int RES_XML_TYPE_START_ELEMENT = 0x0102;
public static final int RES_XML_TYPE_END_ELEMENT = 0x0103;
public static final int RES_XML_TYPE_RESOURCE_MAP = 0x0180;
static final int HEADER_MIN_SIZE_BYTES = 8;
private final int mType;
private final ByteBuffer mHeader;
private final ByteBuffer mContents;
public Chunk(int type, ByteBuffer header, ByteBuffer contents) {
mType = type;
mHeader = header;
mContents = contents;
}
public ByteBuffer getContents() {
ByteBuffer result = mContents.slice();
result.order(mContents.order());
return result;
}
public ByteBuffer getHeader() {
ByteBuffer result = mHeader.slice();
result.order(mHeader.order());
return result;
}
public int getType() {
return mType;
}
public static Chunk get(ByteBuffer input) throws XmlParserException {
if (input.remaining() < HEADER_MIN_SIZE_BYTES) {
// Android ignores the last chunk if its header is too big to fit into the file
input.position(input.limit());
return null;
}
int originalPosition = input.position();
int type = getUnsignedInt16(input);
int headerSize = getUnsignedInt16(input);
long chunkSize = getUnsignedInt32(input);
long chunkRemaining = chunkSize - 8;
if (chunkRemaining > input.remaining()) {
input.position(input.limit());
return null;
}
if (headerSize < HEADER_MIN_SIZE_BYTES) {
throw new XmlParserException(
"Malformed chunk: header too short: " + headerSize + " bytes");
} else if (headerSize > chunkSize) {
throw new XmlParserException(
"Malformed chunk: header too long: " + headerSize + " bytes. Chunk size: "
+ chunkSize + " bytes");
}
int contentStartPosition = originalPosition + headerSize;
long chunkEndPosition = originalPosition + chunkSize;
Chunk chunk =
new Chunk(
type,
sliceFromTo(input, originalPosition, contentStartPosition),
sliceFromTo(input, contentStartPosition, chunkEndPosition));
input.position((int) chunkEndPosition);
return chunk;
}
}
private static class StringPool {
private static final int FLAG_UTF8 = 1 << 8;
private final ByteBuffer mChunkContents;
private final ByteBuffer mStringsSection;
private final int mStringCount;
private final boolean mUtf8Encoded;
private final Map<Integer, String> mCachedStrings = new HashMap<>();
public StringPool(Chunk chunk) throws XmlParserException {
ByteBuffer header = chunk.getHeader();
int headerSizeBytes = header.remaining();
header.position(Chunk.HEADER_MIN_SIZE_BYTES);
if (header.remaining() < 20) {
throw new XmlParserException(
"XML chunk's header too short. Required at least 20 bytes. Available: "
+ header.remaining() + " bytes");
}
long stringCount = getUnsignedInt32(header);
if (stringCount > Integer.MAX_VALUE) {
throw new XmlParserException("Too many strings: " + stringCount);
}
mStringCount = (int) stringCount;
long styleCount = getUnsignedInt32(header);
if (styleCount > Integer.MAX_VALUE) {
throw new XmlParserException("Too many styles: " + styleCount);
}
long flags = getUnsignedInt32(header);
long stringsStartOffset = getUnsignedInt32(header);
long stylesStartOffset = getUnsignedInt32(header);
ByteBuffer contents = chunk.getContents();
if (mStringCount > 0) {
int stringsSectionStartOffsetInContents =
(int) (stringsStartOffset - headerSizeBytes);
int stringsSectionEndOffsetInContents;
if (styleCount > 0) {
if (stylesStartOffset < stringsStartOffset) {
throw new XmlParserException(
"Styles offset (" + stylesStartOffset + ") < strings offset ("
+ stringsStartOffset + ")");
}
stringsSectionEndOffsetInContents = (int) (stylesStartOffset - headerSizeBytes);
} else {
stringsSectionEndOffsetInContents = contents.remaining();
}
mStringsSection =
sliceFromTo(
contents,
stringsSectionStartOffsetInContents,
stringsSectionEndOffsetInContents);
} else {
mStringsSection = ByteBuffer.allocate(0);
}
mUtf8Encoded = (flags & FLAG_UTF8) != 0;
mChunkContents = contents;
}
public String getString(long index) throws XmlParserException {
if (index < 0) {
throw new XmlParserException("Unsuported string index: " + index);
} else if (index >= mStringCount) {
throw new XmlParserException(
"Unsuported string index: " + index + ", max: " + (mStringCount - 1));
}
int idx = (int) index;
String result = mCachedStrings.get(idx);
if (result != null) {
return result;
}
long offsetInStringsSection = getUnsignedInt32(mChunkContents, idx * 4);
if (offsetInStringsSection >= mStringsSection.capacity()) {
throw new XmlParserException(
"Offset of string idx " + idx + " out of bounds: " + offsetInStringsSection
+ ", max: " + (mStringsSection.capacity() - 1));
}
mStringsSection.position((int) offsetInStringsSection);
result =
(mUtf8Encoded)
? getLengthPrefixedUtf8EncodedString(mStringsSection)
: getLengthPrefixedUtf16EncodedString(mStringsSection);
mCachedStrings.put(idx, result);
return result;
}
private static String getLengthPrefixedUtf16EncodedString(ByteBuffer encoded)
throws XmlParserException {
int lengthChars = getUnsignedInt16(encoded);
if ((lengthChars & 0x8000) != 0) {
lengthChars = ((lengthChars & 0x7fff) << 16) | getUnsignedInt16(encoded);
}
if (lengthChars > Integer.MAX_VALUE / 2) {
throw new XmlParserException("String too long: " + lengthChars + " uint16s");
}
int lengthBytes = lengthChars * 2;
byte[] arr;
int arrOffset;
if (encoded.hasArray()) {
arr = encoded.array();
arrOffset = encoded.arrayOffset() + encoded.position();
encoded.position(encoded.position() + lengthBytes);
} else {
arr = new byte[lengthBytes];
arrOffset = 0;
encoded.get(arr);
}
if ((arr[arrOffset + lengthBytes] != 0)
|| (arr[arrOffset + lengthBytes + 1] != 0)) {
throw new XmlParserException("UTF-16 encoded form of string not NULL terminated");
}
return new String(arr, arrOffset, lengthBytes, StandardCharsets.UTF_16LE);
}
private static String getLengthPrefixedUtf8EncodedString(ByteBuffer encoded)
throws XmlParserException {
int lengthBytes = getUnsignedInt8(encoded);
if ((lengthBytes & 0x80) != 0) {
lengthBytes = ((lengthBytes & 0x7f) << 8) | getUnsignedInt8(encoded);
}
lengthBytes = getUnsignedInt8(encoded);
if ((lengthBytes & 0x80) != 0) {
lengthBytes = ((lengthBytes & 0x7f) << 8) | getUnsignedInt8(encoded);
}
byte[] arr;
int arrOffset;
if (encoded.hasArray()) {
arr = encoded.array();
arrOffset = encoded.arrayOffset() + encoded.position();
encoded.position(encoded.position() + lengthBytes);
} else {
arr = new byte[lengthBytes];
arrOffset = 0;
encoded.get(arr);
}
if (arr[arrOffset + lengthBytes] != 0) {
throw new XmlParserException("UTF-8 encoded form of string not NULL terminated");
}
return new String(arr, arrOffset, lengthBytes, StandardCharsets.UTF_8);
}
}
private static class ResourceMap {
private final ByteBuffer mChunkContents;
private final int mEntryCount;
public ResourceMap(Chunk chunk) throws XmlParserException {
mChunkContents = chunk.getContents().slice();
mChunkContents.order(chunk.getContents().order());
// Each entry of the map is four bytes long, containing the int32 resource ID.
mEntryCount = mChunkContents.remaining() / 4;
}
public int getResourceId(long index) {
if ((index < 0) || (index >= mEntryCount)) {
return 0;
}
int idx = (int) index;
// Each entry of the map is four bytes long, containing the int32 resource ID.
return mChunkContents.getInt(idx * 4);
}
}
private static ByteBuffer sliceFromTo(ByteBuffer source, long start, long end) {
if (start < 0) {
throw new IllegalArgumentException("start: " + start);
}
if (end < start) {
throw new IllegalArgumentException("end < start: " + end + " < " + start);
}
int capacity = source.capacity();
if (end > source.capacity()) {
throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
}
return sliceFromTo(source, (int) start, (int) end);
}
private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
if (start < 0) {
throw new IllegalArgumentException("start: " + start);
}
if (end < start) {
throw new IllegalArgumentException("end < start: " + end + " < " + start);
}
int capacity = source.capacity();
if (end > source.capacity()) {
throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
}
int originalLimit = source.limit();
int originalPosition = source.position();
try {
source.position(0);
source.limit(end);
source.position(start);
ByteBuffer result = source.slice();
result.order(source.order());
return result;
} finally {
source.position(0);
source.limit(originalLimit);
source.position(originalPosition);
}
}
private static int getUnsignedInt8(ByteBuffer buffer) {
return buffer.get() & 0xff;
}
private static int getUnsignedInt16(ByteBuffer buffer) {
return buffer.getShort() & 0xffff;
}
private static long getUnsignedInt32(ByteBuffer buffer) {
return buffer.getInt() & 0xffffffffL;
}
private static long getUnsignedInt32(ByteBuffer buffer, int position) {
return buffer.getInt(position) & 0xffffffffL;
}
public static class XmlParserException extends Exception {
private static final long serialVersionUID = 1L;
public XmlParserException(String message) {
super(message);
}
public XmlParserException(String message, Throwable cause) {
super(message, cause);
}
}
}

View File

@@ -0,0 +1,27 @@
package com.bintianqi.owndroid
import android.app.AlertDialog
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.ui.platform.LocalContext
class AutomationActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val result = handleTask(applicationContext, this.intent)
val sharedPrefs = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE)
if(sharedPrefs.getBoolean("automation_debug", false)) {
setContent {
AlertDialog.Builder(LocalContext.current)
.setMessage(result)
.setOnDismissListener { finish() }
.setPositiveButton(R.string.confirm) { _, _ -> finish() }
.show()
}
} else {
finish()
}
}
}

View File

@@ -0,0 +1,48 @@
package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.activity.ComponentActivity
class AutomationReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
handleTask(context, intent)
}
}
@SuppressLint("NewApi")
fun handleTask(context: Context, intent: Intent): String {
val sharedPrefs = context.getSharedPreferences("data", Context.MODE_PRIVATE)
val key = sharedPrefs.getString("automation_key", "") ?: ""
if(key.length < 6) {
return "Key length must longer than 6"
}
if(key != intent.getStringExtra("key")) {
return "Wrong key"
}
val operation = intent.getStringExtra("operation")
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val receiver = ComponentName(context,Receiver::class.java)
val app = intent.getStringExtra("app")
val restriction = intent.getStringExtra("restriction")
try {
when(operation) {
"suspend" -> dpm.setPackagesSuspended(receiver, arrayOf(app), true)
"unsuspend" -> dpm.setPackagesSuspended(receiver, arrayOf(app), false)
"hide" -> dpm.setApplicationHidden(receiver, app, true)
"unhide" -> dpm.setApplicationHidden(receiver, app, false)
"lock" -> dpm.lockNow()
"reboot" -> dpm.reboot(receiver)
"addUserRestriction" -> dpm.addUserRestriction(receiver, restriction)
"clearUserRestriction" -> dpm.clearUserRestriction(receiver, restriction)
else -> return "Operation not defined"
}
} catch(e: Exception) {
return e.message ?: "Failed to get error message"
}
return "No error, or error is unhandled"
}

View File

@@ -8,7 +8,9 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
@@ -17,13 +19,21 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.core.view.WindowCompat
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.bintianqi.owndroid.dpm.installPackage
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import com.github.fishb1.apkinfo.ApkInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.FileInputStream
class InstallAppActivity: FragmentActivity() {
override fun onNewIntent(intent: Intent?) {
@@ -34,37 +44,64 @@ class InstallAppActivity: FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
WindowCompat.setDecorFitsSystemWindows(window, false)
val context = applicationContext
val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE)
window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val uri = this.intent.data!!
var apkInfoText by mutableStateOf(context.getString(R.string.parsing_apk_info))
var status by mutableStateOf("parsing")
this.lifecycleScope.launch(Dispatchers.IO) {
val fd = applicationContext.contentResolver.openFileDescriptor(uri, "r")
val apkInfo = ApkInfo.fromInputStream(
FileInputStream(fd?.fileDescriptor)
)
fd?.close()
withContext(Dispatchers.Main) {
status = "waiting"
apkInfoText = "${context.getString(R.string.package_name)}: ${apkInfo.packageName}\n"
apkInfoText += "${context.getString(R.string.version_name)}: ${apkInfo.versionName}\n"
apkInfoText += "${context.getString(R.string.version_code)}: ${apkInfo.versionCode}"
}
}
setContent {
var installing by remember { mutableStateOf(false) }
OwnDroidTheme(
sharedPref.getBoolean("material_you", true),
sharedPref.getBoolean("black_theme", false)
) {
AlertDialog(
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false),
title = {
Text(stringResource(R.string.install_app))
},
onDismissRequest = {
finish()
if(status != "installing") finish()
},
text = {
AnimatedVisibility(installing) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
Column {
AnimatedVisibility(status != "waiting") {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
Text(text = apkInfoText, modifier = Modifier.padding(top = 4.dp))
}
},
dismissButton = {
TextButton(onClick = { finish() }) { Text(stringResource(R.string.cancel)) }
TextButton(
onClick = { finish() },
enabled = status != "installing"
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
installing = true
status = "installing"
uriToStream(applicationContext, this.intent.data) { stream -> installPackage(applicationContext, stream) }
}
},
enabled = status != "installing"
) {
Text(stringResource(R.string.confirm))
Text(stringResource(R.string.install))
}
},
modifier = Modifier.fillMaxWidth()
@@ -72,7 +109,10 @@ class InstallAppActivity: FragmentActivity() {
}
val installDone by installAppDone.collectAsState()
LaunchedEffect(installDone) {
if(installDone) finish()
if(installDone) {
installAppDone.value = false
finish()
}
}
}
}

View File

@@ -1,6 +1,5 @@
package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
@@ -41,7 +40,6 @@ import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.dpm.*
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import java.util.Locale
@@ -50,7 +48,6 @@ var backToHomeStateFlow = MutableStateFlow(false)
class MainActivity : FragmentActivity() {
private val showAuth = mutableStateOf(false)
@SuppressLint("UnrememberedMutableState")
override fun onCreate(savedInstanceState: Bundle?) {
registerActivityResult(this)
enableEdgeToEdge()
@@ -63,8 +60,8 @@ class MainActivity : FragmentActivity() {
val locale = applicationContext.resources?.configuration?.locale
zhCN = locale == Locale.SIMPLIFIED_CHINESE || locale == Locale.CHINESE || locale == Locale.CHINA
setContent {
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you", true))
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
val materialYou = remember { mutableStateOf(sharedPref.getBoolean("material_you", true)) }
val blackTheme = remember { mutableStateOf(sharedPref.getBoolean("black_theme", false)) }
OwnDroidTheme(materialYou.value, blackTheme.value) {
Home(materialYou, blackTheme)
if(showAuth.value) {
@@ -87,7 +84,6 @@ class MainActivity : FragmentActivity() {
}
@SuppressLint("UnrememberedMutableState")
@ExperimentalMaterial3Api
@Composable
fun Home(materialYou:MutableState<Boolean>, blackTheme:MutableState<Boolean>) {
@@ -97,10 +93,10 @@ fun Home(materialYou:MutableState<Boolean>, blackTheme:MutableState<Boolean>) {
val receiver = ComponentName(context,Receiver::class.java)
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val focusMgr = LocalFocusManager.current
val pkgName = mutableStateOf("")
val dialogStatus = mutableIntStateOf(0)
val pkgName = remember { mutableStateOf("") }
val dialogStatus = remember { mutableIntStateOf(0) }
val backToHome by backToHomeStateFlow.collectAsState()
LaunchedEffect(Unit) {
LaunchedEffect(backToHome) {
if(backToHome) { navCtrl.navigateUp(); backToHomeStateFlow.value = false }
}
NavHost(

View File

@@ -2,8 +2,11 @@ package com.bintianqi.owndroid
import android.Manifest
import android.os.Build.VERSION
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -11,14 +14,17 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
@@ -41,17 +47,25 @@ fun PermissionPicker(navCtrl: NavHostController) {
modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding())
) {
items(permissionList()) {
Column(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable{
selectedPermission.value = it.first
selectedPermission.value = it.permission
navCtrl.navigateUp()
}
.padding(vertical = 6.dp, horizontal = 8.dp)
.padding(vertical = 8.dp, horizontal = 8.dp)
) {
Text(text = it.first)
Text(text = stringResource(it.second), modifier = Modifier.alpha(0.8F))
Icon(
painter = painterResource(it.icon),
contentDescription = stringResource(it.label),
modifier = Modifier.padding(start = 8.dp, end = 10.dp)
)
Column {
Text(text = stringResource(it.label))
Text(text = it.permission, modifier = Modifier.alpha(0.8F), style = MaterialTheme.typography.bodyMedium)
}
}
}
items(1) { Spacer(Modifier.padding(vertical = 30.dp)) }
@@ -59,43 +73,48 @@ fun PermissionPicker(navCtrl: NavHostController) {
}
}
private fun permissionList():List<Pair<String,Int>>{
val list = mutableListOf<Pair<String,Int>>()
list.add(Pair(Manifest.permission.READ_EXTERNAL_STORAGE, R.string.permission_READ_EXTERNAL_STORAGE))
list.add(Pair(Manifest.permission.WRITE_EXTERNAL_STORAGE, R.string.permission_WRITE_EXTERNAL_STORAGE))
private data class PermissionPickerItem(
val permission: String,
@StringRes val label: Int,
@DrawableRes val icon: Int
)
private fun permissionList(): List<PermissionPickerItem>{
val list = mutableListOf<PermissionPickerItem>()
list.add(PermissionPickerItem(Manifest.permission.READ_EXTERNAL_STORAGE, R.string.permission_READ_EXTERNAL_STORAGE, R.drawable.folder_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_EXTERNAL_STORAGE, R.string.permission_WRITE_EXTERNAL_STORAGE, R.drawable.folder_fill0))
if(VERSION.SDK_INT >= 33) {
list.add(Pair(Manifest.permission.READ_MEDIA_AUDIO, R.string.permission_READ_MEDIA_AUDIO))
list.add(Pair(Manifest.permission.READ_MEDIA_VIDEO, R.string.permission_READ_MEDIA_VIDEO))
list.add(Pair(Manifest.permission.READ_MEDIA_IMAGES, R.string.permission_READ_MEDIA_IMAGES))
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_AUDIO, R.string.permission_READ_MEDIA_AUDIO, R.drawable.music_note_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_VIDEO, R.string.permission_READ_MEDIA_VIDEO, R.drawable.movie_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_IMAGES, R.string.permission_READ_MEDIA_IMAGES, R.drawable.image_fill0))
}
list.add(Pair(Manifest.permission.CAMERA, R.string.permission_CAMERA))
list.add(Pair(Manifest.permission.RECORD_AUDIO, R.string.permission_RECORD_AUDIO))
list.add(Pair(Manifest.permission.ACCESS_COARSE_LOCATION, R.string.permission_ACCESS_COARSE_LOCATION))
list.add(Pair(Manifest.permission.ACCESS_FINE_LOCATION, R.string.permission_ACCESS_FINE_LOCATION))
list.add(PermissionPickerItem(Manifest.permission.CAMERA, R.string.permission_CAMERA, R.drawable.photo_camera_fill0))
list.add(PermissionPickerItem(Manifest.permission.RECORD_AUDIO, R.string.permission_RECORD_AUDIO, R.drawable.mic_fill0))
list.add(PermissionPickerItem(Manifest.permission.ACCESS_COARSE_LOCATION, R.string.permission_ACCESS_COARSE_LOCATION, R.drawable.location_on_fill0))
list.add(PermissionPickerItem(Manifest.permission.ACCESS_FINE_LOCATION, R.string.permission_ACCESS_FINE_LOCATION, R.drawable.location_on_fill0))
if(VERSION.SDK_INT >= 29) {
list.add(Pair(Manifest.permission.ACCESS_BACKGROUND_LOCATION, R.string.permission_ACCESS_BACKGROUND_LOCATION))
list.add(PermissionPickerItem(Manifest.permission.ACCESS_BACKGROUND_LOCATION, R.string.permission_ACCESS_BACKGROUND_LOCATION, R.drawable.location_on_fill0))
}
list.add(Pair(Manifest.permission.READ_CONTACTS, R.string.permission_READ_CONTACTS))
list.add(Pair(Manifest.permission.WRITE_CONTACTS, R.string.permission_WRITE_CONTACTS))
list.add(Pair(Manifest.permission.READ_CALENDAR, R.string.permission_READ_CALENDAR))
list.add(Pair(Manifest.permission.WRITE_CALENDAR, R.string.permission_WRITE_CALENDAR))
list.add(Pair(Manifest.permission.CALL_PHONE, R.string.permission_CALL_PHONE))
list.add(Pair(Manifest.permission.READ_PHONE_STATE, R.string.permission_READ_PHONE_STATE))
list.add(Pair(Manifest.permission.READ_SMS, R.string.permission_READ_SMS))
list.add(Pair(Manifest.permission.RECEIVE_SMS, R.string.permission_RECEIVE_SMS))
list.add(Pair(Manifest.permission.SEND_SMS, R.string.permission_SEND_SMS))
list.add(Pair(Manifest.permission.READ_CALL_LOG, R.string.permission_READ_CALL_LOG))
list.add(Pair(Manifest.permission.WRITE_CALL_LOG, R.string.permission_WRITE_CALL_LOG))
list.add(Pair(Manifest.permission.BODY_SENSORS, R.string.permission_BODY_SENSORS))
list.add(PermissionPickerItem(Manifest.permission.READ_CONTACTS, R.string.permission_READ_CONTACTS, R.drawable.contacts_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CONTACTS, R.string.permission_WRITE_CONTACTS, R.drawable.contacts_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_CALENDAR, R.string.permission_READ_CALENDAR, R.drawable.calendar_month_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CALENDAR, R.string.permission_WRITE_CALENDAR, R.drawable.calendar_month_fill0))
list.add(PermissionPickerItem(Manifest.permission.CALL_PHONE, R.string.permission_CALL_PHONE, R.drawable.call_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_PHONE_STATE, R.string.permission_READ_PHONE_STATE, R.drawable.mobile_phone_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_SMS, R.string.permission_READ_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.RECEIVE_SMS, R.string.permission_RECEIVE_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.SEND_SMS, R.string.permission_SEND_SMS, R.drawable.sms_fill0))
list.add(PermissionPickerItem(Manifest.permission.READ_CALL_LOG, R.string.permission_READ_CALL_LOG, R.drawable.call_log_fill0))
list.add(PermissionPickerItem(Manifest.permission.WRITE_CALL_LOG, R.string.permission_WRITE_CALL_LOG, R.drawable.call_log_fill0))
list.add(PermissionPickerItem(Manifest.permission.BODY_SENSORS, R.string.permission_BODY_SENSORS, R.drawable.sensors_fill0))
if(VERSION.SDK_INT >= 33) {
list.add(Pair(Manifest.permission.BODY_SENSORS_BACKGROUND, R.string.permission_BODY_SENSORS_BACKGROUND))
list.add(PermissionPickerItem(Manifest.permission.BODY_SENSORS_BACKGROUND, R.string.permission_BODY_SENSORS_BACKGROUND, R.drawable.sensors_fill0))
}
if(VERSION.SDK_INT > 29) {
list.add(Pair(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION))
list.add(PermissionPickerItem(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION, R.drawable.history_fill0))
}
if(VERSION.SDK_INT >= 33) {
list.add(Pair(Manifest.permission.POST_NOTIFICATIONS, R.string.permission_POST_NOTIFICATIONS))
list.add(PermissionPickerItem(Manifest.permission.POST_NOTIFICATIONS, R.string.permission_POST_NOTIFICATIONS, R.drawable.notifications_fill0))
}
//list.add(Pair(Manifest.permission., R.string.))
return list
}

View File

@@ -1,5 +1,7 @@
package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.app.admin.DeviceAdminReceiver
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver
@@ -10,6 +12,7 @@ import android.content.pm.PackageInstaller.*
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.ui.platform.LocalContext
import com.bintianqi.owndroid.dpm.isDeviceOwner
import com.bintianqi.owndroid.dpm.isProfileOwner
import kotlinx.coroutines.flow.MutableStateFlow
@@ -61,3 +64,16 @@ class PackageInstallerReceiver:BroadcastReceiver(){
}
}
}
class StopLockTaskModeReceiver: BroadcastReceiver() {
@SuppressLint("NewApi")
override fun onReceive(context: Context, intent: Intent) {
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val receiver = ComponentName(context,Receiver::class.java)
val packages = dpm.getLockTaskPackages(receiver)
dpm.setLockTaskPackages(receiver, arrayOf())
dpm.setLockTaskPackages(receiver, packages)
val nm = context.getSystemService(ComponentActivity.NOTIFICATION_SERVICE) as NotificationManager
nm.cancel(1)
}
}

View File

@@ -4,13 +4,16 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION
import android.widget.Toast
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -41,8 +44,10 @@ fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, bl
modifier = Modifier.padding(top = it.calculateTopPadding())
) {
composable(route = "Home") { Home(localNavCtrl) }
composable(route = "Options") { Options() }
composable(route = "Theme") { ThemeSettings(materialYou, blackTheme) }
composable(route = "Auth") { AuthSettings() }
composable(route = "Automation") { Automation() }
composable(route = "About") { About() }
}
}
@@ -51,12 +56,26 @@ fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, bl
@Composable
private fun Home(navCtrl: NavHostController) {
Column(modifier = Modifier.fillMaxSize()) {
SubPageItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("Options") }
SubPageItem(R.string.theme, "", R.drawable.format_paint_fill0) { navCtrl.navigate("Theme") }
SubPageItem(R.string.security, "", R.drawable.lock_fill0) { navCtrl.navigate("Auth") }
SubPageItem(R.string.automation_api, "", R.drawable.apps_fill0) { navCtrl.navigate("Automation") }
SubPageItem(R.string.about, "", R.drawable.info_fill0) { navCtrl.navigate("About") }
}
}
@Composable
private fun Options() {
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
SwitchItem(
R.string.show_dangerous_features, "", R.drawable.warning_fill0,
{ sharedPref.getBoolean("dangerous_features", false) },
{ sharedPref.edit().putBoolean("dangerous_features", it).apply() }
)
}
}
@Composable
private fun ThemeSettings(materialYou:MutableState<Boolean>, blackTheme:MutableState<Boolean>) {
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
@@ -122,6 +141,36 @@ private fun AuthSettings() {
}
}
@Composable
private fun Automation() {
val context = LocalContext.current
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.automation_api), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
var key by remember { mutableStateOf("") }
TextField(
value = key, onValueChange = { key = it }, label = { Text("Key")},
modifier = Modifier.fillMaxWidth()
)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
sharedPref.edit().putString("automation_key", key).apply()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
}
) {
Text(stringResource(R.string.apply))
}
SwitchItem(
R.string.automation_debug, "", null,
{ sharedPref.getBoolean("automation_debug", false) },
{ sharedPref.edit().putBoolean("automation_debug", it).apply() }
)
}
}
@Composable
private fun About() {
val context = LocalContext.current

View File

@@ -1,8 +1,11 @@
package com.bintianqi.owndroid
import android.Manifest
import android.app.admin.DevicePolicyManager
import android.content.*
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build.VERSION
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
@@ -55,6 +58,10 @@ fun Set<Any>.toText(): String{
return output
}
fun MutableList<Int>.toggle(status: Boolean, element: Int) {
if(status) add(element) else remove(element)
}
fun writeClipBoard(context: Context, string: String):Boolean{
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
try {
@@ -65,6 +72,7 @@ fun writeClipBoard(context: Context, string: String):Boolean{
return true
}
lateinit var requestPermission: ActivityResultLauncher<String>
fun registerActivityResult(context: ComponentActivity){
getFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
@@ -83,4 +91,20 @@ fun registerActivityResult(context: ComponentActivity){
backToHomeStateFlow.value = true
}
}
requestPermission = context.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permissionGranted.value = it }
}
val permissionGranted = MutableStateFlow<Boolean?>(null)
suspend fun prepareForNotification(context: Context, action: ()->Unit) {
if(VERSION.SDK_INT >= 33) {
if(context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
action()
} else {
requestPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
permissionGranted.collect { if(it == true) action() }
}
} else {
action()
}
}

View File

@@ -158,6 +158,7 @@ fun ApplicationManage(navCtrl:NavHostController, pkgName: MutableState<String>,
composable(route = "Home") {
Home(localNavCtrl, pkgName.value, dialogStatus, clearAppDataDialog, defaultDialerAppDialog, enableSystemAppDialog)
}
composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(pkgName.value) }
composable(route = "UserControlDisabled") { UserCtrlDisabledPkg(pkgName.value) }
composable(route = "PermissionManage") { PermissionManage(pkgName.value, navCtrl) }
composable(route = "CrossProfilePackage") { CrossProfilePkg(pkgName.value) }
@@ -213,8 +214,8 @@ private fun Home(
if(VERSION.SDK_INT>=24 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) {
val getSuspendStatus = {
try{ dpm.isPackageSuspended(receiver, pkgName) }
catch(e:NameNotFoundException) { false }
catch(e:IllegalArgumentException) { false }
catch(e:NameNotFoundException) { false }
catch(e:IllegalArgumentException) { false }
}
SwitchItem(
title = R.string.suspend, desc = "", icon = R.drawable.block_fill0,
@@ -255,26 +256,7 @@ private fun Home(
)
}
if(VERSION.SDK_INT>=24 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) {
val setAlwaysOnVpn: (Boolean)->Unit = {
try {
dpm.setAlwaysOnVpnPackage(receiver, pkgName, it)
} catch(e: UnsupportedOperationException) {
Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show()
} catch(e: NameNotFoundException) {
Toast.makeText(context, R.string.not_installed, Toast.LENGTH_SHORT).show()
}
}
SwitchItem(
title = R.string.always_on_vpn, desc = "", icon = R.drawable.vpn_key_fill0,
getState = { pkgName == dpm.getAlwaysOnVpnPackage(receiver) },
onCheckedChange = setAlwaysOnVpn,
onClickBlank = {
dialogGetStatus = { pkgName == dpm.getAlwaysOnVpnPackage(receiver) }
dialogConfirmButtonAction = { setAlwaysOnVpn(true) }
dialogDismissButtonAction = { setAlwaysOnVpn(false) }
dialogStatus.intValue = 4
}
)
SubPageItem(R.string.always_on_vpn, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("AlwaysOnVpn") }
}
if((VERSION.SDK_INT>=33&&isProfileOwner(dpm))||(VERSION.SDK_INT>=30&&isDeviceOwner(dpm))) {
SubPageItem(R.string.ucd, "", R.drawable.do_not_touch_fill0) { navCtrl.navigate("UserControlDisabled") }
@@ -318,6 +300,50 @@ private fun Home(
}
}
@SuppressLint("NewApi")
@Composable
fun AlwaysOnVPNPackage(pkgName: String) {
val context = LocalContext.current
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val receiver = ComponentName(context,Receiver::class.java)
var lockdown by remember { mutableStateOf(false) }
var pkg by remember { mutableStateOf<String?>("") }
val refresh = { pkg = dpm.getAlwaysOnVpnPackage(receiver) }
LaunchedEffect(Unit) { refresh() }
val setAlwaysOnVpn: (String?, Boolean)->Unit = { vpnPkg: String?, lockdownEnabled: Boolean ->
try {
dpm.setAlwaysOnVpnPackage(receiver, vpnPkg, lockdownEnabled)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
} catch(e: UnsupportedOperationException) {
Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show()
} catch(e: NameNotFoundException) {
Toast.makeText(context, R.string.not_installed, Toast.LENGTH_SHORT).show()
}
}
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.always_on_vpn), style = typography.headlineLarge, modifier = Modifier.padding(8.dp))
Spacer(Modifier.padding(vertical = 5.dp))
Text(text = stringResource(R.string.current_app_is) + pkg, modifier = Modifier.padding(8.dp))
SwitchItem(R.string.enable_lockdown, "", null, { lockdown }, { lockdown = it })
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = { setAlwaysOnVpn(pkgName, lockdown); refresh() },
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)
) {
Text(stringResource(R.string.apply))
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = { setAlwaysOnVpn(null, false); refresh() },
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)
) {
Text(stringResource(R.string.clear_current_config))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun UserCtrlDisabledPkg(pkgName:String) {

View File

@@ -11,11 +11,15 @@ import android.app.admin.DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
import android.app.admin.DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED
import android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED
import android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT
import android.app.admin.DevicePolicyManager.WIPE_EUICC
import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE
import android.app.admin.DevicePolicyManager.WIPE_SILENTLY
import android.content.*
import android.os.Binder
import android.os.Build.VERSION
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -26,13 +30,17 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -80,6 +88,7 @@ fun ManagedProfile(navCtrl: NavHostController) {
composable(route = "CreateWorkProfile") { CreateWorkProfile() }
composable(route = "SuspendPersonalApp") { SuspendPersonalApp() }
composable(route = "IntentFilter") { IntentFilter() }
composable(route = "DeleteWorkProfile") { DeleteWorkProfile() }
}
}
}
@@ -97,7 +106,7 @@ private fun Home(navCtrl: NavHostController) {
style = typography.headlineLarge,
modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp)
)
if(VERSION.SDK_INT >= 30&&isProfileOwner(dpm) && dpm.isManagedProfile(receiver)) {
if(VERSION.SDK_INT >= 30 && isProfileOwner(dpm) && dpm.isManagedProfile(receiver)) {
SubPageItem(R.string.org_owned_work_profile, "", R.drawable.corporate_fare_fill0) { navCtrl.navigate("OrgOwnedWorkProfile") }
}
if(VERSION.SDK_INT<24 || (VERSION.SDK_INT>=24 && dpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))) {
@@ -106,9 +115,12 @@ private fun Home(navCtrl: NavHostController) {
if(dpm.isOrgProfile(receiver)) {
SubPageItem(R.string.suspend_personal_app, "", R.drawable.block_fill0) { navCtrl.navigate("SuspendPersonalApp") }
}
if(isProfileOwner(dpm) && (VERSION.SDK_INT<24 || (VERSION.SDK_INT>=24 && dpm.isManagedProfile(receiver)))) {
if(isProfileOwner(dpm) && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) {
SubPageItem(R.string.intent_filter, "", R.drawable.filter_alt_fill0) { navCtrl.navigate("IntentFilter") }
}
if(isProfileOwner(dpm) && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) {
SubPageItem(R.string.delete_work_profile, "", R.drawable.delete_forever_fill0) { navCtrl.navigate("DeleteWorkProfile") }
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@@ -265,3 +277,81 @@ private fun IntentFilter() {
}
}
}
@Composable
private fun DeleteWorkProfile() {
val context = LocalContext.current
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val focusMgr = LocalFocusManager.current
var warning by remember { mutableStateOf(false) }
var externalStorage by remember { mutableStateOf(false) }
var euicc by remember { mutableStateOf(false) }
var silent by remember { mutableStateOf(false) }
var reason by remember { mutableStateOf("") }
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(
text = stringResource(R.string.delete_work_profile),
style = typography.headlineLarge,
modifier = Modifier.padding(6.dp),color = colorScheme.error
)
Spacer(Modifier.padding(vertical = 5.dp))
CheckBoxItem(stringResource(R.string.wipe_external_storage), externalStorage, { externalStorage = it })
if(VERSION.SDK_INT >= 28) { CheckBoxItem(stringResource(R.string.wipe_euicc), euicc, { euicc = it }) }
if(VERSION.SDK_INT >= 29) { CheckBoxItem(stringResource(R.string.wipe_silently), silent, { silent = it }) }
AnimatedVisibility(!silent && VERSION.SDK_INT >= 28) {
OutlinedTextField(
value = reason, onValueChange = { reason = it },
label = { Text(stringResource(R.string.reason)) },
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
focusMgr.clearFocus()
warning = true
},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.delete))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
if(warning) {
LaunchedEffect(Unit) { silent = reason == "" }
AlertDialog(
title = {
Text(text = stringResource(R.string.warning), color = colorScheme.error)
},
text = {
Text(text = stringResource(R.string.wipe_work_profile_warning), color = colorScheme.error)
},
onDismissRequest = { warning = false },
confirmButton = {
TextButton(
onClick = {
var flag = 0
if(externalStorage) { flag += WIPE_EXTERNAL_STORAGE }
if(euicc && VERSION.SDK_INT >= 28) { flag += WIPE_EUICC }
if(silent && VERSION.SDK_INT >= 29) { flag += WIPE_SILENTLY }
if(VERSION.SDK_INT >= 28 && !silent) {
dpm.wipeData(flag, reason)
} else {
dpm.wipeData(flag)
}
},
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton(onClick = { warning = false }) {
Text(stringResource(R.string.cancel))
}
}
)
}
}

View File

@@ -36,6 +36,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.backToHomeStateFlow
import com.bintianqi.owndroid.ui.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -119,7 +120,7 @@ private fun Home(localNavCtrl:NavHostController,listScrollState:ScrollState) {
SubPageItem(R.string.enrollment_specific_id, "", R.drawable.id_card_fill0) { localNavCtrl.navigate("SpecificID") }
}
if(isDeviceOwner(dpm) || isProfileOwner(dpm)) {
SubPageItem(R.string.disable_account_management, "", R.drawable.account_circle_fill0) { localNavCtrl.navigate("NoManagementAccount") }
SubPageItem(R.string.disable_account_management, "", R.drawable.account_circle_fill0) { localNavCtrl.navigate("DisableAccountManagement") }
}
if(VERSION.SDK_INT >= 24 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) {
SubPageItem(R.string.device_owner_lock_screen_info, "", R.drawable.screen_lock_portrait_fill0) { localNavCtrl.navigate("LockScreenInfo") }
@@ -178,8 +179,8 @@ private fun DeviceAdmin() {
val context = LocalContext.current
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val receiver = ComponentName(context,Receiver::class.java)
val co = rememberCoroutineScope()
var showDeactivateButton by remember { mutableStateOf(dpm.isAdminActive(receiver)) }
var deactivateDialog by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.device_admin), style = typography.headlineLarge)
@@ -187,10 +188,7 @@ private fun DeviceAdmin() {
Spacer(Modifier.padding(vertical = 5.dp))
AnimatedVisibility(showDeactivateButton) {
Button(
onClick = {
dpm.removeActiveAdmin(receiver)
co.launch{ delay(400); showDeactivateButton = dpm.isAdminActive(receiver) }
},
onClick = { deactivateDialog = true },
enabled = !isProfileOwner(dpm) && !isDeviceOwner(dpm),
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
@@ -210,6 +208,35 @@ private fun DeviceAdmin() {
}
}
}
if(deactivateDialog) {
val co = rememberCoroutineScope()
AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
onDismissRequest = { deactivateDialog = false },
dismissButton = {
TextButton(
onClick = { deactivateDialog = false }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
dpm.removeActiveAdmin(receiver)
co.launch{
delay(300)
deactivateDialog = false
showDeactivateButton = dpm.isAdminActive(receiver)
backToHomeStateFlow.value = !dpm.isAdminActive(receiver)
}
}
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
@Composable
@@ -218,19 +245,16 @@ private fun ProfileOwner() {
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val receiver = ComponentName(context,Receiver::class.java)
var showDeactivateButton by remember { mutableStateOf(isProfileOwner(dpm)) }
var deactivateDialog by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.profile_owner), style = typography.headlineLarge)
Text(stringResource(if(isProfileOwner(dpm)) R.string.activated else R.string.deactivated), style = typography.titleLarge)
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT>=24) {
if(VERSION.SDK_INT >= 24) {
AnimatedVisibility(showDeactivateButton) {
val co = rememberCoroutineScope()
Button(
onClick = {
dpm.clearProfileOwner(receiver)
co.launch { delay(400); showDeactivateButton=isProfileOwner(dpm) }
},
onClick = { deactivateDialog = true },
enabled = !dpm.isManagedProfile(receiver),
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
@@ -247,14 +271,43 @@ private fun ProfileOwner() {
}
}
}
if(deactivateDialog && VERSION.SDK_INT >= 24) {
val co = rememberCoroutineScope()
AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
onDismissRequest = { deactivateDialog = false },
dismissButton = {
TextButton(
onClick = { deactivateDialog = false }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
dpm.clearProfileOwner(receiver)
co.launch{
delay(300)
deactivateDialog = false
showDeactivateButton = isProfileOwner(dpm)
backToHomeStateFlow.value = !isProfileOwner(dpm)
}
}
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
@Composable
private fun DeviceOwner() {
val context = LocalContext.current
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val co = rememberCoroutineScope()
var showDeactivateButton by remember { mutableStateOf(isDeviceOwner(dpm)) }
var deactivateDialog by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.device_owner), style = typography.headlineLarge)
@@ -262,10 +315,7 @@ private fun DeviceOwner() {
Spacer(Modifier.padding(vertical = 5.dp))
AnimatedVisibility(showDeactivateButton) {
Button(
onClick = {
dpm.clearDeviceOwnerApp(context.packageName)
co.launch{ delay(400); showDeactivateButton=isDeviceOwner(dpm) }
},
onClick = { deactivateDialog = true },
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(text = stringResource(R.string.deactivate))
@@ -280,6 +330,35 @@ private fun DeviceOwner() {
}
}
}
if(deactivateDialog) {
val co = rememberCoroutineScope()
AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
onDismissRequest = { deactivateDialog = false },
dismissButton = {
TextButton(
onClick = { deactivateDialog = false }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
dpm.clearDeviceOwnerApp(context.packageName)
co.launch{
delay(300)
deactivateDialog = false
showDeactivateButton = isDeviceOwner(dpm)
backToHomeStateFlow.value = !isDeviceOwner(dpm)
}
}
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
@Composable

View File

@@ -1,6 +1,11 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.ActivityOptions
import android.app.AlertDialog
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY
import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback
@@ -8,7 +13,6 @@ import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_ST
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO
@@ -36,7 +40,6 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.os.Build.VERSION
import android.os.UserManager
import android.util.Log
@@ -77,6 +80,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -87,6 +91,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.core.app.NotificationCompat
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -94,9 +99,12 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.StopLockTaskModeReceiver
import com.bintianqi.owndroid.fileUriFlow
import com.bintianqi.owndroid.getFile
import com.bintianqi.owndroid.prepareForNotification
import com.bintianqi.owndroid.toText
import com.bintianqi.owndroid.toggle
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.Information
@@ -105,9 +113,11 @@ import com.bintianqi.owndroid.ui.SubPageItem
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.ui.TopBar
import com.bintianqi.owndroid.uriToStream
import kotlinx.coroutines.launch
import java.util.Date
import java.util.TimeZone
import java.util.concurrent.Executors
import kotlin.math.pow
@Composable
fun SystemManage(navCtrl:NavHostController) {
@@ -144,7 +154,7 @@ fun SystemManage(navCtrl:NavHostController) {
composable(route = "PermissionPolicy") { PermissionPolicy() }
composable(route = "MTEPolicy") { MTEPolicy() }
composable(route = "NearbyStreamingPolicy") { NearbyStreamingPolicy() }
composable(route = "LockTaskFeatures") { LockTaskFeatures() }
composable(route = "LockTaskMode") { LockTaskMode() }
composable(route = "CaCert") { CaCert() }
composable(route = "SecurityLogs") { SecurityLogs() }
composable(route = "SystemUpdatePolicy") { SysUpdatePolicy() }
@@ -166,6 +176,8 @@ private fun Home(navCtrl: NavHostController, scrollState: ScrollState, rebootDia
val context = LocalContext.current
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val receiver = ComponentName(context, Receiver::class.java)
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
val dangerousFeatures = sharedPref.getBoolean("dangerous_features", false)
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
Text(
text = stringResource(R.string.system_manage),
@@ -196,7 +208,7 @@ private fun Home(navCtrl: NavHostController, scrollState: ScrollState, rebootDia
SubPageItem(R.string.nearby_streaming_policy, "", R.drawable.share_fill0) { navCtrl.navigate("NearbyStreamingPolicy") }
}
if(VERSION.SDK_INT >= 28 && isDeviceOwner(dpm)) {
SubPageItem(R.string.lock_task_feature, "", R.drawable.lock_fill0) { navCtrl.navigate("LockTaskFeatures") }
SubPageItem(R.string.lock_task_mode, "", R.drawable.lock_fill0) { navCtrl.navigate("LockTaskMode") }
}
if(isDeviceOwner(dpm) || isProfileOwner(dpm)) {
SubPageItem(R.string.ca_cert, "", R.drawable.license_fill0) { navCtrl.navigate("CaCert") }
@@ -213,8 +225,8 @@ private fun Home(navCtrl: NavHostController, scrollState: ScrollState, rebootDia
if(VERSION.SDK_INT >= 30 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) {
SubPageItem(R.string.frp_policy, "", R.drawable.device_reset_fill0) { navCtrl.navigate("FRP") }
}
if(dpm.isAdminActive(receiver)) {
SubPageItem(R.string.wipe_data, "", R.drawable.warning_fill0) { navCtrl.navigate("WipeData") }
if(dangerousFeatures && dpm.isAdminActive(receiver) && !(VERSION.SDK_INT >= 24 && isProfileOwner(dpm) && dpm.isManagedProfile(receiver))) {
SubPageItem(R.string.wipe_data, "", R.drawable.device_reset_fill0) { navCtrl.navigate("WipeData") }
}
Spacer(Modifier.padding(vertical = 30.dp))
LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") }
@@ -642,41 +654,30 @@ private fun NearbyStreamingPolicy() {
@SuppressLint("NewApi")
@Composable
private fun LockTaskFeatures() {
private fun LockTaskMode() {
val context = LocalContext.current
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val receiver = ComponentName(context,Receiver::class.java)
val focusMgr = LocalFocusManager.current
val coroutine = rememberCoroutineScope()
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
val lockTaskPolicyList = mutableListOf(
LOCK_TASK_FEATURE_NONE,
LOCK_TASK_FEATURE_SYSTEM_INFO,
LOCK_TASK_FEATURE_NOTIFICATIONS,
LOCK_TASK_FEATURE_HOME,
LOCK_TASK_FEATURE_OVERVIEW,
LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
LOCK_TASK_FEATURE_KEYGUARD
)
var sysInfo by remember { mutableStateOf(false) }
var notifications by remember { mutableStateOf(false) }
var home by remember { mutableStateOf(false) }
var overview by remember { mutableStateOf(false) }
var globalAction by remember { mutableStateOf(false) }
var keyGuard by remember { mutableStateOf(false) }
var blockAct by remember { mutableStateOf(false) }
if(VERSION.SDK_INT >= 30) { lockTaskPolicyList.add(LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK) }
var inited by remember { mutableStateOf(false) }
val lockTaskFeatures = remember { mutableStateListOf<Int>() }
var custom by remember { mutableStateOf(false) }
val refreshFeature = {
var calculate = dpm.getLockTaskFeatures(receiver)
if(calculate!=0) {
if(VERSION.SDK_INT >= 30 && calculate-lockTaskPolicyList[7] >= 0) { blockAct= true; calculate -= lockTaskPolicyList[7] }
if(calculate-lockTaskPolicyList[6] >= 0) { keyGuard = true; calculate -= lockTaskPolicyList[6] }
if(calculate-lockTaskPolicyList[5] >= 0) { globalAction= true; calculate -= lockTaskPolicyList[5] }
if(calculate-lockTaskPolicyList[4] >= 0) { overview= true; calculate -= lockTaskPolicyList[4] }
if(calculate-lockTaskPolicyList[3] >= 0) { home= true; calculate -= lockTaskPolicyList[3] }
if(calculate-lockTaskPolicyList[2] >= 0) { notifications= true; calculate -= lockTaskPolicyList[2] }
if(calculate-lockTaskPolicyList[1] >= 0) { sysInfo= true; calculate -= lockTaskPolicyList[1] }
lockTaskFeatures.clear()
if(calculate != 0) {
var sq = 10
while(sq >= 1) {
val current = (2).toDouble().pow(sq.toDouble()).toInt()
if(calculate - current >= 0) {
lockTaskFeatures += current
calculate -= current
}
sq--
}
if(calculate - 1 >= 0) { lockTaskFeatures += 1 }
custom = true
}else{
custom = false
}
@@ -684,57 +685,87 @@ private fun LockTaskFeatures() {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.lock_task_feature), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
if(!inited) { refreshFeature(); custom=dpm.getLockTaskFeatures(receiver)!=0; inited= true }
LaunchedEffect(Unit) { refreshFeature() }
RadioButtonItem(stringResource(R.string.disable_all), !custom, { custom = false })
RadioButtonItem(stringResource(R.string.custom), custom, { custom = true })
AnimatedVisibility(custom) {
Column {
CheckBoxItem(stringResource(R.string.ltf_sys_info), sysInfo, { sysInfo = it })
CheckBoxItem(stringResource(R.string.ltf_notifications), notifications, { notifications = it })
CheckBoxItem(stringResource(R.string.ltf_home), home, { home = it })
CheckBoxItem(stringResource(R.string.ltf_overview), overview, { overview = it })
CheckBoxItem(stringResource(R.string.ltf_global_actions), globalAction, { globalAction = it })
CheckBoxItem(stringResource(R.string.ltf_keyguard), keyGuard, { keyGuard = it })
if(VERSION.SDK_INT >= 30) { CheckBoxItem(stringResource(R.string.ltf_block_activity_start_in_task), blockAct, { blockAct = it }) }
CheckBoxItem(
stringResource(R.string.ltf_sys_info),
LOCK_TASK_FEATURE_SYSTEM_INFO in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_SYSTEM_INFO) }
)
CheckBoxItem(
stringResource(R.string.ltf_notifications),
LOCK_TASK_FEATURE_NOTIFICATIONS in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_NOTIFICATIONS) }
)
CheckBoxItem(
stringResource(R.string.ltf_home),
LOCK_TASK_FEATURE_HOME in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_HOME) }
)
CheckBoxItem(
stringResource(R.string.ltf_overview),
LOCK_TASK_FEATURE_OVERVIEW in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_OVERVIEW) }
)
CheckBoxItem(
stringResource(R.string.ltf_global_actions),
LOCK_TASK_FEATURE_GLOBAL_ACTIONS in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_GLOBAL_ACTIONS) }
)
CheckBoxItem(
stringResource(R.string.ltf_keyguard),
LOCK_TASK_FEATURE_KEYGUARD in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_KEYGUARD) }
)
if(VERSION.SDK_INT >= 30) {
CheckBoxItem(
stringResource(R.string.ltf_block_activity_start_in_task),
LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK) }
)
}
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
var result = lockTaskPolicyList[0]
var result = 0
if(custom) {
if(blockAct&&VERSION.SDK_INT >= 30) { result += lockTaskPolicyList[7] }
if(keyGuard) { result += lockTaskPolicyList[6] }
if(globalAction) { result += lockTaskPolicyList[5] }
if(overview) { result += lockTaskPolicyList[4] }
if(home) { result += lockTaskPolicyList[3] }
if(notifications) { result += lockTaskPolicyList[2] }
if(sysInfo) { result += lockTaskPolicyList[1] }
lockTaskFeatures.forEach { result += it }
}
try {
dpm.setLockTaskFeatures(receiver,result)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
} catch (e: IllegalArgumentException) {
AlertDialog.Builder(context)
.setTitle("Error")
.setMessage(e.message)
.setPositiveButton(R.string.confirm) { dialog, _ -> dialog.dismiss() }
.show()
}
dpm.setLockTaskFeatures(receiver,result)
refreshFeature()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
}
) {
Text(stringResource(R.string.apply))
}
val lockTaskPackages = remember { mutableStateListOf<String>() }
var inputLockTaskPkg by remember { mutableStateOf("") }
LaunchedEffect(Unit) { lockTaskPackages.addAll(dpm.getLockTaskPackages(receiver)) }
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.lock_task_packages), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
val whitelist = dpm.getLockTaskPackages(receiver).toMutableList()
var listText by remember { mutableStateOf("") }
var inputPkg by remember { mutableStateOf("") }
val refreshWhitelist = {
inputPkg = ""
listText = ""
listText = whitelist.toText()
}
LaunchedEffect(Unit) { refreshWhitelist() }
Text(text = stringResource(R.string.whitelist_app), style = typography.titleLarge)
SelectionContainer(modifier = Modifier.animateContentSize()) {
Text(text = if(listText == "") stringResource(R.string.none) else listText)
var listText = ""
lockTaskPackages.forEach { listText += "\n" + it }
Text(text = stringResource(R.string.app_list_is) + if(listText == "") stringResource(R.string.none) else listText)
}
OutlinedTextField(
value = inputPkg,
onValueChange = { inputPkg = it },
value = inputLockTaskPkg,
onValueChange = { inputLockTaskPkg = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
@@ -742,34 +773,64 @@ private fun LockTaskFeatures() {
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
focusMgr.clearFocus()
whitelist.add(inputPkg)
dpm.setLockTaskPackages(receiver,whitelist.toTypedArray())
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
refreshWhitelist()
},
onClick = { lockTaskPackages.add(inputLockTaskPkg) },
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = {
focusMgr.clearFocus()
if(inputPkg in whitelist) {
whitelist.remove(inputPkg)
dpm.setLockTaskPackages(receiver,whitelist.toTypedArray())
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, R.string.not_exist, Toast.LENGTH_SHORT).show()
}
refreshWhitelist()
},
onClick = { lockTaskPackages.remove(inputLockTaskPkg) },
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
dpm.setLockTaskPackages(receiver, lockTaskPackages.toTypedArray())
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
}
) {
Text(stringResource(R.string.apply))
}
var startLockTaskApp by remember { mutableStateOf("") }
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.start_lock_task_mode), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = startLockTaskApp,
onValueChange = { startLockTaskApp = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
if(!dpm.getLockTaskPackages(receiver).contains(startLockTaskApp)) {
Toast.makeText(context, R.string.app_not_allowed, Toast.LENGTH_SHORT).show()
return@Button
}
val options = ActivityOptions.makeBasic().setLockTaskEnabled(true)
val packageManager = context.packageManager
val launchIntent = packageManager.getLaunchIntentForPackage(startLockTaskApp)
if (launchIntent != null) {
coroutine.launch {
prepareForNotification(context) {
sendStopLockTaskNotification(context)
context.startActivity(launchIntent, options.toBundle())
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
}
}
} else {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
}
) {
Text(stringResource(R.string.start))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@@ -1004,6 +1065,7 @@ fun FactoryResetProtection() {
}
}
@SuppressLint("NewApi")
@Composable
private fun WipeData() {
val context = LocalContext.current
@@ -1011,14 +1073,14 @@ private fun WipeData() {
val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val receiver = ComponentName(context,Receiver::class.java)
val focusMgr = LocalFocusManager.current
var warning by remember { mutableStateOf(false) }
var wipeDevice by remember { mutableStateOf(false) }
var externalStorage by remember { mutableStateOf(false) }
var protectionData by remember { mutableStateOf(false) }
var euicc by remember { mutableStateOf(false) }
var silent by remember { mutableStateOf(false) }
var reason by remember { mutableStateOf("") }
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
var flag by remember { mutableIntStateOf(0) }
var confirmed by remember { mutableStateOf(false) }
var externalStorage by remember { mutableStateOf(false) }
var protectionData by remember { mutableStateOf(false) }
var euicc by remember { mutableStateOf(false) }
var silent by remember { mutableStateOf(false) }
var reason by remember { mutableStateOf("") }
Spacer(Modifier.padding(vertical = 10.dp))
Text(
text = stringResource(R.string.wipe_data),
@@ -1028,76 +1090,91 @@ private fun WipeData() {
Spacer(Modifier.padding(vertical = 5.dp))
CheckBoxItem(
stringResource(R.string.wipe_external_storage),
externalStorage, { externalStorage = it; confirmed = false }
externalStorage, { externalStorage = it }
)
if(VERSION.SDK_INT >= 22 && isDeviceOwner(dpm)) {
CheckBoxItem(stringResource(R.string.wipe_reset_protection_data),
protectionData, { protectionData = it; confirmed = false}
protectionData, { protectionData = it }
)
}
if(VERSION.SDK_INT >= 28) { CheckBoxItem(stringResource(R.string.wipe_euicc), euicc, { euicc = it; confirmed = false }) }
if(VERSION.SDK_INT >= 29) { CheckBoxItem(stringResource(R.string.wipe_silently), silent, { silent = it; confirmed = false }) }
if(VERSION.SDK_INT >= 28) { CheckBoxItem(stringResource(R.string.wipe_euicc), euicc, { euicc = it }) }
if(VERSION.SDK_INT >= 29) { CheckBoxItem(stringResource(R.string.wipe_silently), silent, { silent = it }) }
AnimatedVisibility(!silent && VERSION.SDK_INT >= 28) {
OutlinedTextField(
value = reason, onValueChange = { reason = it },
label = { Text(stringResource(R.string.reason)) },
enabled = !confirmed,
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
focusMgr.clearFocus()
flag = 0
if(externalStorage) { flag += WIPE_EXTERNAL_STORAGE }
if(protectionData && VERSION.SDK_INT >= 22) { flag += WIPE_RESET_PROTECTION_DATA }
if(euicc && VERSION.SDK_INT >= 28) { flag += WIPE_EUICC }
if(reason == "") { silent = true }
if(silent && VERSION.SDK_INT >= 29) { flag += WIPE_SILENTLY }
confirmed = !confirmed
},
colors = ButtonDefaults.buttonColors(
containerColor = if(confirmed) colorScheme.primary else colorScheme.error ,
contentColor = if(confirmed) colorScheme.onPrimary else colorScheme.onError
),
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(if(confirmed) R.string.cancel else R.string.confirm))
}
Button(
onClick = {
if(VERSION.SDK_INT >= 28 && reason != "") {
dpm.wipeData(flag, reason)
}else{
dpm.wipeData(flag)
}
},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
enabled = confirmed && (VERSION.SDK_INT < 34 || (VERSION.SDK_INT >= 34 && !userManager.isSystemUser)),
modifier = Modifier.fillMaxWidth()
) {
Text("WipeData")
if(VERSION.SDK_INT < 34 || (VERSION.SDK_INT >= 34 && !userManager.isSystemUser)) {
Button(
onClick = {
focusMgr.clearFocus()
wipeDevice = false
warning = true
},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
modifier = Modifier.fillMaxWidth()
) {
Text("WipeData")
}
}
if (VERSION.SDK_INT >= 34 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) {
Button(
onClick = { dpm.wipeDevice(flag) },
onClick = {
focusMgr.clearFocus()
wipeDevice = true
warning = true
},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
enabled = confirmed,
modifier = Modifier.fillMaxWidth()
) {
Text("WipeDevice")
}
}
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 24 && isProfileOwner(dpm) && dpm.isManagedProfile(receiver)) {
Information{ Text(text = stringResource(R.string.will_delete_work_profile)) }
}
if(VERSION.SDK_INT >= 34 && Binder.getCallingUid()/100000 == 0) {
Information{ Text(text = stringResource(R.string.api34_or_above_wipedata_cannot_in_system_user)) }
}
Spacer(Modifier.padding(vertical = 30.dp))
}
if(warning) {
LaunchedEffect(Unit) { silent = reason == "" }
AlertDialog(
title = {
Text(text = stringResource(R.string.warning), color = colorScheme.error)
},
text = {
Text(text = stringResource(R.string.wipe_data_warning), color = colorScheme.error)
},
onDismissRequest = { warning = false },
confirmButton = {
TextButton(
onClick = {
var flag = 0
if(externalStorage) { flag += WIPE_EXTERNAL_STORAGE }
if(protectionData && VERSION.SDK_INT >= 22) { flag += WIPE_RESET_PROTECTION_DATA }
if(euicc && VERSION.SDK_INT >= 28) { flag += WIPE_EUICC }
if(silent && VERSION.SDK_INT >= 29) { flag += WIPE_SILENTLY }
if(wipeDevice) {
dpm.wipeDevice(flag)
} else {
if(VERSION.SDK_INT >= 28 && reason != "") {
dpm.wipeData(flag, reason)
} else {
dpm.wipeData(flag)
}
}
},
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton(onClick = { warning = false }) {
Text(stringResource(R.string.cancel))
}
}
)
}
}
@Composable
@@ -1252,3 +1329,23 @@ fun InstallSystemUpdate() {
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
private fun sendStopLockTaskNotification(context: Context) {
val nm = context.getSystemService(ComponentActivity.NOTIFICATION_SERVICE) as NotificationManager
if (VERSION.SDK_INT >= 26) {
val channel = NotificationChannel("LockTaskMode", context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH).apply {
description = "Notification channel for stop lock task mode"
setShowBadge(false)
}
nm.createNotificationChannel(channel)
}
val intent = Intent(context, StopLockTaskModeReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(context, "LockTaskMode")
.setContentTitle(context.getText(R.string.lock_task_mode))
.setSmallIcon(R.drawable.lock_fill0)
.addAction(NotificationCompat.Action.Builder(R.drawable.lock_fill0, context.getText(R.string.stop), pendingIntent).build())
.setPriority(NotificationCompat.PRIORITY_HIGH)
nm.notify(1, builder.build())
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2022 fishbone
*
* This code is licensed under MIT license (see LICENSE file for details)
*/
package com.github.fishb1.apkinfo
import com.android.apksig.internal.apk.AndroidBinXmlParser
import com.android.apksig.internal.apk.AndroidBinXmlParser.XmlParserException
import java.io.InputStream
import java.nio.ByteBuffer
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
data class ApkInfo(
val compileSdkVersion: Int = 0,
val compileSdkVersionCodename: String = "",
val installLocation: String = "",
val packageName: String = "",
val platformBuildVersionCode: Int = 0,
val platformBuildVersionName: String = "",
val versionCode: Int = 0,
val versionName: String = "",
) {
companion object {
private val EMPTY = ApkInfo()
private const val MANIFEST_FILE_NAME = "AndroidManifest.xml"
fun fromInputStream(stream: InputStream): ApkInfo {
ZipInputStream(stream).use { zip ->
var entry: ZipEntry?
do {
entry = zip.nextEntry
if (entry?.name == MANIFEST_FILE_NAME) {
val data = ByteBuffer.wrap(zip.readBytes())
return try {
val parser = AndroidBinXmlParser(data)
ManifestUtils.readApkInfo(parser)
} catch (e: XmlParserException) {
EMPTY
}
}
} while (entry != null)
}
return EMPTY
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2022 fishbone
*
* This code is licensed under MIT license (see LICENSE file for details)
*/
package com.github.fishb1.apkinfo
internal class ApkInfoBuilder {
private var compileSdkVersion: Int = 0
private var compileSdkVersionCodename: String = ""
private var installLocation: String = ""
private var packageName: String = ""
private var platformBuildVersionCode: Int = 0
private var platformBuildVersionName: String = ""
private var versionCode: Int = 0
private var versionName: String = ""
fun compileSdkVersion(value: Int) = apply {
compileSdkVersion = value
}
fun compileSdkVersionCodename(value: String) = apply {
compileSdkVersionCodename = value
}
fun installLocation(value: String) = apply {
installLocation = value
}
fun packageName(value: String) = apply {
packageName = value
}
fun platformBuildVersionCode(value: Int) = apply {
platformBuildVersionCode = value
}
fun platformBuildVersionName(value: String) = apply {
platformBuildVersionName = value
}
fun versionCode(value: Int) = apply {
versionCode = value
}
fun versionName(value: String) = apply {
versionName = value
}
fun build(): ApkInfo {
return ApkInfo(
compileSdkVersion = compileSdkVersion,
compileSdkVersionCodename =compileSdkVersionCodename,
installLocation = installLocation,
packageName = packageName,
platformBuildVersionCode = platformBuildVersionCode,
platformBuildVersionName = platformBuildVersionName,
versionCode = versionCode,
versionName = versionName,
)
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2022 fishbone
*
* This code is licensed under MIT license (see LICENSE file for details)
*/
package com.github.fishb1.apkinfo
import com.android.apksig.internal.apk.AndroidBinXmlParser
internal object ManifestUtils {
private const val TAG_MANIFEST = "manifest"
private const val ATTR_COMPILE_SDK_VERSION = "compileSdkVersion"
private const val ATTR_COMPILE_SDK_VERSION_CODENAME = "compileSdkVersionCodename"
private const val ATTR_INSTALL_LOCATION = "installLocation"
private const val ATTR_PACKAGE = "package"
private const val ATTR_PLATFORM_BUILD_VERSION_CODE = "platformBuildVersionCode"
private const val ATTR_PLATFORM_BUILD_VERSION_NAME = "platformBuildVersionName"
private const val ATTR_VERSION_CODE = "versionCode"
private const val ATTR_VERSION_NAME = "versionName"
fun readApkInfo(parser: AndroidBinXmlParser): ApkInfo {
val builder = ApkInfoBuilder()
var eventType = parser.eventType
while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) {
if (eventType == AndroidBinXmlParser.EVENT_START_ELEMENT && parser.name == TAG_MANIFEST) {
for (i in 0 until parser.attributeCount) {
when (parser.getAttributeName(i)) {
ATTR_COMPILE_SDK_VERSION ->
builder.compileSdkVersion(parser.getAttributeIntValue(i))
ATTR_COMPILE_SDK_VERSION_CODENAME ->
builder.compileSdkVersionCodename(parser.getAttributeStringValue(i))
ATTR_INSTALL_LOCATION ->
builder.installLocation(parser.getAttributeStringValue(i))
ATTR_PACKAGE ->
builder.packageName(parser.getAttributeStringValue(i))
ATTR_PLATFORM_BUILD_VERSION_CODE ->
builder.platformBuildVersionCode(parser.getAttributeIntValue(i))
ATTR_PLATFORM_BUILD_VERSION_NAME ->
builder.platformBuildVersionName(parser.getAttributeStringValue(i))
ATTR_VERSION_CODE ->
builder.versionCode(parser.getAttributeIntValue(i))
ATTR_VERSION_NAME ->
builder.versionName(parser.getAttributeStringValue(i))
}
}
}
eventType = parser.next()
}
return builder.build()
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M200,880q-33,0 -56.5,-23.5T120,800v-560q0,-33 23.5,-56.5T200,160h40v-80h80v80h320v-80h80v80h40q33,0 56.5,23.5T840,240v560q0,33 -23.5,56.5T760,880L200,880ZM200,800h560v-400L200,400v400ZM200,320h560v-80L200,240v80ZM200,320v-80,80ZM480,560q-17,0 -28.5,-11.5T440,520q0,-17 11.5,-28.5T480,480q17,0 28.5,11.5T520,520q0,17 -11.5,28.5T480,560ZM320,560q-17,0 -28.5,-11.5T280,520q0,-17 11.5,-28.5T320,480q17,0 28.5,11.5T360,520q0,17 -11.5,28.5T320,560ZM640,560q-17,0 -28.5,-11.5T600,520q0,-17 11.5,-28.5T640,480q17,0 28.5,11.5T680,520q0,17 -11.5,28.5T640,560ZM480,720q-17,0 -28.5,-11.5T440,680q0,-17 11.5,-28.5T480,640q17,0 28.5,11.5T520,680q0,17 -11.5,28.5T480,720ZM320,720q-17,0 -28.5,-11.5T280,680q0,-17 11.5,-28.5T320,640q17,0 28.5,11.5T360,680q0,17 -11.5,28.5T320,720ZM640,720q-17,0 -28.5,-11.5T600,680q0,-17 11.5,-28.5T640,640q17,0 28.5,11.5T680,680q0,17 -11.5,28.5T640,720Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,160v-80h400v80L480,160ZM480,320v-80h400v80L480,320ZM480,480v-80h400v80L480,480ZM758,880q-125,0 -247,-54.5T289,671Q189,571 134.5,449T80,202q0,-18 12,-30t30,-12h162q14,0 25,9.5t13,22.5l26,140q2,16 -1,27t-11,19l-97,98q20,37 47.5,71.5T347,614q31,31 65,57.5t72,48.5l94,-94q9,-9 23.5,-13.5T630,610l138,28q14,4 23,14.5t9,23.5v162q0,18 -12,30t-30,12ZM201,400l66,-66 -17,-94h-89q5,41 14,81t26,79ZM559,758q39,17 79.5,27t81.5,13v-88l-94,-19 -67,67ZM201,400ZM559,758Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M160,920v-80h640v80L160,920ZM160,120v-80h640v80L160,120ZM480,520q50,0 85,-35t35,-85q0,-50 -35,-85t-85,-35q-50,0 -85,35t-35,85q0,50 35,85t85,35ZM160,800q-33,0 -56.5,-23.5T80,720v-480q0,-33 23.5,-56.5T160,160h640q33,0 56.5,23.5T880,240v480q0,33 -23.5,56.5T800,800L160,800ZM230,720q45,-56 109,-88t141,-32q77,0 141,32t109,88h70v-480L160,240v480h70ZM348,720h264q-29,-20 -62.5,-30T480,680q-36,0 -69.5,10T348,720ZM480,440q-17,0 -28.5,-11.5T440,400q0,-17 11.5,-28.5T480,360q17,0 28.5,11.5T520,400q0,17 -11.5,28.5T480,440ZM480,480Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M160,800q-33,0 -56.5,-23.5T80,720v-480q0,-33 23.5,-56.5T160,160h240l80,80h320q33,0 56.5,23.5T880,320v400q0,33 -23.5,56.5T800,800L160,800ZM160,720h640v-400L447,320l-80,-80L160,240v480ZM160,720v-480,480Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M200,840q-33,0 -56.5,-23.5T120,760v-560q0,-33 23.5,-56.5T200,120h560q33,0 56.5,23.5T840,200v560q0,33 -23.5,56.5T760,840L200,840ZM200,760h560v-560L200,200v560ZM240,680h480L570,480 450,640l-90,-120 -120,160ZM200,760v-560,560Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m160,160 l80,160h120l-80,-160h80l80,160h120l-80,-160h80l80,160h120l-80,-160h120q33,0 56.5,23.5T880,240v480q0,33 -23.5,56.5T800,800L160,800q-33,0 -56.5,-23.5T80,720v-480q0,-33 23.5,-56.5T160,160ZM160,400v320h640v-320L160,400ZM160,400v320,-320Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M400,840q-66,0 -113,-47t-47,-113q0,-66 47,-113t113,-47q23,0 42.5,5.5T480,542v-422h240v160L560,280v400q0,66 -47,113t-113,47Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M197,763q-54,-55 -85.5,-127.5T80,480q0,-84 31.5,-156.5T197,197l57,57q-44,44 -69,102t-25,124q0,67 25,125t69,101l-57,57ZM310,650q-32,-33 -51,-76.5T240,480q0,-51 19,-94.5t51,-75.5l57,57q-22,22 -34.5,51T320,480q0,33 12.5,62t34.5,51l-57,57ZM480,560q-33,0 -56.5,-23.5T400,480q0,-33 23.5,-56.5T480,400q33,0 56.5,23.5T560,480q0,33 -23.5,56.5T480,560ZM650,650 L593,593q22,-22 34.5,-51t12.5,-62q0,-33 -12.5,-62T593,367l57,-57q32,32 51,75.5t19,94.5q0,50 -19,93.5T650,650ZM763,763 L706,706q44,-44 69,-102t25,-124q0,-67 -25,-125t-69,-101l57,-57q54,54 85.5,126.5T880,480q0,83 -31.5,155.5T763,763Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -50,10 +50,12 @@
<string name="io_exception">G/Ç Hatası</string>
<string name="current_status_is">Mevcut Durum: </string>
<string name="start">Başlat</string>
<string name="unknown_error">Bilinmeyen Hata</string>
<string name="stop">Stop</string> <!--TODO-->
<string name="allow_all">Tümünü İzin Ver</string>
<string name="use_policy">Politika Kullan</string>
<string name="account">Hesap</string>
<string name="warning">Warning</string> <!--TODO-->
<string name="delete">Delete</string> <!--TODO-->
<!--Permissions-->
<string name="click_to_activate">Etkinleştirmek İçin Tıklayın</string>
@@ -151,7 +153,11 @@
<string name="nearby_streaming_policy">Yakındaki akış politikası</string>
<string name="nearby_notification_streaming">Yakındaki bildirim akış politikası</string>
<string name="enable_if_secure_enough">Yalnızca aynı yönetilen hesap</string>
<string name="lock_task_mode">Lock task mode</string> <!--TODO-->
<string name="lock_task_feature">Görev kilitleme özelliği</string>
<string name="lock_task_packages">Lock task packages</string> <!--TODO-->
<string name="start_lock_task_mode">Start lock task mode</string> <!--TODO-->
<string name="app_not_allowed">App not allowed</string> <!--TODO-->
<string name="disable_all">Hepsini devre dışı bırak</string>
<!--ltf: lock task feature-->
@@ -162,7 +168,6 @@
<string name="ltf_global_actions">Küresel eylemlere izin ver</string>
<string name="ltf_keyguard">Ekran kilidine izin ver</string>
<string name="ltf_block_activity_start_in_task">Görevde etkinlik başlatmayı engelle</string>
<string name="whitelist_app">Beyaz listeye alınan uygulama</string>
<string name="ca_cert">CA sertifikası</string>
<string name="please_select_ca_cert">Lütfen bir sertifika seçin</string>
<string name="cacert_installed">Yüklenen sertifika: %1$s</string>
@@ -175,8 +180,8 @@
<string name="wipe_reset_protection_data">Korumalı verileri sil</string>
<string name="wipe_euicc">eUICC (eSIM) sil</string>
<string name="wipe_silently">Sessizce sil</string>
<string name="will_delete_work_profile">Çalışma profili silinecek</string>
<string name="api34_or_above_wipedata_cannot_in_system_user">System user\'da WipeData kullanamazsınız</string>
<string name="wipe_data_warning">All data on your device will be ERASED</string> <!--TODO-->
<string name="wipe_work_profile_warning">Your work profile will be DELETED</string> <!--TODO-->
<string name="encrypt_status_is">Şifreleme durumu: </string>
<string name="frp_policy">FRP politikası</string>
<string name="factory_reset_protection_policy">Fabrika ayarlarına sıfırlama koruma politikası</string>
@@ -215,7 +220,6 @@
<string name="ssid_list_is">SSID listesi: </string>
<string name="cannot_be_empty">Boş olamaz</string>
<string name="already_exist">Zaten mevcut</string>
<string name="please_select_a_policy">Lütfen bir politika seçin</string>
<string name="private_dns">Özel DNS</string>
<string name="dns_provide_hostname">Ana bilgisayar adı sağlayın</string>
<string name="host_not_serving_dns_tls">Ana bilgisayar hizmet vermiyor</string>
@@ -272,6 +276,7 @@
<string name="org_id">Kuruluş Kimliği</string>
<string name="length_6_to_64">Uzunluk 6 ile 64 karakter arasında olmalıdır</string>
<string name="get_specific_id_after_set_org_id">Bunu ayarladıktan sonra cihaz spesifik Kimlik alabilirsiniz. </string>
<string name="delete_work_profile">Delete work profile</string> <!--TODO-->
<!--AppManager-->
<string name="app_manager">Uygulama yöneticisi</string>
@@ -286,6 +291,9 @@
<string name="hide">Gizle</string>
<string name="isapphidden_desc">Mevcut olmayan uygulamalar gizlidir</string>
<string name="always_on_vpn">Her zaman açık VPN</string>
<string name="enable_lockdown">Enable lockdown</string> <!--TODO-->
<string name="current_app_is">Current app: </string> <!--TODO-->
<string name="clear_current_config">Clear current config</string> <!--TODO-->
<string name="permission">İzin</string>
<string name="scope_is_work_profile">Kapsam: iş profili</string>
<string name="app_info">Uygulama bilgisi</string>
@@ -520,6 +528,7 @@
<!--Settings&About-->
<string name="setting">Ayarlar</string>
<string name="show_dangerous_features">Show dangerous features</string> <!--TODO-->
<string name="material_you_color">Material You rengi</string>
<string name="dynamic_color_desc">Android 12+</string>
<string name="about">Hakkında</string>
@@ -545,6 +554,9 @@
<string name="clear_storage">Depolamayı temizle</string>
<string name="clear_storage_success">Depolama başarıyla temizlendi\nUygulama kapanacak</string>
<string name="automation_api">Automation API</string> <!--TODO-->
<string name="automation_debug">Debug mode</string> <!--TODO-->
<!--AndroidPermission-->
<string name="permission_READ_EXTERNAL_STORAGE">Harici depolamayı oku</string>
<string name="permission_WRITE_EXTERNAL_STORAGE">Harici depolamaya yaz</string>
@@ -572,4 +584,7 @@
<string name="permission_ACTIVITY_RECOGNITION">Aktivite tanıma</string>
<string name="permission_POST_NOTIFICATIONS">Bildirim gönder</string>
<string name="version_name">Version name</string> <!--TODO-->
<string name="version_code">Version code</string> <!--TODO-->
<string name="parsing_apk_info" tools:ignore="TypographyEllipsis">Parsing APK info...</string>
</resources>

View File

@@ -47,10 +47,12 @@
<string name="io_exception">IO异常</string>
<string name="current_status_is">当前状态:</string>
<string name="start">开始</string>
<string name="unknown_error">未知错误</string>
<string name="stop">停止</string>
<string name="allow_all">允许全部</string>
<string name="use_policy">使用策略</string>
<string name="account">账户</string>
<string name="warning">警告</string>
<string name="delete">删除</string>
<!--Permissions-->
<string name="click_to_activate">点击以激活</string>
@@ -146,7 +148,11 @@
<string name="nearby_streaming_policy">附近流式传输策略</string>
<string name="nearby_notification_streaming">附近通知传输</string>
<string name="enable_if_secure_enough">在足够安全时启用</string>
<string name="lock_task_mode">锁定任务模式</string>
<string name="lock_task_feature">锁定任务功能</string>
<string name="lock_task_packages">锁定任务应用</string>
<string name="start_lock_task_mode">启动锁定任务模式</string>
<string name="app_not_allowed">应用未被允许</string>
<string name="disable_all">禁用全部</string>
<string name="ltf_sys_info">允许状态栏信息</string>
<string name="ltf_notifications">允许通知</string>
@@ -155,7 +161,6 @@
<string name="ltf_global_actions">允许全局行为(比如长按电源键对话框)</string>
<string name="ltf_keyguard">允许锁屏</string>
<string name="ltf_block_activity_start_in_task">阻止启动未允许的应用</string>
<string name="whitelist_app">白名单应用</string>
<string name="package_name">包名</string>
<string name="not_exist">不存在</string>
<string name="ca_cert">Ca证书</string>
@@ -170,8 +175,8 @@
<string name="wipe_reset_protection_data">清除受保护的数据</string>
<string name="wipe_euicc">清除eUICC(eSIM)</string>
<string name="wipe_silently">静默清除</string>
<string name="will_delete_work_profile">将会删除工作资料</string>
<string name="api34_or_above_wipedata_cannot_in_system_user">API34或以上将不能在系统用户中使用WipeData</string>
<string name="wipe_data_warning">你的设备上的所有数据将会被清除</string>
<string name="wipe_work_profile_warning">你的工作资料将会被删除</string>
<string name="encrypt_status_is">加密状态:</string>
<string name="frp_policy">FRP策略</string>
<string name="factory_reset_protection_policy">恢复出厂设置保护策略</string>
@@ -210,7 +215,6 @@
<string name="ssid_list_is">SSID列表</string>
<string name="cannot_be_empty">不能为空</string>
<string name="already_exist">已经存在</string>
<string name="please_select_a_policy">请选择策略</string>
<string name="private_dns">私人DNS</string>
<string name="dns_provide_hostname">指定主机名</string>
<string name="host_not_serving_dns_tls">主机不支持</string>
@@ -267,6 +271,7 @@
<string name="org_id">组织ID</string>
<string name="length_6_to_64">长度应在6~64个字符之间</string>
<string name="get_specific_id_after_set_org_id">设置组织ID后才能获取设备唯一标识码</string>
<string name="delete_work_profile">删除工作资料</string>
<!--AppManage-->
<string name="app_manager">应用管理</string>
@@ -281,6 +286,9 @@
<string name="hide">隐藏</string>
<string name="isapphidden_desc">如果隐藏,有可能是没安装</string>
<string name="always_on_vpn">VPN保持打开</string>
<string name="enable_lockdown">启用锁定</string>
<string name="current_app_is">当前应用:</string>
<string name="clear_current_config">清除当前配置</string>
<string name="permission">权限</string>
<string name="scope_is_work_profile">作用域: 工作资料</string>
<string name="app_info">应用详情</string>
@@ -511,6 +519,7 @@
<!--Settings&About-->
<string name="setting">设置</string>
<string name="show_dangerous_features">显示危险功能</string>
<string name="material_you_color">Material you 颜色</string>
<string name="dynamic_color_desc">安卓12+</string>
<string name="about">关于</string>
@@ -536,6 +545,9 @@
<string name="clear_storage">清除存储空间</string>
<string name="clear_storage_success">清除存储空间成功\n应用即将退出</string>
<string name="automation_api">自动化API</string>
<string name="automation_debug">调试模式</string>
<!--AndroidPermission-->
<string name="permission_READ_EXTERNAL_STORAGE">读取外部存储</string>
<string name="permission_WRITE_EXTERNAL_STORAGE">写入外部存储</string>
@@ -563,4 +575,7 @@
<string name="permission_ACTIVITY_RECOGNITION">查看使用情况</string>
<string name="permission_POST_NOTIFICATIONS">发送通知</string>
<string name="version_name">版本名</string>
<string name="version_code">版本号</string>
<string name="parsing_apk_info" tools:ignore="TypographyEllipsis">解析APK信息中...</string>
</resources>

View File

@@ -50,10 +50,12 @@
<string name="io_exception">IO Exception</string>
<string name="current_status_is">Current status: </string>
<string name="start">Start</string>
<string name="unknown_error">Unknown error</string>
<string name="stop">Stop</string>
<string name="allow_all">Allow all</string>
<string name="use_policy">Use policy</string>
<string name="account">Account</string>
<string name="warning">Warning</string>
<string name="delete">Delete</string>
<!--Permissions-->
<string name="click_to_activate">Click to activate</string>
@@ -109,8 +111,8 @@
<string name="dpm_activate_da_command" translatable="false">dpm set-active-admin com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver</string>
<string name="shizuku_activated_shell">Permission granted (Shell)</string>
<string name="shizuku_activated_root">Permission granted (Root)</string>
<string name="activate_profile_owner">Activate profile owner</string>
<string name="activate_device_owner">Activate device owner</string>
<string name="activate_profile_owner">Activate Profile owner</string>
<string name="activate_device_owner">Activate Device owner</string>
<string name="activate_org_profile">Activate organization-owned work profile</string>
<string name="shizuku_service_disconnected">Shizuku service disconnected</string>
<string name="invalid_binder">Invalid binder</string>
@@ -157,7 +159,11 @@
<string name="nearby_streaming_policy">Nearby streaming policy</string>
<string name="nearby_notification_streaming">Nearby notification streaming policy</string>
<string name="enable_if_secure_enough">Same managed account only</string>
<string name="lock_task_mode">Lock task mode</string>
<string name="lock_task_feature">Lock task feature</string>
<string name="lock_task_packages">Lock task packages</string>
<string name="start_lock_task_mode">Start lock task mode</string>
<string name="app_not_allowed">App not allowed</string>
<string name="disable_all">Disable all</string>
<!--ltf: lock task feature-->
<string name="ltf_sys_info">Allow system info</string>
@@ -167,7 +173,6 @@
<string name="ltf_global_actions">Allow global actions</string>
<string name="ltf_keyguard">Allow keyguard</string>
<string name="ltf_block_activity_start_in_task">Block activity start in task</string>
<string name="whitelist_app">Whitelisted app</string>
<string name="ca_cert">Ca certification</string>
<string name="please_select_ca_cert">Please select a certification</string>
<string name="cacert_installed">Cert installed: %1$s</string>
@@ -180,8 +185,8 @@
<string name="wipe_reset_protection_data">Wipe protected data</string>
<string name="wipe_euicc">Wipe eUICC(eSIM)</string>
<string name="wipe_silently">Wipe silently</string>
<string name="will_delete_work_profile">Work profile will be deleted. </string>
<string name="api34_or_above_wipedata_cannot_in_system_user">You cannot use WipeData in system user. </string>
<string name="wipe_data_warning">All data on your device will be ERASED</string>
<string name="wipe_work_profile_warning">Your work profile will be DELETED</string>
<string name="encrypt_status_is">Encrypt status: </string>
<string name="frp_policy">FRP policy</string>
<string name="factory_reset_protection_policy">Factory reset protection policy</string>
@@ -221,7 +226,6 @@
<string name="ssid_list_is">SSID list: </string>
<string name="cannot_be_empty">Cannot be empty</string>
<string name="already_exist">Already exist</string>
<string name="please_select_a_policy">Please select a policy</string>
<string name="private_dns">PrivateDNS</string>
<string name="dns_provide_hostname">Provide hostname</string>
<string name="host_not_serving_dns_tls">Host not serving</string>
@@ -281,6 +285,7 @@
<string name="org_id">Organization ID</string>
<string name="length_6_to_64">The length should be between 6~64 characters</string>
<string name="get_specific_id_after_set_org_id">You can get device specific ID after set this. </string>
<string name="delete_work_profile">Delete work profile</string>
<!--AppManager-->
<string name="app_manager">App manager</string>
@@ -295,6 +300,9 @@
<string name="hide">Hide</string>
<string name="isapphidden_desc">Non-existent apps is hidden</string>
<string name="always_on_vpn">Always-on VPN</string>
<string name="enable_lockdown">Enable lockdown</string>
<string name="current_app_is">Current app: </string>
<string name="clear_current_config">Clear current config</string>
<string name="permission">Permission</string>
<string name="scope_is_work_profile">Scope: work profile</string>
<string name="app_info">App info</string>
@@ -527,6 +535,7 @@
<!--Settings&About-->
<string name="setting">Settings</string>
<string name="show_dangerous_features">Show dangerous features</string>
<string name="material_you_color">Material you color</string>
<string name="dynamic_color_desc">Android 12+</string>
<string name="about">About</string>
@@ -552,6 +561,9 @@
<string name="clear_storage">Clear storage</string>
<string name="clear_storage_success">Clear storage success\nApplication will exit</string>
<string name="automation_api">Automation API</string>
<string name="automation_debug">Debug mode</string>
<!--AndroidPermission-->
<string name="permission_READ_EXTERNAL_STORAGE">Read external storage</string>
<string name="permission_WRITE_EXTERNAL_STORAGE">Write external storage</string>
@@ -579,4 +591,7 @@
<string name="permission_ACTIVITY_RECOGNITION">Activity recognition</string>
<string name="permission_POST_NOTIFICATIONS">Post notifications</string>
<string name="version_name">Version name</string>
<string name="version_code">Version code</string>
<string name="parsing_apk_info" tools:ignore="TypographyEllipsis">Parsing APK info...</string>
</resources>

View File

@@ -4,7 +4,7 @@
<item name="android:navigationBarColor">#FFFFFF</item>
<item name="android:statusBarColor">#FFFFFF</item>
</style>
<style name="Theme.OwnDroidAppInstaller" parent="android:Theme.Material.Light.NoActionBar">
<style name="Theme.Transparent" parent="Theme.OwnDroid">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>

View File

@@ -1,6 +1,7 @@
[versions]
agp = "8.4.0"
kt-android = "1.9.23"
agp = "8.5.0"
kt-android = "2.0.0"
cc = "2.0.0"
androidx-activity-compose = "1.8.2"
navigation-compose = "2.7.7"
@@ -27,3 +28,4 @@ androidx-fragment = { group = "androidx.fragment", name = "fragment", version.re
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kt-android" }
cc = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "cc" }