mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-27 20:36:00 +00:00
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -4,6 +4,10 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
tags-ignore:
|
||||
- '**'
|
||||
branches-ignore:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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"/>
|
||||
@@ -90,6 +102,17 @@
|
||||
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"
|
||||
android:authorities="${applicationId}.shizuku"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
refreshFeature()
|
||||
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()
|
||||
}
|
||||
refreshFeature()
|
||||
}
|
||||
) {
|
||||
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
|
||||
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
|
||||
var flag by remember { mutableIntStateOf(0) }
|
||||
var confirmed by remember { mutableStateOf(false) }
|
||||
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())) {
|
||||
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))
|
||||
if(VERSION.SDK_INT < 34 || (VERSION.SDK_INT >= 34 && !userManager.isSystemUser)) {
|
||||
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)
|
||||
}
|
||||
wipeDevice = false
|
||||
warning = true
|
||||
},
|
||||
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 && (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())
|
||||
}
|
||||
|
||||
52
app/src/main/java/com/github/fishb1/apkinfo/ApkInfo.kt
Normal file
52
app/src/main/java/com/github/fishb1/apkinfo/ApkInfo.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
55
app/src/main/java/com/github/fishb1/apkinfo/ManifestUtils.kt
Normal file
55
app/src/main/java/com/github/fishb1/apkinfo/ManifestUtils.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
9
app/src/main/res/drawable/calendar_month_fill0.xml
Normal file
9
app/src/main/res/drawable/calendar_month_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/call_log_fill0.xml
Normal file
9
app/src/main/res/drawable/call_log_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/contacts_fill0.xml
Normal file
9
app/src/main/res/drawable/contacts_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/folder_fill0.xml
Normal file
9
app/src/main/res/drawable/folder_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/image_fill0.xml
Normal file
9
app/src/main/res/drawable/image_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/movie_fill0.xml
Normal file
9
app/src/main/res/drawable/movie_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/music_note_fill0.xml
Normal file
9
app/src/main/res/drawable/music_note_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/sensors_fill0.xml
Normal file
9
app/src/main/res/drawable/sensors_fill0.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user