mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 19:15:58 +00:00
Refactor code related to navigation, close #104
New PackageChooserActivity Delete code of parsing apk metadata
This commit is contained in:
@@ -1,635 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class ApiReceiver: BroadcastReceiver() {
|
||||
false
|
||||
}
|
||||
}
|
||||
log += "success: $ok"
|
||||
log += "\nsuccess: $ok"
|
||||
} catch(e: Exception) {
|
||||
e.printStackTrace()
|
||||
val message = (e::class.qualifiedName ?: "Exception") + ": " + (e.message ?: "")
|
||||
|
||||
@@ -73,7 +73,8 @@ class AppInstallerActivity:FragmentActivity() {
|
||||
val vm by viewModels<AppInstallerViewModel>()
|
||||
vm.initialize(intent)
|
||||
setContent {
|
||||
OwnDroidTheme(myVm) {
|
||||
val theme by myVm.theme.collectAsStateWithLifecycle()
|
||||
OwnDroidTheme(theme) {
|
||||
val installing by vm.installing.collectAsStateWithLifecycle()
|
||||
val sessionMode by vm.sessionMode.collectAsStateWithLifecycle()
|
||||
val packages by vm.packages.collectAsStateWithLifecycle()
|
||||
|
||||
@@ -22,15 +22,16 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.NavHostController
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable object Authenticate
|
||||
|
||||
@Composable
|
||||
fun Authenticate(activity: FragmentActivity, navCtrl: NavHostController) {
|
||||
fun AuthenticateScreen(activity: FragmentActivity, onAuthSucceed: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
BackHandler { activity.moveTaskToBack(true) }
|
||||
var status by rememberSaveable { mutableIntStateOf(0) } // 0:Prompt automatically, 1:Authenticating, 2:Prompt manually
|
||||
val onAuthSucceed = { navCtrl.navigateUp() }
|
||||
val callback = object: AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
|
||||
@@ -56,80 +56,147 @@ import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.bintianqi.owndroid.dpm.AccountsViewer
|
||||
import com.bintianqi.owndroid.dpm.AffiliationID
|
||||
import com.bintianqi.owndroid.dpm.AlwaysOnVPNPackage
|
||||
import com.bintianqi.owndroid.dpm.ApplicationManage
|
||||
import com.bintianqi.owndroid.dpm.CACert
|
||||
import androidx.navigation.toRoute
|
||||
import com.bintianqi.owndroid.dpm.Accounts
|
||||
import com.bintianqi.owndroid.dpm.AccountsScreen
|
||||
import com.bintianqi.owndroid.dpm.AddNetwork
|
||||
import com.bintianqi.owndroid.dpm.AddNetworkScreen
|
||||
import com.bintianqi.owndroid.dpm.AffiliationId
|
||||
import com.bintianqi.owndroid.dpm.AffiliationIdScreen
|
||||
import com.bintianqi.owndroid.dpm.AlwaysOnVpnPackage
|
||||
import com.bintianqi.owndroid.dpm.AlwaysOnVpnPackageScreen
|
||||
import com.bintianqi.owndroid.dpm.Applications
|
||||
import com.bintianqi.owndroid.dpm.ApplicationsScreen
|
||||
import com.bintianqi.owndroid.dpm.CaCert
|
||||
import com.bintianqi.owndroid.dpm.CaCertScreen
|
||||
import com.bintianqi.owndroid.dpm.ChangeTime
|
||||
import com.bintianqi.owndroid.dpm.ChangeTimeScreen
|
||||
import com.bintianqi.owndroid.dpm.ChangeTimeZone
|
||||
import com.bintianqi.owndroid.dpm.ChangeTimeZoneScreen
|
||||
import com.bintianqi.owndroid.dpm.ChangeUserIcon
|
||||
import com.bintianqi.owndroid.dpm.ChangeUserIconScreen
|
||||
import com.bintianqi.owndroid.dpm.ChangeUsername
|
||||
import com.bintianqi.owndroid.dpm.ChangeUsernameScreen
|
||||
import com.bintianqi.owndroid.dpm.ContentProtectionPolicy
|
||||
import com.bintianqi.owndroid.dpm.ContentProtectionPolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.CreateUser
|
||||
import com.bintianqi.owndroid.dpm.CreateUserScreen
|
||||
import com.bintianqi.owndroid.dpm.CreateWorkProfile
|
||||
import com.bintianqi.owndroid.dpm.CurrentUserInfo
|
||||
import com.bintianqi.owndroid.dpm.CreateWorkProfileScreen
|
||||
import com.bintianqi.owndroid.dpm.CrossProfileIntentFilter
|
||||
import com.bintianqi.owndroid.dpm.CrossProfileIntentFilterScreen
|
||||
import com.bintianqi.owndroid.dpm.DelegatedAdmins
|
||||
import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen
|
||||
import com.bintianqi.owndroid.dpm.DeleteWorkProfile
|
||||
import com.bintianqi.owndroid.dpm.DeleteWorkProfileScreen
|
||||
import com.bintianqi.owndroid.dpm.DeviceAdmin
|
||||
import com.bintianqi.owndroid.dpm.DeviceAdminScreen
|
||||
import com.bintianqi.owndroid.dpm.DeviceInfo
|
||||
import com.bintianqi.owndroid.dpm.DeviceInfoScreen
|
||||
import com.bintianqi.owndroid.dpm.DeviceOwner
|
||||
import com.bintianqi.owndroid.dpm.DeviceOwnerScreen
|
||||
import com.bintianqi.owndroid.dpm.DisableAccountManagement
|
||||
import com.bintianqi.owndroid.dpm.DisableKeyguardFeatures
|
||||
import com.bintianqi.owndroid.dpm.FRPPolicy
|
||||
import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen
|
||||
import com.bintianqi.owndroid.dpm.FrpPolicy
|
||||
import com.bintianqi.owndroid.dpm.FrpPolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.HardwareMonitor
|
||||
import com.bintianqi.owndroid.dpm.HardwareMonitorScreen
|
||||
import com.bintianqi.owndroid.dpm.InstallSystemUpdate
|
||||
import com.bintianqi.owndroid.dpm.IntentFilter
|
||||
import com.bintianqi.owndroid.dpm.InstallSystemUpdateScreen
|
||||
import com.bintianqi.owndroid.dpm.Keyguard
|
||||
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeatures
|
||||
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeaturesScreen
|
||||
import com.bintianqi.owndroid.dpm.KeyguardScreen
|
||||
import com.bintianqi.owndroid.dpm.LockScreenInfo
|
||||
import com.bintianqi.owndroid.dpm.LockScreenInfoScreen
|
||||
import com.bintianqi.owndroid.dpm.LockTaskMode
|
||||
import com.bintianqi.owndroid.dpm.MTEPolicy
|
||||
import com.bintianqi.owndroid.dpm.LockTaskModeScreen
|
||||
import com.bintianqi.owndroid.dpm.MtePolicy
|
||||
import com.bintianqi.owndroid.dpm.MtePolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.NearbyStreamingPolicy
|
||||
import com.bintianqi.owndroid.dpm.NearbyStreamingPolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.Network
|
||||
import com.bintianqi.owndroid.dpm.NetworkLogging
|
||||
import com.bintianqi.owndroid.dpm.NetworkLoggingScreen
|
||||
import com.bintianqi.owndroid.dpm.NetworkOptions
|
||||
import com.bintianqi.owndroid.dpm.NetworkStats
|
||||
import com.bintianqi.owndroid.dpm.NetworkOptionsScreen
|
||||
import com.bintianqi.owndroid.dpm.NetworkScreen
|
||||
import com.bintianqi.owndroid.dpm.NetworkStatsScreen
|
||||
import com.bintianqi.owndroid.dpm.NetworkStatsViewer
|
||||
import com.bintianqi.owndroid.dpm.OrgOwnedProfile
|
||||
import com.bintianqi.owndroid.dpm.OverrideAPN
|
||||
import com.bintianqi.owndroid.dpm.NetworkStatsViewerScreen
|
||||
import com.bintianqi.owndroid.dpm.OrganizationOwnedProfile
|
||||
import com.bintianqi.owndroid.dpm.OrganizationOwnedProfileScreen
|
||||
import com.bintianqi.owndroid.dpm.OverrideApn
|
||||
import com.bintianqi.owndroid.dpm.OverrideApnScreen
|
||||
import com.bintianqi.owndroid.dpm.Password
|
||||
import com.bintianqi.owndroid.dpm.PasswordComplexity
|
||||
import com.bintianqi.owndroid.dpm.PasswordInfo
|
||||
import com.bintianqi.owndroid.dpm.PasswordQuality
|
||||
import com.bintianqi.owndroid.dpm.PasswordInfoScreen
|
||||
import com.bintianqi.owndroid.dpm.PasswordScreen
|
||||
import com.bintianqi.owndroid.dpm.PermissionPolicy
|
||||
import com.bintianqi.owndroid.dpm.PermissionPolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.Permissions
|
||||
import com.bintianqi.owndroid.dpm.PermissionsScreen
|
||||
import com.bintianqi.owndroid.dpm.PreferentialNetworkService
|
||||
import com.bintianqi.owndroid.dpm.PrivateDNS
|
||||
import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen
|
||||
import com.bintianqi.owndroid.dpm.PrivateDns
|
||||
import com.bintianqi.owndroid.dpm.PrivateDnsScreen
|
||||
import com.bintianqi.owndroid.dpm.ProfileOwner
|
||||
import com.bintianqi.owndroid.dpm.ProfileOwnerScreen
|
||||
import com.bintianqi.owndroid.dpm.QueryNetworkStats
|
||||
import com.bintianqi.owndroid.dpm.RecommendedGlobalProxy
|
||||
import com.bintianqi.owndroid.dpm.RecommendedGlobalProxyScreen
|
||||
import com.bintianqi.owndroid.dpm.RequiredPasswordComplexity
|
||||
import com.bintianqi.owndroid.dpm.RequiredPasswordComplexityScreen
|
||||
import com.bintianqi.owndroid.dpm.RequiredPasswordQuality
|
||||
import com.bintianqi.owndroid.dpm.RequiredPasswordQualityScreen
|
||||
import com.bintianqi.owndroid.dpm.ResetPassword
|
||||
import com.bintianqi.owndroid.dpm.ResetPasswordScreen
|
||||
import com.bintianqi.owndroid.dpm.ResetPasswordToken
|
||||
import com.bintianqi.owndroid.dpm.RestrictionData
|
||||
import com.bintianqi.owndroid.dpm.ResetPasswordTokenScreen
|
||||
import com.bintianqi.owndroid.dpm.Restriction
|
||||
import com.bintianqi.owndroid.dpm.SecurityLogging
|
||||
import com.bintianqi.owndroid.dpm.Shizuku
|
||||
import com.bintianqi.owndroid.dpm.SupportMessages
|
||||
import com.bintianqi.owndroid.dpm.SecurityLoggingScreen
|
||||
import com.bintianqi.owndroid.dpm.SetSystemUpdatePolicy
|
||||
import com.bintianqi.owndroid.dpm.ShizukuScreen
|
||||
import com.bintianqi.owndroid.dpm.SupportMessage
|
||||
import com.bintianqi.owndroid.dpm.SupportMessageScreen
|
||||
import com.bintianqi.owndroid.dpm.SuspendPersonalApp
|
||||
import com.bintianqi.owndroid.dpm.SystemManage
|
||||
import com.bintianqi.owndroid.dpm.SuspendPersonalAppScreen
|
||||
import com.bintianqi.owndroid.dpm.SystemManager
|
||||
import com.bintianqi.owndroid.dpm.SystemManagerScreen
|
||||
import com.bintianqi.owndroid.dpm.SystemOptions
|
||||
import com.bintianqi.owndroid.dpm.SystemOptionsScreen
|
||||
import com.bintianqi.owndroid.dpm.SystemUpdatePolicy
|
||||
import com.bintianqi.owndroid.dpm.TransferOwnership
|
||||
import com.bintianqi.owndroid.dpm.UpdateNetwork
|
||||
import com.bintianqi.owndroid.dpm.TransferOwnershipScreen
|
||||
import com.bintianqi.owndroid.dpm.UserInfo
|
||||
import com.bintianqi.owndroid.dpm.UserInfoScreen
|
||||
import com.bintianqi.owndroid.dpm.UserOperation
|
||||
import com.bintianqi.owndroid.dpm.UserOptions
|
||||
import com.bintianqi.owndroid.dpm.UserOperationScreen
|
||||
import com.bintianqi.owndroid.dpm.UserRestriction
|
||||
import com.bintianqi.owndroid.dpm.UserRestrictionOptions
|
||||
import com.bintianqi.owndroid.dpm.UserRestrictionOptionsScreen
|
||||
import com.bintianqi.owndroid.dpm.UserRestrictionScreen
|
||||
import com.bintianqi.owndroid.dpm.UserSessionMessage
|
||||
import com.bintianqi.owndroid.dpm.UserSessionMessageScreen
|
||||
import com.bintianqi.owndroid.dpm.Users
|
||||
import com.bintianqi.owndroid.dpm.Wifi
|
||||
import com.bintianqi.owndroid.dpm.UsersOptions
|
||||
import com.bintianqi.owndroid.dpm.UsersOptionsScreen
|
||||
import com.bintianqi.owndroid.dpm.UsersScreen
|
||||
import com.bintianqi.owndroid.dpm.WiFi
|
||||
import com.bintianqi.owndroid.dpm.WifiAuthKeypair
|
||||
import com.bintianqi.owndroid.dpm.WifiAuthKeypairScreen
|
||||
import com.bintianqi.owndroid.dpm.WifiScreen
|
||||
import com.bintianqi.owndroid.dpm.WifiSecurityLevel
|
||||
import com.bintianqi.owndroid.dpm.WifiSsidPolicy
|
||||
import com.bintianqi.owndroid.dpm.WifiSecurityLevelScreen
|
||||
import com.bintianqi.owndroid.dpm.WifiSsidPolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.WipeData
|
||||
import com.bintianqi.owndroid.dpm.WipeDataScreen
|
||||
import com.bintianqi.owndroid.dpm.WorkProfile
|
||||
import com.bintianqi.owndroid.dpm.WorkProfileScreen
|
||||
import com.bintianqi.owndroid.dpm.dhizukuErrorStatus
|
||||
import com.bintianqi.owndroid.dpm.dhizukuPermissionGranted
|
||||
import com.bintianqi.owndroid.dpm.getDPM
|
||||
@@ -144,6 +211,7 @@ import com.rosan.dhizuku.api.Dhizuku
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import java.util.Locale
|
||||
|
||||
@@ -162,7 +230,8 @@ class MainActivity : FragmentActivity() {
|
||||
val vm by viewModels<MyViewModel>()
|
||||
lifecycleScope.launch { delay(5000); setDefaultAffiliationID(context) }
|
||||
setContent {
|
||||
OwnDroidTheme(vm) {
|
||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
||||
OwnDroidTheme(theme) {
|
||||
Home(this, vm)
|
||||
}
|
||||
}
|
||||
@@ -208,9 +277,10 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
||||
context.showOperationResultToast(false)
|
||||
}
|
||||
}
|
||||
fun navigateUp() { navCtrl.navigateUp() }
|
||||
@Suppress("NewApi") NavHost(
|
||||
navController = navCtrl,
|
||||
startDestination = "HomePage",
|
||||
startDestination = Home,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(colorScheme.background)
|
||||
@@ -221,129 +291,121 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
||||
popEnterTransition = Animations.navHostPopEnterTransition,
|
||||
popExitTransition = Animations.navHostPopExitTransition
|
||||
) {
|
||||
composable(route = "HomePage") { HomePage(navCtrl) }
|
||||
composable<Home> { HomeScreen { navCtrl.navigate(it) } }
|
||||
|
||||
composable(route = "Permissions") { Permissions(navCtrl) }
|
||||
composable(route = "Shizuku") { Shizuku(navCtrl, it.arguments!!) }
|
||||
composable(route = "AccountsViewer") { AccountsViewer(navCtrl, it.arguments!!) }
|
||||
composable(route = "DeviceAdmin") { DeviceAdmin(navCtrl) }
|
||||
composable(route = "ProfileOwner") { ProfileOwner(navCtrl) }
|
||||
composable(route = "DeviceOwner") { DeviceOwner(navCtrl) }
|
||||
composable(route = "DelegatedAdmins") { DelegatedAdmins(navCtrl, vm) }
|
||||
composable(route = "DeviceInfo") { DeviceInfo(navCtrl) }
|
||||
composable(route = "LockScreenInfo") { LockScreenInfo(navCtrl) }
|
||||
composable(route = "SupportMessages") { SupportMessages(navCtrl) }
|
||||
composable(route = "TransferOwnership") { TransferOwnership(navCtrl) }
|
||||
|
||||
composable(route = "System") { SystemManage(navCtrl) }
|
||||
composable(route = "SystemOptions") { SystemOptions(navCtrl) }
|
||||
composable(route = "Keyguard") { Keyguard(navCtrl) }
|
||||
composable(route = "HardwareMonitor") { HardwareMonitor(navCtrl) }
|
||||
composable(route = "ChangeTime") { ChangeTime(navCtrl) }
|
||||
composable(route = "ChangeTimeZone") { ChangeTimeZone(navCtrl) }
|
||||
//composable(route = "KeyPairs") { KeyPairs(navCtrl) }
|
||||
composable(route = "ContentProtectionPolicy") { ContentProtectionPolicy(navCtrl) }
|
||||
composable(route = "PermissionPolicy") { PermissionPolicy(navCtrl) }
|
||||
composable(route = "MTEPolicy") { MTEPolicy(navCtrl) }
|
||||
composable(route = "NearbyStreamingPolicy") { NearbyStreamingPolicy(navCtrl) }
|
||||
composable(route = "LockTaskMode") { LockTaskMode(navCtrl, vm) }
|
||||
composable(route = "CACert") { CACert(navCtrl) }
|
||||
composable(route = "SecurityLogging") { SecurityLogging(navCtrl) }
|
||||
composable(route = "DisableAccountManagement") { DisableAccountManagement(navCtrl) }
|
||||
composable(route = "SystemUpdatePolicy") { SystemUpdatePolicy(navCtrl) }
|
||||
composable(route = "InstallSystemUpdate") { InstallSystemUpdate(navCtrl) }
|
||||
composable(route = "FRPPolicy") { FRPPolicy(navCtrl) }
|
||||
composable(route = "WipeData") { WipeData(navCtrl) }
|
||||
|
||||
composable(route = "Network") { Network(navCtrl) }
|
||||
composable(route = "Wifi") { Wifi(navCtrl) }
|
||||
composable(route = "NetworkOptions") { NetworkOptions(navCtrl) }
|
||||
composable(route = "UpdateNetwork") { UpdateNetwork(it.arguments!!, navCtrl) }
|
||||
composable(route = "MinWifiSecurityLevel") { WifiSecurityLevel(navCtrl) }
|
||||
composable(route = "WifiSsidPolicy") { WifiSsidPolicy(navCtrl) }
|
||||
composable(route = "NetworkStats") { NetworkStats(navCtrl, vm) }
|
||||
composable(route = "NetworkStatsViewer") { NetworkStatsViewer(navCtrl, it.arguments!!) }
|
||||
composable(route = "PrivateDNS") { PrivateDNS(navCtrl) }
|
||||
composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(navCtrl, vm) }
|
||||
composable(route = "RecommendedGlobalProxy") { RecommendedGlobalProxy(navCtrl) }
|
||||
composable(route = "NetworkLog") { NetworkLogging(navCtrl) }
|
||||
composable(route = "WifiAuthKeypair") { WifiAuthKeypair(navCtrl) }
|
||||
composable(route = "PreferentialNetworkService") { PreferentialNetworkService(navCtrl) }
|
||||
composable(route = "OverrideAPN") { OverrideAPN(navCtrl) }
|
||||
|
||||
composable(route = "WorkProfile") { WorkProfile(navCtrl) }
|
||||
composable(route = "OrgOwnedWorkProfile") { OrgOwnedProfile(navCtrl) }
|
||||
composable(route = "CreateWorkProfile") { CreateWorkProfile(navCtrl) }
|
||||
composable(route = "SuspendPersonalApp") { SuspendPersonalApp(navCtrl) }
|
||||
composable(route = "IntentFilter") { IntentFilter(navCtrl) }
|
||||
composable(route = "DeleteWorkProfile") { DeleteWorkProfile(navCtrl) }
|
||||
|
||||
composable(route = "Applications") { ApplicationManage(navCtrl, vm) }
|
||||
|
||||
composable(route = "UserRestriction") { UserRestriction(navCtrl, vm) }
|
||||
composable(route = "UR-Internet") {
|
||||
UserRestrictionScreen(R.string.network_and_internet, RestrictionData.internet, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
composable<Permissions> {
|
||||
PermissionsScreen(::navigateUp, { navCtrl.navigate(it) }) {
|
||||
val dest = navCtrl.graph.findNode(ShizukuScreen)!!.id
|
||||
navCtrl.navigate(dest, it)
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Connectivity") {
|
||||
UserRestrictionScreen(R.string.connectivity, RestrictionData.connectivity, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
composable<ShizukuScreen> { ShizukuScreen(it.arguments!!, ::navigateUp) { navCtrl.navigate(it) } }
|
||||
composable<Accounts>(mapOf(serializableNavTypePair<List<Accounts.Account>>())) { AccountsScreen(it.toRoute(), ::navigateUp) }
|
||||
composable<DeviceAdmin> { DeviceAdminScreen(::navigateUp) }
|
||||
composable<ProfileOwner> { ProfileOwnerScreen(::navigateUp) }
|
||||
composable<DeviceOwner> { DeviceOwnerScreen(::navigateUp) }
|
||||
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp) }
|
||||
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
|
||||
composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
|
||||
composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
|
||||
composable<TransferOwnership> { TransferOwnershipScreen(::navigateUp) }
|
||||
|
||||
composable<SystemManager> { SystemManagerScreen(::navigateUp) { navCtrl.navigate(it) } }
|
||||
composable<SystemOptions> { SystemOptionsScreen(::navigateUp) }
|
||||
composable<Keyguard> { KeyguardScreen(::navigateUp) }
|
||||
composable<HardwareMonitor> { HardwareMonitorScreen(::navigateUp) }
|
||||
composable<ChangeTime> { ChangeTimeScreen(::navigateUp) }
|
||||
composable<ChangeTimeZone> { ChangeTimeZoneScreen(::navigateUp) }
|
||||
//composable<> { KeyPairs(::navigateUp) }
|
||||
composable<ContentProtectionPolicy> { ContentProtectionPolicyScreen(::navigateUp) }
|
||||
composable<PermissionPolicy> { PermissionPolicyScreen(::navigateUp) }
|
||||
composable<MtePolicy> { MtePolicyScreen(::navigateUp) }
|
||||
composable<NearbyStreamingPolicy> { NearbyStreamingPolicyScreen(::navigateUp) }
|
||||
composable<LockTaskMode> { LockTaskModeScreen(::navigateUp) }
|
||||
composable<CaCert> { CaCertScreen(::navigateUp) }
|
||||
composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) }
|
||||
composable<DisableAccountManagement> { DisableAccountManagementScreen(::navigateUp) }
|
||||
composable<SetSystemUpdatePolicy> { SystemUpdatePolicy(::navigateUp) }
|
||||
composable<InstallSystemUpdate> { InstallSystemUpdateScreen(::navigateUp) }
|
||||
composable<FrpPolicy> { FrpPolicyScreen(::navigateUp) }
|
||||
composable<WipeData> { WipeDataScreen(::navigateUp) }
|
||||
|
||||
composable<Network> { NetworkScreen(::navigateUp) { navCtrl.navigate(it) } }
|
||||
composable<WiFi> {
|
||||
WifiScreen(::navigateUp, { navCtrl.navigate(it) }) {
|
||||
val dest = navCtrl.graph.findNode(AddNetwork)!!.id
|
||||
navCtrl.navigate(dest, it)
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Applications") {
|
||||
UserRestrictionScreen(R.string.applications, RestrictionData.applications, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
composable<NetworkOptions> { NetworkOptionsScreen(::navigateUp) }
|
||||
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
|
||||
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
|
||||
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
|
||||
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp) { navCtrl.navigate(it) } }
|
||||
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
|
||||
NetworkStatsViewerScreen(it.toRoute()) { navCtrl.navigateUp() }
|
||||
}
|
||||
composable<PrivateDns> { PrivateDnsScreen(::navigateUp) }
|
||||
composable<AlwaysOnVpnPackage> { AlwaysOnVpnPackageScreen(::navigateUp) }
|
||||
composable<RecommendedGlobalProxy> { RecommendedGlobalProxyScreen(::navigateUp) }
|
||||
composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) }
|
||||
composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
|
||||
composable<PreferentialNetworkService> { PreferentialNetworkServiceScreen(::navigateUp) }
|
||||
composable<OverrideApn> { OverrideApnScreen(::navigateUp) }
|
||||
|
||||
composable<WorkProfile> { WorkProfileScreen(::navigateUp) { navCtrl.navigate(it) } }
|
||||
composable<OrganizationOwnedProfile> { OrganizationOwnedProfileScreen(::navigateUp) }
|
||||
composable<CreateWorkProfile> { CreateWorkProfileScreen(::navigateUp) }
|
||||
composable<SuspendPersonalApp> { SuspendPersonalAppScreen(::navigateUp) }
|
||||
composable<CrossProfileIntentFilter> { CrossProfileIntentFilterScreen(::navigateUp) }
|
||||
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(::navigateUp) }
|
||||
|
||||
composable<Applications> { ApplicationsScreen(::navigateUp) }
|
||||
|
||||
composable<UserRestriction> {
|
||||
LaunchedEffect(Unit) {
|
||||
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
|
||||
}
|
||||
UserRestrictionScreen(::navigateUp) { title, items ->
|
||||
navCtrl.navigate(UserRestrictionOptions(title, items))
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Users") {
|
||||
UserRestrictionScreen(R.string.users, RestrictionData.users, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Media") {
|
||||
UserRestrictionScreen(R.string.media, RestrictionData.media, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Other") {
|
||||
UserRestrictionScreen(R.string.other, RestrictionData.other, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
composable<UserRestrictionOptions>(mapOf(serializableNavTypePair<List<Restriction>>())) {
|
||||
UserRestrictionOptionsScreen(it.toRoute(), userRestrictions, ::onUserRestrictionsChange, ::navigateUp)
|
||||
}
|
||||
|
||||
composable(route = "Users") { Users(navCtrl) }
|
||||
composable(route = "UserInfo") { CurrentUserInfo(navCtrl) }
|
||||
composable(route = "UserOptions") { UserOptions(navCtrl) }
|
||||
composable(route = "UserOperation") { UserOperation(navCtrl) }
|
||||
composable(route = "CreateUser") { CreateUser(navCtrl) }
|
||||
composable(route = "ChangeUsername") { ChangeUsername(navCtrl) }
|
||||
composable(route = "ChangeUserIcon") { ChangeUserIcon(navCtrl) }
|
||||
composable(route = "UserSessionMessage") { UserSessionMessage(navCtrl) }
|
||||
composable(route = "AffiliationID") { AffiliationID(navCtrl) }
|
||||
composable<Users> { UsersScreen(::navigateUp) { navCtrl.navigate(it) } }
|
||||
composable<UserInfo> { UserInfoScreen(::navigateUp) }
|
||||
composable<UsersOptions> { UsersOptionsScreen(::navigateUp) }
|
||||
composable<UserOperation> { UserOperationScreen(::navigateUp) }
|
||||
composable<CreateUser> { CreateUserScreen(::navigateUp) }
|
||||
composable<ChangeUsername> { ChangeUsernameScreen(::navigateUp) }
|
||||
composable<ChangeUserIcon> { ChangeUserIconScreen(::navigateUp) }
|
||||
composable<UserSessionMessage> { UserSessionMessageScreen(::navigateUp) }
|
||||
composable<AffiliationId> { AffiliationIdScreen(::navigateUp) }
|
||||
|
||||
composable(route = "Password") { Password(navCtrl) }
|
||||
composable(route = "PasswordInfo") { PasswordInfo(navCtrl) }
|
||||
composable(route = "ResetPasswordToken") { ResetPasswordToken(navCtrl) }
|
||||
composable(route = "ResetPassword") { ResetPassword(navCtrl) }
|
||||
composable(route = "RequirePasswordComplexity") { PasswordComplexity(navCtrl) }
|
||||
composable(route = "DisableKeyguardFeatures") { DisableKeyguardFeatures(navCtrl) }
|
||||
composable(route = "RequirePasswordQuality") { PasswordQuality(navCtrl) }
|
||||
composable<Password> { PasswordScreen(::navigateUp) { navCtrl.navigate(it) } }
|
||||
composable<PasswordInfo> { PasswordInfoScreen(::navigateUp) }
|
||||
composable<ResetPasswordToken> { ResetPasswordTokenScreen(::navigateUp) }
|
||||
composable<ResetPassword> { ResetPasswordScreen(::navigateUp) }
|
||||
composable<RequiredPasswordComplexity> { RequiredPasswordComplexityScreen(::navigateUp) }
|
||||
composable<KeyguardDisabledFeatures> { KeyguardDisabledFeaturesScreen(::navigateUp) }
|
||||
composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) }
|
||||
|
||||
composable(route = "Settings") { Settings(navCtrl) }
|
||||
composable(route = "Options") { SettingsOptions(navCtrl) }
|
||||
composable(route = "Appearance") { Appearance(navCtrl, vm) }
|
||||
composable(route = "AuthSettings") { AuthSettings(navCtrl) }
|
||||
composable(route = "ApiSettings") { ApiSettings(navCtrl) }
|
||||
composable(route = "About") { About(navCtrl) }
|
||||
composable<Settings> { SettingsScreen(::navigateUp) { navCtrl.navigate(it) } }
|
||||
composable<SettingsOptions> { SettingsOptionsScreen(::navigateUp) }
|
||||
composable<Appearance> {
|
||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
||||
AppearanceScreen(::navigateUp, theme) { vm.theme.value = it }
|
||||
}
|
||||
composable<AuthSettings> { AuthSettingsScreen(::navigateUp) }
|
||||
composable<ApiSettings> { ApiSettings(::navigateUp) }
|
||||
composable<About> { AboutScreen(::navigateUp) }
|
||||
|
||||
composable(route = "PackageSelector") { PackageSelector(navCtrl, vm) }
|
||||
|
||||
composable(
|
||||
route = "Authenticate",
|
||||
composable<Authenticate>(
|
||||
enterTransition = { fadeIn(animationSpec = tween(200)) },
|
||||
popExitTransition = { fadeOut(animationSpec = tween(400)) }
|
||||
) { Authenticate(activity, navCtrl) }
|
||||
) { AuthenticateScreen(activity, ::navigateUp) }
|
||||
}
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
@@ -351,7 +413,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
||||
(event == Lifecycle.Event.ON_RESUME && sp.auth && sp.lockInBackground) ||
|
||||
(event == Lifecycle.Event.ON_CREATE && sp.auth)
|
||||
) {
|
||||
navCtrl.navigate("Authenticate") { launchSingleTop = true }
|
||||
navCtrl.navigate(Authenticate) { launchSingleTop = true }
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
@@ -370,8 +432,10 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
||||
DhizukuErrorDialog()
|
||||
}
|
||||
|
||||
@Serializable private object Home
|
||||
|
||||
@Composable
|
||||
private fun HomePage(navCtrl:NavHostController) {
|
||||
private fun HomeScreen(onNavigate: (Any) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -406,7 +470,7 @@ private fun HomePage(navCtrl:NavHostController) {
|
||||
.padding(vertical = 8.dp, horizontal = 8.dp)
|
||||
.clip(RoundedCornerShape(15))
|
||||
.background(color = colorScheme.primary)
|
||||
.clickable(onClick = { navCtrl.navigate("Permissions") })
|
||||
.clickable(onClick = { onNavigate(Permissions) })
|
||||
.padding(vertical = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
@@ -427,33 +491,33 @@ private fun HomePage(navCtrl:NavHostController) {
|
||||
if(activateType != "") { Text(text = activateType, color = colorScheme.onPrimary) }
|
||||
}
|
||||
}
|
||||
HomePageItem(R.string.system, R.drawable.android_fill0, "System", navCtrl)
|
||||
if(deviceOwner || profileOwner) { HomePageItem(R.string.network, R.drawable.wifi_fill0, "Network", navCtrl) }
|
||||
HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) }
|
||||
if(deviceOwner || profileOwner) { HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) } }
|
||||
if(
|
||||
(VERSION.SDK_INT < 24 && !deviceOwner) || (dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) ||
|
||||
(profileOwner && dpm.isManagedProfile(receiver))
|
||||
)
|
||||
) {
|
||||
HomePageItem(R.string.work_profile, R.drawable.work_fill0, "ManagedProfile", navCtrl)
|
||||
HomePageItem(R.string.work_profile, R.drawable.work_fill0) { onNavigate(WorkProfile) }
|
||||
}
|
||||
if(deviceOwner || profileOwner) HomePageItem(R.string.applications, R.drawable.apps_fill0, "Applications", navCtrl)
|
||||
if(deviceOwner || profileOwner) HomePageItem(R.string.applications, R.drawable.apps_fill0) { onNavigate(Applications) }
|
||||
if(VERSION.SDK_INT >= 24 && (profileOwner || deviceOwner)) {
|
||||
HomePageItem(R.string.user_restriction, R.drawable.person_off, "UserRestriction", navCtrl)
|
||||
HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) }
|
||||
}
|
||||
HomePageItem(R.string.users,R.drawable.manage_accounts_fill0,"Users", navCtrl)
|
||||
HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0, "Password", navCtrl)
|
||||
HomePageItem(R.string.settings, R.drawable.settings_fill0, "Settings", navCtrl)
|
||||
HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) }
|
||||
HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) }
|
||||
HomePageItem(R.string.settings, R.drawable.settings_fill0) { onNavigate(Settings) }
|
||||
Spacer(Modifier.padding(vertical = 20.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomePageItem(name: Int, imgVector: Int, navTo: String, navCtrl: NavHostController) {
|
||||
fun HomePageItem(name: Int, imgVector: Int, onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { navCtrl.navigate(navTo) })
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@@ -55,7 +56,8 @@ class ManageSpaceActivity: FragmentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
OwnDroidTheme(vm) {
|
||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
||||
OwnDroidTheme(theme) {
|
||||
AlertDialog(
|
||||
text = {
|
||||
Text(stringResource(R.string.clear_storage))
|
||||
|
||||
@@ -9,8 +9,6 @@ import kotlinx.coroutines.launch
|
||||
|
||||
class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
val theme = MutableStateFlow(ThemeSettings())
|
||||
val installedPackages = mutableListOf<PackageInfo>()
|
||||
val selectedPackage = MutableStateFlow("")
|
||||
val userRestrictions = MutableStateFlow(Bundle())
|
||||
|
||||
init {
|
||||
|
||||
224
app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt
Normal file
224
app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt
Normal file
@@ -0,0 +1,224 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
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
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
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.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
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
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class PackageChooserActivity: ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val myVm by viewModels<MyViewModel>()
|
||||
val vm by viewModels<PackageChooserViewModel>()
|
||||
vm.initialize()
|
||||
setContent {
|
||||
val theme by myVm.theme.collectAsStateWithLifecycle()
|
||||
OwnDroidTheme(theme) {
|
||||
val packages by vm.packages.collectAsStateWithLifecycle()
|
||||
val progress by vm.progress.collectAsStateWithLifecycle()
|
||||
PackageChooserScreen(packages, progress, vm::getPackages) {
|
||||
setResult(0, Intent().putExtra("package", it))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PackageChooserViewModel(application: Application): AndroidViewModel(application) {
|
||||
val packages = MutableStateFlow(emptyList<PackageInfo>())
|
||||
val progress = MutableStateFlow(0F)
|
||||
val flags = if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0
|
||||
fun initialize() {
|
||||
if(progress.value < 1F) getPackages()
|
||||
}
|
||||
fun getPackages() {
|
||||
packages.value = emptyList()
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val pm = getApplication<Application>().packageManager
|
||||
val apps = pm.getInstalledApplications(flags)
|
||||
for(pkg in apps) {
|
||||
packages.update {
|
||||
it + PackageInfo(
|
||||
pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm),
|
||||
(pkg.flags and ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
)
|
||||
}
|
||||
withContext(Dispatchers.Main) { progress.value = packages.value.size.toFloat() / apps.size }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class PackageInfo(
|
||||
val name: String,
|
||||
val label: String,
|
||||
val icon: Drawable,
|
||||
val system: Boolean
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun PackageChooserScreen(
|
||||
packages: List<PackageInfo>, progress: Float, onRefresh: () -> Unit, onChoosePackage: (String?) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var system by remember { mutableStateOf(false) }
|
||||
var search by remember { mutableStateOf("") }
|
||||
var searchMode by remember { mutableStateOf(false) }
|
||||
val filteredPackages = packages.filter {
|
||||
system == it.system &&
|
||||
(if(search.isEmpty()) true
|
||||
else it.name.contains(search, ignoreCase = true) || it.label.contains(search, ignoreCase = true))
|
||||
}
|
||||
val focusMgr = LocalFocusManager.current
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
actions = {
|
||||
if(!searchMode) {
|
||||
IconButton({ searchMode = true }) {
|
||||
Icon(painter = painterResource(R.drawable.search_fill0), contentDescription = stringResource(R.string.search))
|
||||
}
|
||||
IconButton({
|
||||
system = !system
|
||||
Toast.makeText(context, if(system) R.string.show_system_app else R.string.show_user_app, Toast.LENGTH_SHORT).show()
|
||||
}) {
|
||||
Icon(painter = painterResource(R.drawable.filter_alt_fill0), contentDescription = null)
|
||||
}
|
||||
IconButton(onRefresh) {
|
||||
Icon(painter = painterResource(R.drawable.refresh_fill0), contentDescription = null)
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
if(searchMode) {
|
||||
val fr = FocusRequester()
|
||||
LaunchedEffect(Unit) { fr.requestFocus() }
|
||||
OutlinedTextField(
|
||||
value = search,
|
||||
onValueChange = { search = it },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
|
||||
placeholder = { Text(stringResource(R.string.search)) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.close_fill0),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.clickable {
|
||||
focusMgr.clearFocus()
|
||||
search = ""
|
||||
searchMode = false
|
||||
}
|
||||
)
|
||||
},
|
||||
textStyle = typography.bodyLarge,
|
||||
modifier = Modifier.fillMaxWidth().focusRequester(fr)
|
||||
)
|
||||
} else {
|
||||
Text(stringResource(R.string.package_chooser))
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton({ onChoosePackage(null) }) {
|
||||
Icon(Icons.AutoMirrored.Default.ArrowBack, null)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.background)
|
||||
)
|
||||
}
|
||||
) { paddingValues->
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding())
|
||||
) {
|
||||
stickyHeader {
|
||||
AnimatedVisibility(progress < 1F) {
|
||||
LinearProgressIndicator(progress = { progress }, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
items(filteredPackages, { it.name }) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onChoosePackage(it.name) }
|
||||
.padding(horizontal = 8.dp, vertical = 10.dp)
|
||||
.animateItem()
|
||||
) {
|
||||
Image(
|
||||
painter = rememberDrawablePainter(it.icon), contentDescription = null,
|
||||
modifier = Modifier.padding(start = 12.dp, end = 18.dp).size(40.dp)
|
||||
)
|
||||
Column {
|
||||
Text(text = it.label, style = typography.titleLarge)
|
||||
Text(text = it.name, modifier = Modifier.alpha(0.8F))
|
||||
}
|
||||
}
|
||||
}
|
||||
item { Spacer(Modifier.padding(vertical = 30.dp)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
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
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
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.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
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
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.ui.NavIcon
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class PackageInfo(
|
||||
val pkgName: String,
|
||||
val label: String,
|
||||
val icon: Drawable,
|
||||
val system: Boolean
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PackageSelector(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
val context = LocalContext.current
|
||||
val pm = context.packageManager
|
||||
val flags = if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0
|
||||
val apps = pm.getInstalledApplications(flags)
|
||||
var progress by remember { mutableIntStateOf(0) }
|
||||
var show by remember { mutableStateOf(true) }
|
||||
var hideProgress by remember { mutableStateOf(true) }
|
||||
var system by remember { mutableStateOf(false) }
|
||||
var search by remember { mutableStateOf("") }
|
||||
var searchMode by remember { mutableStateOf(false) }
|
||||
val scrollState = rememberLazyListState()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val co = rememberCoroutineScope()
|
||||
suspend fun getPkgList() {
|
||||
show = false
|
||||
progress = 0
|
||||
hideProgress = false
|
||||
vm.installedPackages.clear()
|
||||
for(pkg in apps) {
|
||||
vm.installedPackages += PackageInfo(
|
||||
pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm),
|
||||
(pkg.flags and ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
)
|
||||
withContext(Dispatchers.Main) { progress += 1 }
|
||||
}
|
||||
show = true
|
||||
delay(500)
|
||||
hideProgress = true
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
actions = {
|
||||
if(!searchMode) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.search_fill0),
|
||||
contentDescription = "search",
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.clip(RoundedCornerShape(50))
|
||||
.clickable { searchMode = true }
|
||||
.padding(5.dp)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.filter_alt_fill0),
|
||||
contentDescription = "filter",
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.clip(RoundedCornerShape(50))
|
||||
.clickable {
|
||||
system = !system
|
||||
Toast.makeText(context, if(system) R.string.show_system_app else R.string.show_user_app, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
.padding(5.dp)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.refresh_fill0),
|
||||
contentDescription = "refresh",
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.clip(RoundedCornerShape(50))
|
||||
.clickable { co.launch { getPkgList() } }
|
||||
.padding(5.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
title = {
|
||||
if(searchMode) {
|
||||
val fr = FocusRequester()
|
||||
LaunchedEffect(Unit) { fr.requestFocus() }
|
||||
OutlinedTextField(
|
||||
value = search,
|
||||
onValueChange = { search = it },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
|
||||
placeholder = { Text(stringResource(R.string.search)) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.close_fill0),
|
||||
contentDescription = "clear search",
|
||||
modifier = Modifier.clickable {
|
||||
focusMgr.clearFocus()
|
||||
search = ""
|
||||
searchMode = false
|
||||
}
|
||||
)
|
||||
},
|
||||
textStyle = typography.bodyLarge,
|
||||
modifier = Modifier.fillMaxWidth().focusRequester(fr)
|
||||
)
|
||||
} else {
|
||||
Text(stringResource(R.string.pkg_selector))
|
||||
}
|
||||
},
|
||||
navigationIcon = { NavIcon{ navCtrl.navigateUp() } },
|
||||
colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.background)
|
||||
)
|
||||
}
|
||||
) { paddingValues->
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding()),
|
||||
state = scrollState
|
||||
) {
|
||||
items(1) {
|
||||
AnimatedVisibility(!hideProgress) {
|
||||
LinearProgressIndicator(progress = { progress.toFloat()/apps.size }, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
if(show) {
|
||||
items(vm.installedPackages) {
|
||||
if(system == it.system) {
|
||||
if(search != "") {
|
||||
if(it.pkgName.contains(search, ignoreCase = true) || it.label.contains(search, ignoreCase = true)) {
|
||||
PackageItem(it, navCtrl, vm)
|
||||
}
|
||||
} else {
|
||||
PackageItem(it, navCtrl, vm)
|
||||
}
|
||||
}
|
||||
}
|
||||
items(1) { Spacer(Modifier.padding(vertical = 30.dp)) }
|
||||
} else {
|
||||
items(1) {
|
||||
Spacer(Modifier.padding(top = 5.dp))
|
||||
Text(text = stringResource(R.string.loading), modifier = Modifier.alpha(0.8F))
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if(vm.installedPackages.isEmpty()) { getPkgList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PackageItem(pkg: PackageInfo, navCtrl: NavHostController, vm: MyViewModel) {
|
||||
val focusMgr = LocalFocusManager.current
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable{
|
||||
focusMgr.clearFocus()
|
||||
vm.selectedPackage.value = pkg.pkgName
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
.padding(horizontal = 8.dp, vertical = 10.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = rememberDrawablePainter(pkg.icon), contentDescription = "App icon",
|
||||
modifier = Modifier.padding(start = 12.dp, end = 18.dp).size(40.dp)
|
||||
)
|
||||
Column {
|
||||
Text(text = pkg.label, style = typography.titleLarge)
|
||||
Text(text = pkg.pkgName, modifier = Modifier.alpha(0.8F))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,29 +32,32 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.ui.FunctionItem
|
||||
import com.bintianqi.owndroid.ui.InfoCard
|
||||
import com.bintianqi.owndroid.ui.MyScaffold
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.security.SecureRandom
|
||||
|
||||
@Serializable object Settings
|
||||
|
||||
@Composable
|
||||
fun Settings(navCtrl: NavHostController) {
|
||||
MyScaffold(R.string.settings, 0.dp, navCtrl) {
|
||||
FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { navCtrl.navigate("Options") }
|
||||
FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { navCtrl.navigate("Appearance") }
|
||||
FunctionItem(title = R.string.security, icon = R.drawable.lock_fill0) { navCtrl.navigate("AuthSettings") }
|
||||
FunctionItem(title = R.string.api, icon = R.drawable.apps_fill0) { navCtrl.navigate("ApiSettings") }
|
||||
FunctionItem(title = R.string.about, icon = R.drawable.info_fill0) { navCtrl.navigate("About") }
|
||||
fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
MyScaffold(R.string.settings, 0.dp, onNavigateUp) {
|
||||
FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SettingsOptions) }
|
||||
FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { onNavigate(Appearance) }
|
||||
FunctionItem(title = R.string.security, icon = R.drawable.lock_fill0) { onNavigate(AuthSettings) }
|
||||
FunctionItem(title = R.string.api, icon = R.drawable.apps_fill0) { onNavigate(ApiSettings) }
|
||||
FunctionItem(title = R.string.about, icon = R.drawable.info_fill0) { onNavigate(About) }
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object SettingsOptions
|
||||
|
||||
@Composable
|
||||
fun SettingsOptions(navCtrl: NavHostController) {
|
||||
fun SettingsOptionsScreen(onNavigateUp: () -> Unit) {
|
||||
val sp = SharedPrefs(LocalContext.current)
|
||||
MyScaffold(R.string.options, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.options, 0.dp, onNavigateUp) {
|
||||
SwitchItem(
|
||||
R.string.show_dangerous_features, icon = R.drawable.warning_fill0,
|
||||
getState = { sp.displayDangerousFeatures },
|
||||
@@ -63,18 +66,19 @@ fun SettingsOptions(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object Appearance
|
||||
|
||||
@Composable
|
||||
fun Appearance(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
||||
fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) {
|
||||
var darkThemeMenu by remember { mutableStateOf(false) }
|
||||
val darkThemeTextID = when(theme.darkTheme) {
|
||||
1 -> R.string.on
|
||||
0 -> R.string.off
|
||||
else -> R.string.follow_system
|
||||
}
|
||||
MyScaffold(R.string.appearance, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.appearance, 0.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 31) {
|
||||
SwitchItem(R.string.material_you_color, state = theme.materialYou, onCheckedChange = { vm.theme.value = theme.copy(materialYou = it) })
|
||||
SwitchItem(R.string.material_you_color, state = theme.materialYou, onCheckedChange = { onThemeChange(theme.copy(materialYou = it)) })
|
||||
}
|
||||
Box {
|
||||
FunctionItem(R.string.dark_theme, stringResource(darkThemeTextID)) { darkThemeMenu = true }
|
||||
@@ -85,38 +89,40 @@ fun Appearance(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.follow_system)) },
|
||||
onClick = {
|
||||
vm.theme.value = theme.copy(darkTheme = -1)
|
||||
onThemeChange(theme.copy(darkTheme = -1))
|
||||
darkThemeMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.on)) },
|
||||
onClick = {
|
||||
vm.theme.value = theme.copy(darkTheme = 1)
|
||||
onThemeChange(theme.copy(darkTheme = 1))
|
||||
darkThemeMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.off)) },
|
||||
onClick = {
|
||||
vm.theme.value = theme.copy(darkTheme = 0)
|
||||
onThemeChange(theme.copy(darkTheme = 0))
|
||||
darkThemeMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())) {
|
||||
SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { vm.theme.value = theme.copy(blackTheme = it) })
|
||||
SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { onThemeChange(theme.copy(blackTheme = it)) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object AuthSettings
|
||||
|
||||
@Composable
|
||||
fun AuthSettings(navCtrl: NavHostController) {
|
||||
fun AuthSettingsScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val sp = SharedPrefs(context)
|
||||
var auth by remember{ mutableStateOf(sp.auth) }
|
||||
MyScaffold(R.string.security, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.security, 0.dp, onNavigateUp) {
|
||||
SwitchItem(
|
||||
R.string.lock_owndroid, state = auth,
|
||||
onCheckedChange = {
|
||||
@@ -146,11 +152,13 @@ fun AuthSettings(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object ApiSettings
|
||||
|
||||
@Composable
|
||||
fun ApiSettings(navCtrl: NavHostController) {
|
||||
fun ApiSettings(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val sp = SharedPrefs(context)
|
||||
MyScaffold(R.string.api, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.api, 8.dp, onNavigateUp) {
|
||||
var enabled by remember { mutableStateOf(sp.isApiEnabled) }
|
||||
SwitchItem(R.string.enable, state = enabled, onCheckedChange = {
|
||||
enabled = it
|
||||
@@ -188,13 +196,15 @@ fun ApiSettings(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object About
|
||||
|
||||
@Composable
|
||||
fun About(navCtrl: NavHostController) {
|
||||
fun AboutScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val pkgInfo = context.packageManager.getPackageInfo(context.packageName,0)
|
||||
val verCode = pkgInfo.versionCode
|
||||
val verName = pkgInfo.versionName
|
||||
MyScaffold(R.string.about, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.about, 0.dp, onNavigateUp) {
|
||||
Text(text = stringResource(R.string.app_name)+" v$verName ($verCode)", modifier = Modifier.padding(start = 16.dp))
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
FunctionItem(R.string.project_homepage, "GitHub", R.drawable.open_in_new) { shareLink(context, "https://github.com/BinTianqi/OwnDroid") }
|
||||
|
||||
@@ -6,13 +6,19 @@ import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.navigation.NavType
|
||||
import com.bintianqi.owndroid.dpm.addDeviceAdmin
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -22,6 +28,7 @@ import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
var zhCN = true
|
||||
|
||||
@@ -93,3 +100,22 @@ fun getContext(): Context {
|
||||
}
|
||||
|
||||
const val APK_MIME = "application/vnd.android.package-archive"
|
||||
|
||||
inline fun <reified T> serializableNavTypePair() =
|
||||
typeOf<T>() to object : NavType<T>(false) {
|
||||
override fun get(bundle: Bundle, key: String): T? =
|
||||
bundle.getString(key)?.let { parseValue(it) }
|
||||
override fun put(bundle: Bundle, key: String, value: T) =
|
||||
bundle.putString(key, serializeAsValue(value))
|
||||
override fun parseValue(value: String): T =
|
||||
Json.decodeFromString(value)
|
||||
override fun serializeAsValue(value: T): String =
|
||||
Json.encodeToString(value)
|
||||
}
|
||||
|
||||
class ChoosePackageContract: ActivityResultContract<Nothing?, String?>() {
|
||||
override fun createIntent(context: Context, input: Nothing?): Intent =
|
||||
Intent(context, PackageChooserActivity::class.java)
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): String? =
|
||||
intent?.getStringExtra("package")
|
||||
}
|
||||
|
||||
@@ -79,16 +79,14 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.bintianqi.owndroid.APK_MIME
|
||||
import com.bintianqi.owndroid.AppInstallerActivity
|
||||
import com.bintianqi.owndroid.AppInstallerViewModel
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.ChoosePackageContract
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.Animations
|
||||
import com.bintianqi.owndroid.ui.FunctionItem
|
||||
@@ -97,20 +95,20 @@ import com.bintianqi.owndroid.ui.ListItem
|
||||
import com.bintianqi.owndroid.ui.NavIcon
|
||||
import com.bintianqi.owndroid.ui.RadioButtonItem
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@Serializable
|
||||
object Applications
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ApplicationManage(navCtrl:NavHostController, vm: MyViewModel) {
|
||||
fun ApplicationsScreen(onNavigateUp: () -> Unit) {
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val localNavCtrl = rememberNavController()
|
||||
val navController = rememberNavController()
|
||||
var pkgName by rememberSaveable { mutableStateOf("") }
|
||||
val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(updatePackage) {
|
||||
if(updatePackage != "") {
|
||||
pkgName = updatePackage
|
||||
vm.selectedPackage.value = ""
|
||||
}
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) {result ->
|
||||
result?.let { pkgName = it }
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -129,7 +127,7 @@ fun ApplicationManage(navCtrl:NavHostController, vm: MyViewModel) {
|
||||
.clip(RoundedCornerShape(50))
|
||||
.clickable(onClick = {
|
||||
focusMgr.clearFocus()
|
||||
navCtrl.navigate("PackageSelector")
|
||||
choosePackage.launch(null)
|
||||
})
|
||||
.padding(3.dp))
|
||||
},
|
||||
@@ -137,35 +135,37 @@ fun ApplicationManage(navCtrl:NavHostController, vm: MyViewModel) {
|
||||
singleLine = true
|
||||
)
|
||||
},
|
||||
navigationIcon = { NavIcon { navCtrl.navigateUp() } },
|
||||
navigationIcon = { NavIcon(onNavigateUp) },
|
||||
colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.background)
|
||||
)
|
||||
}
|
||||
) { paddingValues->
|
||||
@Suppress("NewApi") NavHost(
|
||||
modifier = Modifier.padding(top = paddingValues.calculateTopPadding()),
|
||||
navController = localNavCtrl, startDestination = "Home",
|
||||
navController = navController, startDestination = Home,
|
||||
enterTransition = Animations.navHostEnterTransition,
|
||||
exitTransition = Animations.navHostExitTransition,
|
||||
popEnterTransition = Animations.navHostPopEnterTransition,
|
||||
popExitTransition = Animations.navHostPopExitTransition
|
||||
) {
|
||||
composable(route = "Home") { Home(localNavCtrl, pkgName) }
|
||||
composable(route = "UserControlDisabled") { UserCtrlDisabledPkg(pkgName) }
|
||||
composable(route = "PermissionManage") { PermissionManage(pkgName) }
|
||||
composable(route = "CrossProfilePackage") { CrossProfilePkg(pkgName) }
|
||||
composable(route = "CrossProfileWidget") { CrossProfileWidget(pkgName) }
|
||||
composable(route = "CredentialManagePolicy") { CredentialManagePolicy(pkgName) }
|
||||
composable(route = "Accessibility") { PermittedAccessibility(pkgName) }
|
||||
composable(route = "IME") { PermittedIME(pkgName) }
|
||||
composable(route = "KeepUninstalled") { KeepUninstalledApp(pkgName) }
|
||||
composable(route = "UninstallApp") { UninstallApp(pkgName) }
|
||||
composable<Home> { HomeScreen(pkgName) { navController.navigate(it) } }
|
||||
composable<UserControlDisabledPackages> { UserControlDisabledPackagesScreen(pkgName) }
|
||||
composable<PermissionManager> { PermissionManagerScreen(pkgName) }
|
||||
composable<CrossProfilePackages> { CrossProfilePackagesScreen(pkgName) }
|
||||
composable<CrossProfileWidgetProviders> { CrossProfileWidgetProvidersScreen(pkgName) }
|
||||
composable<CredentialManagerPolicy> { CredentialManagerPolicyScreen(pkgName) }
|
||||
composable<PermittedAccessibilityServices> { PermittedAccessibilityServicesScreen(pkgName) }
|
||||
composable<PermittedInputMethods> { PermittedInputMethodsScreen(pkgName) }
|
||||
composable<KeepUninstalledPackages> { KeepUninstalledPackagesScreen(pkgName) }
|
||||
composable<UninstallPackage> { UninstallPackageScreen(pkgName) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object Home
|
||||
|
||||
@Composable
|
||||
private fun Home(navCtrl:NavHostController, pkgName: String) {
|
||||
private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
|
||||
var dialogStatus by remember { mutableIntStateOf(0) }
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
@@ -231,27 +231,29 @@ private fun Home(navCtrl:NavHostController, pkgName: String) {
|
||||
onClickBlank = { appControlAction = 3; dialogStatus = 4 }
|
||||
)
|
||||
if(VERSION.SDK_INT >= 30 && (deviceOwner || (VERSION.SDK_INT >= 33 && profileOwner))) {
|
||||
FunctionItem(title = R.string.ucd, icon = R.drawable.do_not_touch_fill0) { navCtrl.navigate("UserControlDisabled") }
|
||||
FunctionItem(title = R.string.ucd, icon = R.drawable.do_not_touch_fill0) { onNavigate(UserControlDisabledPackages) }
|
||||
}
|
||||
if(VERSION.SDK_INT>=23) {
|
||||
FunctionItem(title = R.string.permission_manage, icon = R.drawable.key_fill0) { navCtrl.navigate("PermissionManage") }
|
||||
FunctionItem(title = R.string.permission_manage, icon = R.drawable.key_fill0) { onNavigate(PermissionManager) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 30 && profileOwner && dpm.isManagedProfile(receiver)) {
|
||||
FunctionItem(title = R.string.cross_profile_package, icon = R.drawable.work_fill0) { navCtrl.navigate("CrossProfilePackage") }
|
||||
FunctionItem(title = R.string.cross_profile_package, icon = R.drawable.work_fill0) { onNavigate(CrossProfilePackages) }
|
||||
}
|
||||
if(profileOwner) {
|
||||
FunctionItem(title = R.string.cross_profile_widget, icon = R.drawable.widgets_fill0) { navCtrl.navigate("CrossProfileWidget") }
|
||||
FunctionItem(title = R.string.cross_profile_widget, icon = R.drawable.widgets_fill0) { onNavigate(CrossProfileWidgetProviders) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 34 && deviceOwner) {
|
||||
FunctionItem(title = R.string.credential_manage_policy, icon = R.drawable.license_fill0) { navCtrl.navigate("CredentialManagePolicy") }
|
||||
FunctionItem(title = R.string.credential_manage_policy, icon = R.drawable.license_fill0) { onNavigate(CredentialManagerPolicy) }
|
||||
}
|
||||
FunctionItem(title = R.string.permitted_accessibility_services, icon = R.drawable.settings_accessibility_fill0) { navCtrl.navigate("Accessibility") }
|
||||
FunctionItem(title = R.string.permitted_ime, icon = R.drawable.keyboard_fill0) { navCtrl.navigate("IME") }
|
||||
FunctionItem(title = R.string.permitted_accessibility_services, icon = R.drawable.settings_accessibility_fill0) {
|
||||
onNavigate(PermittedAccessibilityServices)
|
||||
}
|
||||
FunctionItem(title = R.string.permitted_ime, icon = R.drawable.keyboard_fill0) { onNavigate(PermittedInputMethods) }
|
||||
FunctionItem(title = R.string.enable_system_app, icon = R.drawable.enable_fill0) {
|
||||
if(pkgName != "") dialogStatus = 1
|
||||
}
|
||||
if(VERSION.SDK_INT >= 28 && deviceOwner) {
|
||||
FunctionItem(title = R.string.keep_uninstalled_packages, icon = R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") }
|
||||
FunctionItem(title = R.string.keep_uninstalled_packages, icon = R.drawable.delete_fill0) { onNavigate(KeepUninstalledPackages) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 28) {
|
||||
FunctionItem(title = R.string.clear_app_storage, icon = R.drawable.mop_fill0) {
|
||||
@@ -267,7 +269,7 @@ private fun Home(navCtrl:NavHostController, pkgName: String) {
|
||||
FunctionItem(title = R.string.install_app, icon = R.drawable.install_mobile_fill0) {
|
||||
chooseApks.launch(APK_MIME)
|
||||
}
|
||||
FunctionItem(title = R.string.uninstall_app, icon = R.drawable.delete_fill0) { navCtrl.navigate("UninstallApp") }
|
||||
FunctionItem(title = R.string.uninstall_app, icon = R.drawable.delete_fill0) { onNavigate(UninstallPackage) }
|
||||
if(VERSION.SDK_INT >= 34 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
FunctionItem(title = R.string.set_default_dialer, icon = R.drawable.call_fill0) {
|
||||
if(pkgName != "") dialogStatus = 3
|
||||
@@ -425,10 +427,11 @@ private fun Home(navCtrl:NavHostController, pkgName: String) {
|
||||
LaunchedEffect(dialogStatus) { focusMgr.clearFocus() }
|
||||
}
|
||||
|
||||
@Serializable private object UserControlDisabledPackages
|
||||
|
||||
@RequiresApi(30)
|
||||
@Composable
|
||||
private fun UserCtrlDisabledPkg(pkgName:String) {
|
||||
private fun UserControlDisabledPackagesScreen(pkgName:String) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -469,9 +472,11 @@ private fun UserCtrlDisabledPkg(pkgName:String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object PermissionManager
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
private fun PermissionManage(pkgName: String) {
|
||||
private fun PermissionManagerScreen(pkgName: String) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -569,9 +574,11 @@ private fun PermissionManage(pkgName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object CrossProfilePackages
|
||||
|
||||
@RequiresApi(30)
|
||||
@Composable
|
||||
private fun CrossProfilePkg(pkgName: String) {
|
||||
private fun CrossProfilePackagesScreen(pkgName: String) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -610,8 +617,10 @@ private fun CrossProfilePkg(pkgName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object CrossProfileWidgetProviders
|
||||
|
||||
@Composable
|
||||
private fun CrossProfileWidget(pkgName: String) {
|
||||
private fun CrossProfileWidgetProvidersScreen(pkgName: String) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -649,9 +658,11 @@ private fun CrossProfileWidget(pkgName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object CredentialManagerPolicy
|
||||
|
||||
@RequiresApi(34)
|
||||
@Composable
|
||||
private fun CredentialManagePolicy(pkgName: String) {
|
||||
private fun CredentialManagerPolicyScreen(pkgName: String) { // TODO: rename "manage" to "manager"
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
var policy: PackagePolicy?
|
||||
@@ -716,8 +727,10 @@ private fun CredentialManagePolicy(pkgName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object PermittedAccessibilityServices
|
||||
|
||||
@Composable
|
||||
private fun PermittedAccessibility(pkgName: String) {
|
||||
private fun PermittedAccessibilityServicesScreen(pkgName: String) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -779,8 +792,10 @@ private fun PermittedAccessibility(pkgName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object PermittedInputMethods
|
||||
|
||||
@Composable
|
||||
private fun PermittedIME(pkgName: String) {
|
||||
private fun PermittedInputMethodsScreen(pkgName: String) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -835,9 +850,11 @@ private fun PermittedIME(pkgName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object KeepUninstalledPackages
|
||||
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
private fun KeepUninstalledApp(pkgName: String) {
|
||||
private fun KeepUninstalledPackagesScreen(pkgName: String) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -879,8 +896,10 @@ private fun KeepUninstalledApp(pkgName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable private object UninstallPackage
|
||||
|
||||
@Composable
|
||||
private fun UninstallApp(pkgName: String) {
|
||||
private fun UninstallPackageScreen(pkgName: String) {
|
||||
val context = LocalContext.current
|
||||
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
|
||||
Spacer(Modifier.padding(vertical = 10.dp))
|
||||
|
||||
@@ -108,7 +108,6 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
@@ -130,9 +129,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.ChoosePackageContract
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.SharedPrefs
|
||||
import com.bintianqi.owndroid.formatFileSize
|
||||
@@ -154,57 +151,62 @@ import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.InetAddress
|
||||
import kotlin.math.max
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
@Serializable object Network
|
||||
|
||||
@Composable
|
||||
fun Network(navCtrl:NavHostController) {
|
||||
fun NetworkScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
val profileOwner = context.isProfileOwner
|
||||
val dhizuku = SharedPrefs(context).dhizuku
|
||||
MyScaffold(R.string.network, 0.dp, navCtrl) {
|
||||
if(!dhizuku) FunctionItem(R.string.wifi, icon = R.drawable.wifi_fill0) { navCtrl.navigate("Wifi") }
|
||||
MyScaffold(R.string.network, 0.dp, onNavigateUp) {
|
||||
if(!dhizuku) FunctionItem(R.string.wifi, icon = R.drawable.wifi_fill0) { onNavigate(WiFi) }
|
||||
if(VERSION.SDK_INT >= 30) {
|
||||
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { navCtrl.navigate("NetworkOptions") }
|
||||
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(NetworkOptions) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner))
|
||||
FunctionItem(R.string.network_stats, icon = R.drawable.query_stats_fill0) { navCtrl.navigate("NetworkStats") }
|
||||
FunctionItem(R.string.network_stats, icon = R.drawable.query_stats_fill0) { onNavigate(QueryNetworkStats) }
|
||||
if(VERSION.SDK_INT >= 29 && deviceOwner) {
|
||||
FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { navCtrl.navigate("PrivateDNS") }
|
||||
FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { onNavigate(PrivateDns) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 24) {
|
||||
FunctionItem(R.string.always_on_vpn, icon = R.drawable.vpn_key_fill0) { navCtrl.navigate("AlwaysOnVpn") }
|
||||
FunctionItem(R.string.always_on_vpn, icon = R.drawable.vpn_key_fill0) { onNavigate(AlwaysOnVpnPackage) }
|
||||
}
|
||||
if(deviceOwner) {
|
||||
FunctionItem(R.string.recommended_global_proxy, icon = R.drawable.vpn_key_fill0) { navCtrl.navigate("RecommendedGlobalProxy") }
|
||||
FunctionItem(R.string.recommended_global_proxy, icon = R.drawable.vpn_key_fill0) { onNavigate(RecommendedGlobalProxy) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) {
|
||||
FunctionItem(R.string.network_logging, icon = R.drawable.description_fill0) { navCtrl.navigate("NetworkLog") }
|
||||
FunctionItem(R.string.network_logging, icon = R.drawable.description_fill0) { onNavigate(NetworkLogging) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 31) {
|
||||
FunctionItem(R.string.wifi_auth_keypair, icon = R.drawable.key_fill0) { navCtrl.navigate("WifiAuthKeypair") }
|
||||
FunctionItem(R.string.wifi_auth_keypair, icon = R.drawable.key_fill0) { onNavigate(WifiAuthKeypair) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 33) {
|
||||
FunctionItem(R.string.preferential_network_service, icon = R.drawable.globe_fill0) { navCtrl.navigate("PreferentialNetworkService") }
|
||||
FunctionItem(R.string.preferential_network_service, icon = R.drawable.globe_fill0) { onNavigate(PreferentialNetworkService) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 28 && deviceOwner) {
|
||||
FunctionItem(R.string.override_apn_settings, icon = R.drawable.cell_tower_fill0) { navCtrl.navigate("OverrideAPN") }
|
||||
FunctionItem(R.string.override_apn_settings, icon = R.drawable.cell_tower_fill0) { onNavigate(OverrideApn) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object NetworkOptions
|
||||
|
||||
@Composable
|
||||
fun NetworkOptions(navCtrl: NavHostController) {
|
||||
fun NetworkOptionsScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.options, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.options, 0.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT>=30 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
SwitchItem(R.string.lockdown_admin_configured_network, icon = R.drawable.wifi_password_fill0,
|
||||
getState = { dpm.hasLockdownAdminConfiguredNetworks(receiver) }, onCheckedChange = { dpm.setConfiguredNetworksLockdownState(receiver,it) },
|
||||
@@ -221,9 +223,11 @@ fun NetworkOptions(navCtrl: NavHostController) {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object WiFi
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Wifi(navCtrl: NavHostController) {
|
||||
fun WifiScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateToUpdateNetwork: (Bundle) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val coroutine = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState { 3 }
|
||||
@@ -233,7 +237,7 @@ fun Wifi(navCtrl: NavHostController) {
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.wifi)) },
|
||||
navigationIcon = { NavIcon { navCtrl.navigateUp() } }
|
||||
navigationIcon = { NavIcon(onNavigateUp) }
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
@@ -295,14 +299,14 @@ fun Wifi(navCtrl: NavHostController) {
|
||||
FunctionItem(R.string.wifi_mac_address) { wifiMacDialog = true }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 33 && (deviceOwner || orgProfileOwner)) {
|
||||
FunctionItem(R.string.min_wifi_security_level) { navCtrl.navigate("MinWifiSecurityLevel") }
|
||||
FunctionItem(R.string.wifi_ssid_policy) { navCtrl.navigate("WifiSsidPolicy") }
|
||||
FunctionItem(R.string.min_wifi_security_level) { onNavigate(WifiSecurityLevel) }
|
||||
FunctionItem(R.string.wifi_ssid_policy) { onNavigate(WifiSsidPolicyScreen) }
|
||||
}
|
||||
}
|
||||
} else if(page == 1) {
|
||||
SavedNetworks(navCtrl)
|
||||
SavedNetworks(onNavigateToUpdateNetwork)
|
||||
} else {
|
||||
AddNetwork()
|
||||
AddNetworkScreen(null) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,7 +338,7 @@ fun Wifi(navCtrl: NavHostController) {
|
||||
@Suppress("DEPRECATION")
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
private fun SavedNetworks(navCtrl: NavHostController) {
|
||||
private fun SavedNetworks(onNavigateToUpdateNetwork: (Bundle) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
val configuredNetworks = remember { mutableStateListOf<WifiConfiguration>() }
|
||||
@@ -431,9 +435,7 @@ private fun SavedNetworks(navCtrl: NavHostController) {
|
||||
Button(
|
||||
onClick = {
|
||||
networkDetailsDialog = -1
|
||||
val dest = navCtrl.graph.findNode("UpdateNetwork")
|
||||
if(dest != null)
|
||||
navCtrl.navigate(dest.id, bundleOf("wifi_configuration" to network))
|
||||
onNavigateToUpdateNetwork(bundleOf("wifi_configuration" to network))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
@@ -461,17 +463,20 @@ private fun SavedNetworks(navCtrl: NavHostController) {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
object AddNetwork
|
||||
|
||||
@Composable
|
||||
fun UpdateNetwork(arguments: Bundle, navCtrl: NavHostController) {
|
||||
MyScaffold(R.string.update_network, 0.dp, navCtrl, false) {
|
||||
AddNetwork(arguments.getParcelable("wifi_configuration"), navCtrl)
|
||||
fun AddNetworkScreen(data: Bundle, onNavigateUp: () -> Unit) {
|
||||
MyScaffold(R.string.update_network, 0.dp, onNavigateUp, false) {
|
||||
AddNetworkScreen(data.getParcelable("wifi_configuration"), onNavigateUp)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AddNetwork(wifiConfig: WifiConfiguration? = null, navCtrl: NavHostController? = null) {
|
||||
private fun AddNetworkScreen(wifiConfig: WifiConfiguration? = null, onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
var resultDialog by remember { mutableStateOf(false) }
|
||||
var createdNetworkId by remember { mutableIntStateOf(-1) }
|
||||
@@ -721,7 +726,7 @@ private fun AddNetwork(wifiConfig: WifiConfiguration? = null, navCtrl: NavHostCo
|
||||
TextButton(
|
||||
onClick = {
|
||||
resultDialog = false
|
||||
if(createdNetworkId != -1) navCtrl?.navigateUp()
|
||||
if(createdNetworkId != -1) onNavigateUp()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.confirm))
|
||||
@@ -732,14 +737,16 @@ private fun AddNetwork(wifiConfig: WifiConfiguration? = null, navCtrl: NavHostCo
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object WifiSecurityLevel
|
||||
|
||||
@RequiresApi(33)
|
||||
@Composable
|
||||
fun WifiSecurityLevel(navCtrl: NavHostController) {
|
||||
fun WifiSecurityLevelScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
var selectedWifiSecLevel by remember { mutableIntStateOf(0) }
|
||||
LaunchedEffect(Unit) { selectedWifiSecLevel = dpm.minimumRequiredWifiSecurityLevel }
|
||||
MyScaffold(R.string.min_wifi_security_level, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.min_wifi_security_level, 8.dp, onNavigateUp) {
|
||||
RadioButtonItem(R.string.wifi_security_open, selectedWifiSecLevel == WIFI_SECURITY_OPEN) { selectedWifiSecLevel = WIFI_SECURITY_OPEN }
|
||||
RadioButtonItem("WEP, WPA(2)-PSK", selectedWifiSecLevel == WIFI_SECURITY_PERSONAL) { selectedWifiSecLevel = WIFI_SECURITY_PERSONAL }
|
||||
RadioButtonItem("WPA-EAP", selectedWifiSecLevel == WIFI_SECURITY_ENTERPRISE_EAP) { selectedWifiSecLevel = WIFI_SECURITY_ENTERPRISE_EAP }
|
||||
@@ -758,13 +765,15 @@ fun WifiSecurityLevel(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object WifiSsidPolicyScreen
|
||||
|
||||
@RequiresApi(33)
|
||||
@Composable
|
||||
fun WifiSsidPolicy(navCtrl: NavHostController) {
|
||||
fun WifiSsidPolicyScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
MyScaffold(R.string.wifi_ssid_policy, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.wifi_ssid_policy, 8.dp, onNavigateUp) {
|
||||
var selectedPolicyType by remember { mutableIntStateOf(-1) }
|
||||
val ssidList = remember { mutableStateListOf<WifiSsid>() }
|
||||
val refreshPolicy = {
|
||||
@@ -862,10 +871,12 @@ fun NetworkStats.toBucketList(): List<NetworkStats.Bucket> {
|
||||
return list
|
||||
}
|
||||
|
||||
@Serializable object QueryNetworkStats
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
fun NetworkStats(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkStatsViewer) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
val nsm = context.getSystemService(NetworkStatsManager::class.java)
|
||||
@@ -884,7 +895,7 @@ fun NetworkStats(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
val endTimeTextFieldInteractionSource = remember { MutableInteractionSource() }
|
||||
if(startTimeTextFieldInteractionSource.collectIsPressedAsState().value) activeTextField = NetworkStatsActiveTextField.StartTime
|
||||
if(endTimeTextFieldInteractionSource.collectIsPressedAsState().value) activeTextField = NetworkStatsActiveTextField.EndTime
|
||||
MyScaffold(R.string.network_stats, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.network_stats, 8.dp, onNavigateUp) {
|
||||
ExposedDropdownMenuBox(
|
||||
activeTextField == NetworkStatsActiveTextField.Type,
|
||||
{ activeTextField = if(it) NetworkStatsActiveTextField.Type else NetworkStatsActiveTextField.Type }
|
||||
@@ -1028,12 +1039,12 @@ fun NetworkStats(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
var uidText by rememberSaveable { mutableStateOf(context.getString(NetworkStatsUID.All.strRes)) }
|
||||
var readOnly by rememberSaveable { mutableStateOf(true) }
|
||||
if(!readOnly && uidText.toIntOrNull() != null) uid = uidText.toInt()
|
||||
if(VERSION.SDK_INT >= 24) {
|
||||
val selectedPackage by vm.selectedPackage.collectAsStateWithLifecycle()
|
||||
if(readOnly && selectedPackage != "") {
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) {
|
||||
it ?: return@rememberLauncherForActivityResult
|
||||
if(VERSION.SDK_INT >= 24 && readOnly) {
|
||||
try {
|
||||
uid = context.packageManager.getPackageUid(selectedPackage, 0)
|
||||
uidText = "$selectedPackage ($uid)"
|
||||
uid = context.packageManager.getPackageUid(it, 0)
|
||||
uidText = "$it ($uid)"
|
||||
} catch(_: NameNotFoundException) {
|
||||
context.showOperationResultToast(false)
|
||||
}
|
||||
@@ -1066,8 +1077,8 @@ fun NetworkStats(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
text = { Text(stringResource(R.string.choose_an_app)) },
|
||||
onClick = {
|
||||
readOnly = true
|
||||
navCtrl.navigate("PackageSelector")
|
||||
activeTextField = NetworkStatsActiveTextField.None
|
||||
choosePackage.launch(null)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
@@ -1186,28 +1197,18 @@ fun NetworkStats(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
} else {
|
||||
val bundle = Bundle()
|
||||
bundle.putInt("size", buckets.size)
|
||||
buckets.forEachIndexed { index, bucket ->
|
||||
val subBundle = bundleOf(
|
||||
"rx_bytes" to bucket.rxBytes,
|
||||
"rx_packets" to bucket.rxPackets,
|
||||
"tx_bytes" to bucket.txBytes,
|
||||
"tx_packets" to bucket.txPackets,
|
||||
"uid" to bucket.uid,
|
||||
"state" to bucket.state,
|
||||
"start_time" to bucket.startTimeStamp,
|
||||
"end_time" to bucket.endTimeStamp
|
||||
val stats = buckets.map {
|
||||
NetworkStatsViewer.Data(
|
||||
it.rxBytes, it.rxPackets, it.txBytes, it.txPackets,
|
||||
it.uid, it.state, it.startTimeStamp, it.endTimeStamp,
|
||||
if(VERSION.SDK_INT >= 24) it.tag else null,
|
||||
if(VERSION.SDK_INT >= 24) it.roaming else null,
|
||||
if(VERSION.SDK_INT >= 26) it.metered else null
|
||||
)
|
||||
if(VERSION.SDK_INT >= 24) {
|
||||
subBundle.putInt("tag", bucket.tag)
|
||||
subBundle.putInt("roaming", bucket.roaming)
|
||||
}
|
||||
if(VERSION.SDK_INT >= 26) subBundle.putInt("metered", bucket.metered)
|
||||
bundle.putBundle(index.toString(), subBundle)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
querying = false
|
||||
val nodeId = navCtrl.graph.findNode("NetworkStatsViewer")?.id
|
||||
if(nodeId != null) navCtrl.navigate(nodeId, bundle)
|
||||
onNavigateToViewer(NetworkStatsViewer(stats))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1245,12 +1246,32 @@ fun NetworkStats(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class NetworkStatsViewer(
|
||||
val stats: List<Data>
|
||||
) {
|
||||
@Serializable
|
||||
data class Data(
|
||||
val rxBytes: Long,
|
||||
val rxPackets: Long,
|
||||
val txBytes: Long,
|
||||
val txPackets: Long,
|
||||
val uid: Int,
|
||||
val state: Int,
|
||||
val startTime: Long,
|
||||
val endTime: Long,
|
||||
val tag: Int?,
|
||||
val roaming: Int?,
|
||||
val metered: Int?
|
||||
)
|
||||
}
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
fun NetworkStatsViewer(navCtrl: NavHostController, navArgs: Bundle) {
|
||||
fun NetworkStatsViewerScreen(nsv: NetworkStatsViewer, onNavigateUp: () -> Unit) {
|
||||
var index by remember { mutableIntStateOf(0) }
|
||||
val size = navArgs.getInt("size", 1)
|
||||
MyScaffold(R.string.place_holder, 8.dp, navCtrl, false) {
|
||||
val size = nsv.stats.size
|
||||
MyScaffold(R.string.place_holder, 8.dp, onNavigateUp, false) {
|
||||
if(size > 1) Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally).padding(bottom = 8.dp)
|
||||
@@ -1269,70 +1290,75 @@ fun NetworkStatsViewer(navCtrl: NavHostController, navArgs: Bundle) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Default.KeyboardArrowRight, contentDescription = null)
|
||||
}
|
||||
}
|
||||
val data = navArgs.getBundle(index.toString())!!
|
||||
val data = nsv.stats[index]
|
||||
Text(
|
||||
data.getLong("start_time").humanReadableDate + " ~ " + data.getLong("end_time").humanReadableDate,
|
||||
data.startTime.humanReadableDate + " ~ " + data.endTime.humanReadableDate,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally).padding(bottom = 8.dp)
|
||||
)
|
||||
val txBytes = data.getLong("tx_bytes")
|
||||
val txBytes = data.txBytes
|
||||
Text(stringResource(R.string.transmitted), style = typography.titleLarge)
|
||||
Column(modifier = Modifier.padding(start = 8.dp, bottom = 4.dp)) {
|
||||
Text("$txBytes bytes")
|
||||
Text(formatFileSize(txBytes))
|
||||
Text(data.getLong("tx_packets").toString() + " packets")
|
||||
Text(data.txPackets.toString() + " packets")
|
||||
}
|
||||
val rxBytes = data.getLong("rx_bytes")
|
||||
val rxBytes = data.rxBytes
|
||||
Text(stringResource(R.string.received), style = typography.titleLarge)
|
||||
Column(modifier = Modifier.padding(start = 8.dp, bottom = 8.dp)) {
|
||||
Text("$rxBytes bytes")
|
||||
Text(formatFileSize(rxBytes))
|
||||
Text(data.getLong("rx_packets").toString() + " packets")
|
||||
Text(data.rxPackets.toString() + " packets")
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val textMap = mapOf(
|
||||
NetworkStats.Bucket.STATE_ALL to R.string.all,
|
||||
NetworkStats.Bucket.STATE_DEFAULT to R.string.default_str,
|
||||
NetworkStats.Bucket.STATE_FOREGROUND to R.string.foreground
|
||||
)
|
||||
val text = when(data.state) {
|
||||
NetworkStats.Bucket.STATE_ALL -> R.string.all
|
||||
NetworkStats.Bucket.STATE_DEFAULT -> R.string.default_str
|
||||
NetworkStats.Bucket.STATE_FOREGROUND -> R.string.foreground
|
||||
else -> R.string.unknown
|
||||
}
|
||||
Text(stringResource(R.string.state), style = typography.titleMedium, modifier = Modifier.padding(end = 8.dp))
|
||||
Text(stringResource(textMap[data.getInt("state")] ?: R.string.unknown))
|
||||
Text(stringResource(text))
|
||||
}
|
||||
if(VERSION.SDK_INT >= 24) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val tag = data.getInt("tag")
|
||||
val tag = data.tag
|
||||
Text(stringResource(R.string.tag), style = typography.titleMedium, modifier = Modifier.padding(end = 8.dp))
|
||||
Text(if(tag == NetworkStats.Bucket.TAG_NONE) stringResource(R.string.all) else tag.toString())
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val textMap = mapOf(
|
||||
NetworkStats.Bucket.ROAMING_ALL to R.string.all,
|
||||
NetworkStats.Bucket.ROAMING_YES to R.string.yes,
|
||||
NetworkStats.Bucket.ROAMING_NO to R.string.no
|
||||
)
|
||||
val text = when(data.roaming) {
|
||||
NetworkStats.Bucket.ROAMING_ALL -> R.string.all
|
||||
NetworkStats.Bucket.ROAMING_YES -> R.string.yes
|
||||
NetworkStats.Bucket.ROAMING_NO -> R.string.no
|
||||
else -> R.string.unknown
|
||||
}
|
||||
Text(stringResource(R.string.roaming), style = typography.titleMedium, modifier = Modifier.padding(end = 8.dp))
|
||||
Text(stringResource(textMap[data.getInt("roaming")] ?: R.string.unknown))
|
||||
Text(stringResource(text))
|
||||
}
|
||||
}
|
||||
if(VERSION.SDK_INT >= 26) Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val textMap = mapOf(
|
||||
NetworkStats.Bucket.METERED_ALL to R.string.all,
|
||||
NetworkStats.Bucket.METERED_YES to R.string.yes,
|
||||
NetworkStats.Bucket.METERED_NO to R.string.no
|
||||
)
|
||||
val text = when(data.metered) {
|
||||
NetworkStats.Bucket.METERED_ALL -> R.string.all
|
||||
NetworkStats.Bucket.METERED_YES -> R.string.yes
|
||||
NetworkStats.Bucket.METERED_NO -> R.string.no
|
||||
else -> R.string.unknown
|
||||
}
|
||||
Text(stringResource(R.string.metered), style = typography.titleMedium, modifier = Modifier.padding(end = 8.dp))
|
||||
Text(stringResource(textMap[data.getInt("metered")] ?: R.string.unknown))
|
||||
Text(stringResource(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object PrivateDns
|
||||
|
||||
@RequiresApi(29)
|
||||
@Composable
|
||||
fun PrivateDNS(navCtrl: NavHostController) {
|
||||
fun PrivateDnsScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
MyScaffold(R.string.private_dns, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.private_dns, 8.dp, onNavigateUp) {
|
||||
val dnsStatus = mapOf(
|
||||
PRIVATE_DNS_MODE_UNKNOWN to stringResource(R.string.unknown),
|
||||
PRIVATE_DNS_MODE_OFF to stringResource(R.string.disabled),
|
||||
@@ -1397,9 +1423,11 @@ fun PrivateDNS(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object AlwaysOnVpnPackage
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
fun AlwaysOnVpnPackageScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -1408,12 +1436,8 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val refresh = { pkgName = dpm.getAlwaysOnVpnPackage(receiver) ?: "" }
|
||||
LaunchedEffect(Unit) { refresh() }
|
||||
val updatePackage by vm.selectedPackage.collectAsState()
|
||||
LaunchedEffect(updatePackage) {
|
||||
if(updatePackage != "") {
|
||||
pkgName = updatePackage
|
||||
vm.selectedPackage.value = ""
|
||||
}
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
||||
result?.let { pkgName = it }
|
||||
}
|
||||
val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean ->
|
||||
try {
|
||||
@@ -1430,7 +1454,7 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
false
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.always_on_vpn, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.always_on_vpn, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = pkgName,
|
||||
onValueChange = { pkgName = it },
|
||||
@@ -1441,10 +1465,7 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(50))
|
||||
.clickable(onClick = {
|
||||
focusMgr.clearFocus()
|
||||
navCtrl.navigate("PackageSelector")
|
||||
})
|
||||
.clickable { choosePackage.launch(null) }
|
||||
.padding(3.dp))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
|
||||
@@ -1468,8 +1489,10 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object RecommendedGlobalProxy
|
||||
|
||||
@Composable
|
||||
fun RecommendedGlobalProxy(navCtrl: NavHostController) {
|
||||
fun RecommendedGlobalProxyScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -1479,7 +1502,7 @@ fun RecommendedGlobalProxy(navCtrl: NavHostController) {
|
||||
var specifyPort by remember { mutableStateOf(false) }
|
||||
var proxyPort by remember { mutableStateOf("") }
|
||||
var exclList by remember { mutableStateOf("") }
|
||||
MyScaffold(R.string.recommended_global_proxy, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.recommended_global_proxy, 8.dp, onNavigateUp) {
|
||||
RadioButtonItem(R.string.proxy_type_off, proxyType == 0) { proxyType = 0 }
|
||||
RadioButtonItem(R.string.proxy_type_pac, proxyType == 1) { proxyType = 1 }
|
||||
RadioButtonItem(R.string.proxy_type_direct, proxyType == 2) { proxyType = 2 }
|
||||
@@ -1564,9 +1587,11 @@ fun RecommendedGlobalProxy(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object NetworkLogging
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
fun NetworkLogging(navCtrl: NavHostController) {
|
||||
fun NetworkLoggingScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -1583,7 +1608,7 @@ fun NetworkLogging(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.network_logging, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.network_logging, 8.dp, onNavigateUp) {
|
||||
SwitchItem(
|
||||
R.string.enable,
|
||||
getState = { dpm.isNetworkLoggingEnabled(receiver) },
|
||||
@@ -1620,14 +1645,16 @@ fun NetworkLogging(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object WifiAuthKeypair
|
||||
|
||||
@RequiresApi(31)
|
||||
@Composable
|
||||
fun WifiAuthKeypair(navCtrl: NavHostController) {
|
||||
fun WifiAuthKeypairScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var keyPair by remember { mutableStateOf("") }
|
||||
MyScaffold(R.string.wifi_auth_keypair, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.wifi_auth_keypair, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = keyPair,
|
||||
label = { Text(stringResource(R.string.alias)) },
|
||||
@@ -1662,9 +1689,11 @@ fun WifiAuthKeypair(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object PreferentialNetworkService
|
||||
|
||||
@RequiresApi(33)
|
||||
@Composable
|
||||
fun PreferentialNetworkService(navCtrl: NavHostController) {
|
||||
fun PreferentialNetworkServiceScreen(onNavigateUp: () -> Unit) {
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
@@ -1703,7 +1732,7 @@ fun PreferentialNetworkService(navCtrl: NavHostController) {
|
||||
refresh()
|
||||
}
|
||||
LaunchedEffect(Unit) { initialize() }
|
||||
MyScaffold(R.string.preferential_network_service, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.preferential_network_service, 8.dp, onNavigateUp) {
|
||||
SwitchItem(R.string.enabled, state = masterEnabled, onCheckedChange = { masterEnabled = it }, padding = false)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceAround,
|
||||
@@ -1814,9 +1843,11 @@ fun PreferentialNetworkService(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object OverrideApn
|
||||
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun OverrideAPN(navCtrl: NavHostController) {
|
||||
fun OverrideApnScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -1825,7 +1856,7 @@ fun OverrideAPN(navCtrl: NavHostController) {
|
||||
var inputNum by remember { mutableStateOf("0") }
|
||||
var nextStep by remember { mutableStateOf(false) }
|
||||
val builder = Builder()
|
||||
MyScaffold(R.string.override_apn_settings, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.override_apn_settings, 8.dp, onNavigateUp) {
|
||||
Text(text = stringResource(id = R.string.developing))
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
SwitchItem(
|
||||
|
||||
@@ -78,30 +78,33 @@ import com.bintianqi.owndroid.ui.InfoCard
|
||||
import com.bintianqi.owndroid.ui.MyScaffold
|
||||
import com.bintianqi.owndroid.ui.RadioButtonItem
|
||||
import com.bintianqi.owndroid.yesOrNo
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable object Password
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Composable
|
||||
fun Password(navCtrl: NavHostController) {
|
||||
fun PasswordScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val deviceAdmin = context.isDeviceAdmin
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
val profileOwner = context.isProfileOwner
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.password_and_keyguard, 0.dp, navCtrl) {
|
||||
FunctionItem(R.string.password_info, icon = R.drawable.info_fill0) { navCtrl.navigate("PasswordInfo") }
|
||||
MyScaffold(R.string.password_and_keyguard, 0.dp, onNavigateUp) {
|
||||
FunctionItem(R.string.password_info, icon = R.drawable.info_fill0) { onNavigate(PasswordInfo) }
|
||||
if(SharedPrefs(context).displayDangerousFeatures) {
|
||||
if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) {
|
||||
FunctionItem(R.string.reset_password_token, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("ResetPasswordToken") }
|
||||
FunctionItem(R.string.reset_password_token, icon = R.drawable.key_vertical_fill0) { onNavigate(ResetPasswordToken) }
|
||||
}
|
||||
if(deviceAdmin || deviceOwner || profileOwner) {
|
||||
FunctionItem(R.string.reset_password, icon = R.drawable.lock_reset_fill0) { navCtrl.navigate("ResetPassword") }
|
||||
FunctionItem(R.string.reset_password, icon = R.drawable.lock_reset_fill0) { onNavigate(ResetPassword) }
|
||||
}
|
||||
}
|
||||
if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) {
|
||||
FunctionItem(R.string.required_password_complexity, icon = R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordComplexity") }
|
||||
FunctionItem(R.string.required_password_complexity, icon = R.drawable.password_fill0) { onNavigate(RequiredPasswordComplexity) }
|
||||
}
|
||||
if(deviceAdmin) {
|
||||
FunctionItem(R.string.disable_keyguard_features, icon = R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("DisableKeyguardFeatures") }
|
||||
FunctionItem(R.string.disable_keyguard_features, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(KeyguardDisabledFeatures) }
|
||||
}
|
||||
if(deviceOwner) {
|
||||
FunctionItem(R.string.max_time_to_lock, icon = R.drawable.schedule_fill0) { dialog = 1 }
|
||||
@@ -115,7 +118,7 @@ fun Password(navCtrl: NavHostController) {
|
||||
FunctionItem(R.string.pwd_history, icon = R.drawable.history_fill0) { dialog = 5 }
|
||||
}
|
||||
if(VERSION.SDK_INT < 31 && (deviceOwner || profileOwner)) {
|
||||
FunctionItem(R.string.required_password_quality, icon = R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordQuality") }
|
||||
FunctionItem(R.string.required_password_quality, icon = R.drawable.password_fill0) { onNavigate(RequiredPasswordQuality) }
|
||||
}
|
||||
}
|
||||
if(dialog != 0) {
|
||||
@@ -207,14 +210,16 @@ fun Password(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object PasswordInfo
|
||||
|
||||
@Composable
|
||||
fun PasswordInfo(navCtrl: NavHostController) {
|
||||
fun PasswordInfoScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
val profileOwner = context.isProfileOwner
|
||||
MyScaffold(R.string.password_info, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.password_info, 8.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 29) {
|
||||
val passwordComplexity = mapOf(
|
||||
PASSWORD_COMPLEXITY_NONE to R.string.password_complexity_none,
|
||||
@@ -233,16 +238,18 @@ fun PasswordInfo(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object ResetPasswordToken
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
fun ResetPasswordToken(navCtrl: NavHostController) {
|
||||
fun ResetPasswordTokenScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
var token by remember { mutableStateOf("") }
|
||||
val tokenByteArray = token.toByteArray()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
MyScaffold(R.string.reset_password_token, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.reset_password_token, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = token, onValueChange = { token = it },
|
||||
label = { Text(stringResource(R.string.token)) },
|
||||
@@ -295,8 +302,10 @@ fun ResetPasswordToken(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object ResetPassword
|
||||
|
||||
@Composable
|
||||
fun ResetPassword(navCtrl: NavHostController) {
|
||||
fun ResetPasswordScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -307,7 +316,7 @@ fun ResetPassword(navCtrl: NavHostController) {
|
||||
val tokenByteArray = token.toByteArray()
|
||||
var flag by remember { mutableIntStateOf(0) }
|
||||
var confirmDialog by remember { mutableStateOf(false) }
|
||||
MyScaffold(R.string.reset_password, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.reset_password, 8.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 26) {
|
||||
OutlinedTextField(
|
||||
value = token, onValueChange = { token = it },
|
||||
@@ -413,9 +422,11 @@ fun ResetPassword(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object RequiredPasswordComplexity
|
||||
|
||||
@RequiresApi(31)
|
||||
@Composable
|
||||
fun PasswordComplexity(navCtrl: NavHostController) {
|
||||
fun RequiredPasswordComplexityScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val passwordComplexity = mapOf(
|
||||
@@ -426,7 +437,7 @@ fun PasswordComplexity(navCtrl: NavHostController) {
|
||||
)
|
||||
var selectedItem by remember { mutableIntStateOf(PASSWORD_COMPLEXITY_NONE) }
|
||||
LaunchedEffect(Unit) { selectedItem = dpm.requiredPasswordComplexity }
|
||||
MyScaffold(R.string.required_password_complexity, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.required_password_complexity, 8.dp, onNavigateUp) {
|
||||
passwordComplexity.forEach {
|
||||
RadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key }
|
||||
}
|
||||
@@ -450,8 +461,10 @@ fun PasswordComplexity(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object KeyguardDisabledFeatures
|
||||
|
||||
@Composable
|
||||
fun DisableKeyguardFeatures(navCtrl: NavHostController) {
|
||||
fun KeyguardDisabledFeaturesScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -481,7 +494,7 @@ fun DisableKeyguardFeatures(navCtrl: NavHostController) {
|
||||
}
|
||||
LaunchedEffect(mode) { if(mode != 2) flag = dpm.getKeyguardDisabledFeatures(receiver) }
|
||||
LaunchedEffect(Unit) { refresh() }
|
||||
MyScaffold(R.string.disable_keyguard_features, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.disable_keyguard_features, 8.dp, onNavigateUp) {
|
||||
RadioButtonItem(R.string.enable_all, mode == 0) { mode = 0 }
|
||||
RadioButtonItem(R.string.disable_all, mode == 1) { mode = 1 }
|
||||
RadioButtonItem(R.string.custom, mode == 2) { mode = 2 }
|
||||
@@ -509,8 +522,10 @@ fun DisableKeyguardFeatures(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object RequiredPasswordQuality
|
||||
|
||||
@Composable
|
||||
fun PasswordQuality(navCtrl: NavHostController) {
|
||||
fun RequiredPasswordQualityScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -525,7 +540,7 @@ fun PasswordQuality(navCtrl: NavHostController) {
|
||||
)
|
||||
var selectedItem by remember { mutableIntStateOf(PASSWORD_QUALITY_UNSPECIFIED) }
|
||||
LaunchedEffect(Unit) { selectedItem=dpm.getPasswordQuality(receiver) }
|
||||
MyScaffold(R.string.required_password_quality, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.required_password_quality, 8.dp, onNavigateUp) {
|
||||
passwordQuality.forEach {
|
||||
RadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key }
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Binder
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.os.UserManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
@@ -41,10 +43,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.ChoosePackageContract
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.SharedPrefs
|
||||
import com.bintianqi.owndroid.backToHomeStateFlow
|
||||
@@ -55,12 +54,15 @@ import com.bintianqi.owndroid.yesOrNo
|
||||
import com.rosan.dhizuku.api.Dhizuku
|
||||
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.sui.Sui
|
||||
|
||||
@Serializable object Permissions
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Composable
|
||||
fun Permissions(navCtrl: NavHostController) {
|
||||
fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateToShizuku: (Bundle) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -71,7 +73,7 @@ fun Permissions(navCtrl: NavHostController) {
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
var bindingShizuku by remember { mutableStateOf(false) }
|
||||
val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) dpm.enrollmentSpecificId else ""
|
||||
MyScaffold(R.string.permissions, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.permissions, 0.dp, onNavigateUp) {
|
||||
if(!dpm.isDeviceOwnerApp(context.packageName)) {
|
||||
SwitchItem(
|
||||
R.string.dhizuku,
|
||||
@@ -82,18 +84,18 @@ fun Permissions(navCtrl: NavHostController) {
|
||||
}
|
||||
FunctionItem(
|
||||
R.string.device_admin, stringResource(if(deviceAdmin) R.string.activated else R.string.deactivated),
|
||||
operation = { navCtrl.navigate("DeviceAdmin") }
|
||||
operation = { onNavigate(DeviceAdmin) }
|
||||
)
|
||||
if(profileOwner || !userManager.isSystemUser) {
|
||||
FunctionItem(
|
||||
R.string.profile_owner, stringResource(if(profileOwner) R.string.activated else R.string.deactivated),
|
||||
operation = { navCtrl.navigate("ProfileOwner") }
|
||||
operation = { onNavigate(ProfileOwner) }
|
||||
)
|
||||
}
|
||||
if(!profileOwner && userManager.isSystemUser) {
|
||||
FunctionItem(
|
||||
R.string.device_owner, stringResource(if(deviceOwner) R.string.activated else R.string.deactivated),
|
||||
operation = { navCtrl.navigate("DeviceOwner") }
|
||||
operation = { onNavigate(DeviceOwner) }
|
||||
)
|
||||
}
|
||||
FunctionItem(R.string.shizuku) {
|
||||
@@ -101,9 +103,8 @@ fun Permissions(navCtrl: NavHostController) {
|
||||
if(Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
|
||||
bindingShizuku = true
|
||||
fun onBind(binder: IBinder) {
|
||||
val destinationId = navCtrl.graph.findNode("Shizuku")!!.id
|
||||
bindingShizuku = false
|
||||
navCtrl.navigate(destinationId, bundleOf("binder" to binder), NavOptions.Builder().setLaunchSingleTop(true).build())
|
||||
onNavigateToShizuku(bundleOf("binder" to binder))
|
||||
}
|
||||
try {
|
||||
controlShizukuService(context, ::onBind, { bindingShizuku = false }, true)
|
||||
@@ -129,8 +130,8 @@ fun Permissions(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner))
|
||||
FunctionItem(R.string.delegated_admins) { navCtrl.navigate("DelegatedAdmins") }
|
||||
FunctionItem(R.string.device_info, icon = R.drawable.perm_device_information_fill0) { navCtrl.navigate("DeviceInfo") }
|
||||
FunctionItem(R.string.delegated_admins) { onNavigate(DelegatedAdmins) }
|
||||
FunctionItem(R.string.device_info, icon = R.drawable.perm_device_information_fill0) { onNavigate(DeviceInfo) }
|
||||
if(VERSION.SDK_INT >= 24 && (profileOwner || (VERSION.SDK_INT >= 26 && deviceOwner))) {
|
||||
FunctionItem(R.string.org_name, icon = R.drawable.corporate_fare_fill0) { dialog = 2 }
|
||||
}
|
||||
@@ -141,13 +142,13 @@ fun Permissions(navCtrl: NavHostController) {
|
||||
FunctionItem(R.string.enrollment_specific_id, icon = R.drawable.id_card_fill0) { dialog = 1 }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 24 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
FunctionItem(R.string.lock_screen_info, icon = R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("LockScreenInfo") }
|
||||
FunctionItem(R.string.lock_screen_info, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(LockScreenInfo) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 24 && deviceAdmin) {
|
||||
FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { navCtrl.navigate("SupportMessages") }
|
||||
FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { onNavigate(SupportMessage) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) {
|
||||
FunctionItem(R.string.transfer_ownership, icon = R.drawable.admin_panel_settings_fill0) { navCtrl.navigate("TransferOwnership") }
|
||||
FunctionItem(R.string.transfer_ownership, icon = R.drawable.admin_panel_settings_fill0) { onNavigate(TransferOwnership) }
|
||||
}
|
||||
}
|
||||
if(bindingShizuku) {
|
||||
@@ -265,15 +266,17 @@ private fun toggleDhizukuMode(status: Boolean, context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object LockScreenInfo
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun LockScreenInfo(navCtrl: NavHostController) {
|
||||
fun LockScreenInfoScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var infoText by remember { mutableStateOf(dpm.deviceOwnerLockScreenInfo?.toString() ?: "") }
|
||||
MyScaffold(R.string.lock_screen_info, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.lock_screen_info, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = infoText,
|
||||
label = { Text(stringResource(R.string.lock_screen_info)) },
|
||||
@@ -307,14 +310,16 @@ fun LockScreenInfo(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object DeviceAdmin
|
||||
|
||||
@Composable
|
||||
fun DeviceAdmin(navCtrl: NavHostController) {
|
||||
fun DeviceAdminScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
var deactivateDialog by remember { mutableStateOf(false) }
|
||||
val deviceAdmin = context.isDeviceAdmin
|
||||
MyScaffold(R.string.device_admin, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.device_admin, 8.dp, onNavigateUp) {
|
||||
Text(text = stringResource(if(context.isDeviceAdmin) R.string.activated else R.string.deactivated), style = typography.titleLarge)
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
AnimatedVisibility(deviceAdmin) {
|
||||
@@ -364,14 +369,16 @@ fun DeviceAdmin(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object ProfileOwner
|
||||
|
||||
@Composable
|
||||
fun ProfileOwner(navCtrl: NavHostController) {
|
||||
fun ProfileOwnerScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
var deactivateDialog by remember { mutableStateOf(false) }
|
||||
val profileOwner = context.isProfileOwner
|
||||
MyScaffold(R.string.profile_owner, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.profile_owner, 8.dp, onNavigateUp) {
|
||||
Text(stringResource(if(profileOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge)
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
if(VERSION.SDK_INT >= 24 && profileOwner) {
|
||||
@@ -417,13 +424,15 @@ fun ProfileOwner(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object DeviceOwner
|
||||
|
||||
@Composable
|
||||
fun DeviceOwner(navCtrl: NavHostController) {
|
||||
fun DeviceOwnerScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
var deactivateDialog by remember { mutableStateOf(false) }
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
MyScaffold(R.string.device_owner, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.device_owner, 8.dp, onNavigateUp) {
|
||||
Text(text = stringResource(if(deviceOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge)
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
AnimatedVisibility(deviceOwner) {
|
||||
@@ -502,9 +511,11 @@ private enum class DelegatedScope(val id: String, @StringRes val string: Int, va
|
||||
SecurityLogging(DevicePolicyManager.DELEGATION_SECURITY_LOGGING, R.string.security_logging, 31)
|
||||
}
|
||||
|
||||
@Serializable object DelegatedAdmins
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
fun DelegatedAdmins(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
fun DelegatedAdminsScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -529,7 +540,7 @@ fun DelegatedAdmins(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
packages.putAll(list)
|
||||
}
|
||||
LaunchedEffect(Unit) { refresh() }
|
||||
MyScaffold(R.string.delegated_admins, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.delegated_admins, 0.dp, onNavigateUp) {
|
||||
packages.forEach { (pkg, scopes) ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -558,12 +569,8 @@ fun DelegatedAdmins(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
Text(stringResource(R.string.add_delegated_admin), style = typography.titleLarge)
|
||||
}
|
||||
if(dialog != 0) {
|
||||
val selectedPackage by vm.selectedPackage.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(selectedPackage) {
|
||||
if(selectedPackage != "") {
|
||||
inputPackageName = selectedPackage
|
||||
vm.selectedPackage.value = ""
|
||||
}
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
||||
result?.let { inputPackageName = it }
|
||||
}
|
||||
AlertDialog(
|
||||
text = {
|
||||
@@ -573,7 +580,7 @@ fun DelegatedAdmins(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
value = inputPackageName, onValueChange = { inputPackageName = it },
|
||||
label = { Text(stringResource(R.string.package_name)) },
|
||||
trailingIcon = {
|
||||
if(dialog == 2) IconButton({ navCtrl.navigate("PackageSelector") }) {
|
||||
if(dialog == 2) IconButton({ choosePackage.launch(null) }) {
|
||||
Icon(painterResource(R.drawable.list_fill0), null)
|
||||
}
|
||||
},
|
||||
@@ -614,13 +621,15 @@ fun DelegatedAdmins(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object DeviceInfo
|
||||
|
||||
@Composable
|
||||
fun DeviceInfo(navCtrl: NavHostController) {
|
||||
fun DeviceInfoScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.device_info, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.device_info, 8.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT>=34 && (context.isDeviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
CardItem(R.string.financed_device, dpm.isDeviceFinanced.yesOrNo)
|
||||
}
|
||||
@@ -654,9 +663,11 @@ fun DeviceInfo(navCtrl: NavHostController) {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object SupportMessage
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun SupportMessages(navCtrl: NavHostController) {
|
||||
fun SupportMessageScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -667,7 +678,7 @@ fun SupportMessages(navCtrl: NavHostController) {
|
||||
longMsg = dpm.getLongSupportMessage(receiver)?.toString() ?: ""
|
||||
}
|
||||
LaunchedEffect(Unit) { refreshMsg() }
|
||||
MyScaffold(R.string.support_messages, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.support_messages, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = shortMsg,
|
||||
label = { Text(stringResource(R.string.short_support_msg)) },
|
||||
@@ -732,15 +743,17 @@ fun SupportMessages(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object TransferOwnership
|
||||
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun TransferOwnership(navCtrl: NavHostController) {
|
||||
fun TransferOwnershipScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var input by remember { mutableStateOf("") }
|
||||
val componentName = ComponentName.unflattenFromString(input)
|
||||
var dialog by remember { mutableStateOf(false) }
|
||||
MyScaffold(R.string.transfer_ownership, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.transfer_ownership, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = input, onValueChange = { input = it }, label = { Text(stringResource(R.string.target_component_name)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.bintianqi.owndroid.dpm
|
||||
|
||||
import android.accounts.Account
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
@@ -37,17 +36,19 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.IUserService
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.ui.MyScaffold
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import rikka.shizuku.Shizuku
|
||||
|
||||
@Serializable object ShizukuScreen
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Shizuku(navCtrl: NavHostController, navArgs: Bundle) {
|
||||
fun ShizukuScreen(navArgs: Bundle, onNavigateUp: () -> Unit, onNavigateToAccountsViewer: (Accounts) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -65,7 +66,7 @@ fun Shizuku(navCtrl: NavHostController, navArgs: Bundle) {
|
||||
null
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.shizuku, 0.dp, navCtrl, false) {
|
||||
MyScaffold(R.string.shizuku, 0.dp, onNavigateUp, false) {
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
@@ -93,9 +94,10 @@ fun Shizuku(navCtrl: NavHostController, navArgs: Bundle) {
|
||||
onClick = {
|
||||
Log.d("Shizuku", "List accounts")
|
||||
try {
|
||||
val accounts = service!!.listAccounts()
|
||||
val dest = navCtrl.graph.findNode("AccountsViewer")!!.id
|
||||
navCtrl.navigate(dest, Bundle().apply { putParcelableArray("accounts", accounts) })
|
||||
val accounts = service!!.listAccounts().map {
|
||||
Accounts.Account(it.type, it.name)
|
||||
}
|
||||
onNavigateToAccountsViewer(Accounts(accounts))
|
||||
} catch(_: Exception) {
|
||||
outputText = service!!.execute("dumpsys account")
|
||||
coScope.launch{
|
||||
@@ -174,11 +176,17 @@ fun controlShizukuService(
|
||||
else Shizuku.unbindUserService(userServiceArgs, userServiceConnection, true)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Accounts(
|
||||
val list: List<Account>
|
||||
) {
|
||||
@Serializable data class Account(val type: String, val name: String)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AccountsViewer(navCtrl: NavHostController, navArgs: Bundle) {
|
||||
val accounts = navArgs.getParcelableArray("accounts") as Array<Account>
|
||||
MyScaffold(R.string.accounts, 8.dp, navCtrl, false) {
|
||||
accounts.forEach {
|
||||
fun AccountsScreen(accounts: Accounts, onNavigateUp: () -> Unit) {
|
||||
MyScaffold(R.string.accounts, 8.dp, onNavigateUp, false) {
|
||||
accounts.list.forEach {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth().padding(vertical = 4.dp)
|
||||
|
||||
@@ -119,9 +119,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.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.ChoosePackageContract
|
||||
import com.bintianqi.owndroid.NotificationUtils
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.SharedPrefs
|
||||
@@ -139,6 +137,7 @@ import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import com.bintianqi.owndroid.uriToStream
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
@@ -146,8 +145,10 @@ import java.util.concurrent.Executors
|
||||
import kotlin.collections.addAll
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
@Serializable object SystemManager
|
||||
|
||||
@Composable
|
||||
fun SystemManage(navCtrl: NavHostController) {
|
||||
fun SystemManagerScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -156,13 +157,13 @@ fun SystemManage(navCtrl: NavHostController) {
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
val profileOwner = context.isProfileOwner
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.system, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.system, 0.dp, onNavigateUp) {
|
||||
if(deviceOwner || profileOwner) {
|
||||
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { navCtrl.navigate("SystemOptions") }
|
||||
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SystemOptions) }
|
||||
}
|
||||
FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("Keyguard") }
|
||||
FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(Keyguard) }
|
||||
if(VERSION.SDK_INT >= 24 && deviceOwner && !dhizuku)
|
||||
FunctionItem(R.string.hardware_monitor, icon = R.drawable.memory_fill0) { navCtrl.navigate("HardwareMonitor") }
|
||||
FunctionItem(R.string.hardware_monitor, icon = R.drawable.memory_fill0) { onNavigate(HardwareMonitor) }
|
||||
if(VERSION.SDK_INT >= 24 && deviceOwner) {
|
||||
FunctionItem(R.string.reboot, icon = R.drawable.restart_alt_fill0) { dialog = 1 }
|
||||
}
|
||||
@@ -170,45 +171,45 @@ fun SystemManage(navCtrl: NavHostController) {
|
||||
FunctionItem(R.string.bug_report, icon = R.drawable.bug_report_fill0) { dialog = 2 }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 28 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
FunctionItem(R.string.change_time, icon = R.drawable.schedule_fill0) { navCtrl.navigate("ChangeTime") }
|
||||
FunctionItem(R.string.change_timezone, icon = R.drawable.schedule_fill0) { navCtrl.navigate("ChangeTimeZone") }
|
||||
FunctionItem(R.string.change_time, icon = R.drawable.schedule_fill0) { onNavigate(ChangeTime) }
|
||||
FunctionItem(R.string.change_timezone, icon = R.drawable.schedule_fill0) { onNavigate(ChangeTimeZone) }
|
||||
}
|
||||
/*if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner))
|
||||
FunctionItem(R.string.key_pairs, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("KeyPairs") }*/
|
||||
if(VERSION.SDK_INT >= 35 && (deviceOwner || (profileOwner && dpm.isAffiliatedUser)))
|
||||
FunctionItem(R.string.content_protection_policy, icon = R.drawable.search_fill0) { navCtrl.navigate("ContentProtectionPolicy") }
|
||||
FunctionItem(R.string.content_protection_policy, icon = R.drawable.search_fill0) { onNavigate(ContentProtectionPolicy) }
|
||||
if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) {
|
||||
FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { navCtrl.navigate("PermissionPolicy") }
|
||||
FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { onNavigate(PermissionPolicy) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 34 && deviceOwner) {
|
||||
FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { navCtrl.navigate("MTEPolicy") }
|
||||
FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { onNavigate(MtePolicy) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) {
|
||||
FunctionItem(R.string.nearby_streaming_policy, icon = R.drawable.share_fill0) { navCtrl.navigate("NearbyStreamingPolicy") }
|
||||
FunctionItem(R.string.nearby_streaming_policy, icon = R.drawable.share_fill0) { onNavigate(NearbyStreamingPolicy) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 28 && deviceOwner) {
|
||||
FunctionItem(R.string.lock_task_mode, icon = R.drawable.lock_fill0) { navCtrl.navigate("LockTaskMode") }
|
||||
FunctionItem(R.string.lock_task_mode, icon = R.drawable.lock_fill0) { onNavigate(LockTaskMode) }
|
||||
}
|
||||
if(deviceOwner || profileOwner) {
|
||||
FunctionItem(R.string.ca_cert, icon = R.drawable.license_fill0) { navCtrl.navigate("CACert") }
|
||||
FunctionItem(R.string.ca_cert, icon = R.drawable.license_fill0) { onNavigate(CaCert) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
FunctionItem(R.string.security_logging, icon = R.drawable.description_fill0) { navCtrl.navigate("SecurityLogging") }
|
||||
FunctionItem(R.string.security_logging, icon = R.drawable.description_fill0) { onNavigate(SecurityLogging) }
|
||||
}
|
||||
if(deviceOwner || profileOwner) {
|
||||
FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { navCtrl.navigate("DisableAccountManagement") }
|
||||
FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { onNavigate(DisableAccountManagement) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 23 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
FunctionItem(R.string.system_update_policy, icon = R.drawable.system_update_fill0) { navCtrl.navigate("SystemUpdatePolicy") }
|
||||
FunctionItem(R.string.system_update_policy, icon = R.drawable.system_update_fill0) { onNavigate(SetSystemUpdatePolicy) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 29 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
FunctionItem(R.string.install_system_update, icon = R.drawable.system_update_fill0) { navCtrl.navigate("InstallSystemUpdate") }
|
||||
FunctionItem(R.string.install_system_update, icon = R.drawable.system_update_fill0) { onNavigate(InstallSystemUpdate) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 30 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||
FunctionItem(R.string.frp_policy, icon = R.drawable.device_reset_fill0) { navCtrl.navigate("FRPPolicy") }
|
||||
FunctionItem(R.string.frp_policy, icon = R.drawable.device_reset_fill0) { onNavigate(FrpPolicy) }
|
||||
}
|
||||
if(sp.displayDangerousFeatures && context.isDeviceAdmin && !(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver))) {
|
||||
FunctionItem(R.string.wipe_data, icon = R.drawable.device_reset_fill0) { navCtrl.navigate("WipeData") }
|
||||
FunctionItem(R.string.wipe_data, icon = R.drawable.device_reset_fill0) { onNavigate(WipeData) }
|
||||
}
|
||||
}
|
||||
if(dialog != 0 &&VERSION.SDK_INT >= 24) AlertDialog(
|
||||
@@ -238,8 +239,10 @@ fun SystemManage(navCtrl: NavHostController) {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object SystemOptions
|
||||
|
||||
@Composable
|
||||
fun SystemOptions(navCtrl: NavHostController) {
|
||||
fun SystemOptionsScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -247,7 +250,7 @@ fun SystemOptions(navCtrl: NavHostController) {
|
||||
val profileOwner = context.isProfileOwner
|
||||
val um = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.options, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.options, 0.dp, onNavigateUp) {
|
||||
if(deviceOwner || profileOwner) {
|
||||
SwitchItem(R.string.disable_cam, icon = R.drawable.photo_camera_fill0,
|
||||
getState = { dpm.getCameraDisabled(null) }, onCheckedChange = { dpm.setCameraDisabled(receiver,it) }
|
||||
@@ -323,14 +326,16 @@ fun SystemOptions(navCtrl: NavHostController) {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object Keyguard
|
||||
|
||||
@Composable
|
||||
fun Keyguard(navCtrl: NavHostController) {
|
||||
fun KeyguardScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
val profileOwner = context.isProfileOwner
|
||||
MyScaffold(R.string.keyguard, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.keyguard, 8.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 23) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -379,10 +384,12 @@ fun Keyguard(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object HardwareMonitor
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun HardwareMonitor(navCtrl: NavHostController) {
|
||||
fun HardwareMonitorScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val hpm = context.getSystemService(HardwarePropertiesManager::class.java)
|
||||
var refreshInterval by remember { mutableFloatStateOf(1F) }
|
||||
@@ -412,7 +419,7 @@ fun HardwareMonitor(navCtrl: NavHostController) {
|
||||
delay(refreshIntervalMs)
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.hardware_monitor, 8.dp, navCtrl, false) {
|
||||
MyScaffold(R.string.hardware_monitor, 8.dp, onNavigateUp, false) {
|
||||
Text(stringResource(R.string.refresh_interval), style = typography.titleLarge, modifier = Modifier.padding(vertical = 4.dp))
|
||||
Slider(refreshInterval, { refreshInterval = it }, valueRange = 0.5F..2F, steps = 14)
|
||||
Text("${refreshIntervalMs}ms")
|
||||
@@ -460,10 +467,12 @@ fun HardwareMonitor(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object ChangeTime
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun ChangeTime(navCtrl: NavHostController) {
|
||||
fun ChangeTimeScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -481,7 +490,7 @@ fun ChangeTime(navCtrl: NavHostController) {
|
||||
if(timeInteractionSource.collectIsPressedAsState().value) picker = 2
|
||||
val isInputLegal = (manualInput && (try { inputTime.toLong() } catch(_: Exception) { -1 }) >= 0) ||
|
||||
(!manualInput && datePickerState.selectedDateMillis != null)
|
||||
MyScaffold(R.string.change_time, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.change_time, 8.dp, onNavigateUp) {
|
||||
SingleChoiceSegmentedButtonRow(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 4.dp)
|
||||
) {
|
||||
@@ -571,16 +580,18 @@ fun ChangeTime(navCtrl: NavHostController) {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object ChangeTimeZone
|
||||
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun ChangeTimeZone(navCtrl: NavHostController) {
|
||||
fun ChangeTimeZoneScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val receiver = context.getReceiver()
|
||||
var inputTimezone by remember { mutableStateOf("") }
|
||||
var dialog by remember { mutableStateOf(false) }
|
||||
MyScaffold(R.string.change_timezone, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.change_timezone, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = inputTimezone,
|
||||
label = { Text(stringResource(R.string.timezone_id)) },
|
||||
@@ -814,16 +825,18 @@ fun KeyPairs(navCtrl: NavHostController) {
|
||||
}
|
||||
}*/
|
||||
|
||||
@Serializable object ContentProtectionPolicy
|
||||
|
||||
@RequiresApi(35)
|
||||
@Composable
|
||||
fun ContentProtectionPolicy(navCtrl: NavHostController) {
|
||||
fun ContentProtectionPolicyScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
var policy by remember { mutableIntStateOf(DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY) }
|
||||
fun refresh() { policy = dpm.getContentProtectionPolicy(receiver) }
|
||||
LaunchedEffect(Unit) { refresh() }
|
||||
MyScaffold(R.string.content_protection_policy, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.content_protection_policy, 8.dp, onNavigateUp) {
|
||||
mapOf(
|
||||
DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY to R.string.not_controlled_by_policy,
|
||||
DevicePolicyManager.CONTENT_PROTECTION_ENABLED to R.string.enabled,
|
||||
@@ -845,14 +858,16 @@ fun ContentProtectionPolicy(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object PermissionPolicy
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
fun PermissionPolicy(navCtrl: NavHostController) {
|
||||
fun PermissionPolicyScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
var selectedPolicy by remember { mutableIntStateOf(dpm.getPermissionPolicy(receiver)) }
|
||||
MyScaffold(R.string.permission_policy, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.permission_policy, 8.dp, onNavigateUp) {
|
||||
RadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT) { selectedPolicy = PERMISSION_POLICY_PROMPT }
|
||||
RadioButtonItem(R.string.auto_grant, selectedPolicy == PERMISSION_POLICY_AUTO_GRANT) { selectedPolicy = PERMISSION_POLICY_AUTO_GRANT }
|
||||
RadioButtonItem(R.string.auto_deny, selectedPolicy == PERMISSION_POLICY_AUTO_DENY) { selectedPolicy = PERMISSION_POLICY_AUTO_DENY }
|
||||
@@ -870,13 +885,15 @@ fun PermissionPolicy(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object MtePolicy
|
||||
|
||||
@RequiresApi(34)
|
||||
@Composable
|
||||
fun MTEPolicy(navCtrl: NavHostController) {
|
||||
fun MtePolicyScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
var selectedMtePolicy by remember { mutableIntStateOf(dpm.mtePolicy) }
|
||||
MyScaffold(R.string.mte_policy, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.mte_policy, 8.dp, onNavigateUp) {
|
||||
RadioButtonItem(R.string.decide_by_user, selectedMtePolicy == MTE_NOT_CONTROLLED_BY_POLICY) { selectedMtePolicy = MTE_NOT_CONTROLLED_BY_POLICY }
|
||||
RadioButtonItem(R.string.enabled, selectedMtePolicy == MTE_ENABLED) { selectedMtePolicy = MTE_ENABLED }
|
||||
RadioButtonItem(R.string.disabled, selectedMtePolicy == MTE_DISABLED) { selectedMtePolicy = MTE_DISABLED }
|
||||
@@ -898,13 +915,15 @@ fun MTEPolicy(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object NearbyStreamingPolicy
|
||||
|
||||
@RequiresApi(31)
|
||||
@Composable
|
||||
fun NearbyStreamingPolicy(navCtrl: NavHostController) {
|
||||
fun NearbyStreamingPolicyScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
var appPolicy by remember { mutableIntStateOf(dpm.nearbyAppStreamingPolicy) }
|
||||
MyScaffold(R.string.nearby_streaming_policy, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.nearby_streaming_policy, 8.dp, onNavigateUp) {
|
||||
Spacer(Modifier.padding(vertical = 10.dp))
|
||||
Text(text = stringResource(R.string.nearby_app_streaming), style = typography.titleLarge)
|
||||
Spacer(Modifier.padding(vertical = 3.dp))
|
||||
@@ -963,10 +982,12 @@ fun NearbyStreamingPolicy(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object LockTaskMode
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
|
||||
val coroutine = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState { 3 }
|
||||
var tabIndex by remember { mutableIntStateOf(0) }
|
||||
@@ -975,7 +996,7 @@ fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.lock_task_mode)) },
|
||||
navigationIcon = { NavIcon { navCtrl.navigateUp() } }
|
||||
navigationIcon = { NavIcon(onNavigateUp) }
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
@@ -1000,8 +1021,8 @@ fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 8.dp, end = 8.dp, bottom = 80.dp)
|
||||
) {
|
||||
if(page == 0) StartLockTaskMode(navCtrl, vm)
|
||||
else if(page == 1) LockTaskPackages(navCtrl, vm)
|
||||
if(page == 0) StartLockTaskMode()
|
||||
else if(page == 1) LockTaskPackages()
|
||||
else LockTaskFeatures()
|
||||
}
|
||||
}
|
||||
@@ -1011,19 +1032,15 @@ fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
private fun ColumnScope.StartLockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
private fun ColumnScope.StartLockTaskMode() {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var startLockTaskApp by rememberSaveable { mutableStateOf("") }
|
||||
var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
|
||||
var specifyActivity by rememberSaveable { mutableStateOf(false) }
|
||||
val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(updatePackage) {
|
||||
if(updatePackage != "") {
|
||||
startLockTaskApp = updatePackage
|
||||
vm.selectedPackage.value = ""
|
||||
}
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
||||
result?.let { startLockTaskApp = it }
|
||||
}
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
OutlinedTextField(
|
||||
@@ -1036,10 +1053,7 @@ private fun ColumnScope.StartLockTaskMode(navCtrl: NavHostController, vm: MyView
|
||||
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(50))
|
||||
.clickable(onClick = {
|
||||
focusMgr.clearFocus()
|
||||
navCtrl.navigate("PackageSelector")
|
||||
})
|
||||
.clickable { choosePackage.launch(null) }
|
||||
.padding(3.dp))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
|
||||
@@ -1081,19 +1095,15 @@ private fun ColumnScope.StartLockTaskMode(navCtrl: NavHostController, vm: MyView
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
private fun ColumnScope.LockTaskPackages(navCtrl: NavHostController, vm: MyViewModel) {
|
||||
private fun ColumnScope.LockTaskPackages() {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val lockTaskPackages = remember { mutableStateListOf<String>() }
|
||||
var input by rememberSaveable { mutableStateOf("") }
|
||||
val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(updatePackage) {
|
||||
if(updatePackage != "") {
|
||||
input = updatePackage
|
||||
vm.selectedPackage.value = ""
|
||||
}
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
||||
result?.let { input = it }
|
||||
}
|
||||
LaunchedEffect(Unit) { lockTaskPackages.addAll(dpm.getLockTaskPackages(receiver)) }
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
@@ -1111,10 +1121,7 @@ private fun ColumnScope.LockTaskPackages(navCtrl: NavHostController, vm: MyViewM
|
||||
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(50))
|
||||
.clickable(onClick = {
|
||||
focusMgr.clearFocus()
|
||||
navCtrl.navigate("PackageSelector")
|
||||
})
|
||||
.clickable { choosePackage.launch(null) }
|
||||
.padding(3.dp))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
|
||||
@@ -1221,8 +1228,10 @@ private fun ColumnScope.LockTaskFeatures() {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object CaCert
|
||||
|
||||
@Composable
|
||||
fun CACert(navCtrl: NavHostController) {
|
||||
fun CaCertScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -1235,7 +1244,7 @@ fun CACert(navCtrl: NavHostController) {
|
||||
}
|
||||
dialog = true
|
||||
}
|
||||
MyScaffold(R.string.ca_cert, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.ca_cert, 8.dp, onNavigateUp) {
|
||||
Button(
|
||||
onClick = {
|
||||
getFileLauncher.launch("*/*")
|
||||
@@ -1278,9 +1287,11 @@ fun CACert(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object SecurityLogging
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun SecurityLogging(navCtrl: NavHostController) {
|
||||
fun SecurityLoggingScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -1305,7 +1316,7 @@ fun SecurityLogging(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.security_logging, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.security_logging, 8.dp, onNavigateUp) {
|
||||
SwitchItem(
|
||||
R.string.enable,
|
||||
getState = { dpm.isSecurityLoggingEnabled(receiver) }, onCheckedChange = { dpm.setSecurityLoggingEnabled(receiver, it) },
|
||||
@@ -1366,13 +1377,15 @@ fun SecurityLogging(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object DisableAccountManagement
|
||||
|
||||
@Composable
|
||||
fun DisableAccountManagement(navCtrl: NavHostController) {
|
||||
fun DisableAccountManagementScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
MyScaffold(R.string.disable_account_management, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.disable_account_management, 8.dp, onNavigateUp) {
|
||||
val list = remember { mutableStateListOf<String>() }
|
||||
fun refreshList() {
|
||||
list.clear()
|
||||
@@ -1414,9 +1427,11 @@ fun DisableAccountManagement(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object FrpPolicy
|
||||
|
||||
@RequiresApi(30)
|
||||
@Composable
|
||||
fun FRPPolicy(navCtrl: NavHostController) {
|
||||
fun FrpPolicyScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
@@ -1442,7 +1457,7 @@ fun FRPPolicy(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.frp_policy, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.frp_policy, 8.dp, onNavigateUp) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp, vertical = 8.dp)
|
||||
@@ -1506,8 +1521,10 @@ fun FRPPolicy(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object WipeData
|
||||
|
||||
@Composable
|
||||
fun WipeData(navCtrl: NavHostController) {
|
||||
fun WipeDataScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
val dpm = context.getDPM()
|
||||
@@ -1517,7 +1534,7 @@ fun WipeData(navCtrl: NavHostController) {
|
||||
var wipeDevice by remember { mutableStateOf(false) }
|
||||
var silent by remember { mutableStateOf(false) }
|
||||
var reason by remember { mutableStateOf("") }
|
||||
MyScaffold(R.string.wipe_data, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.wipe_data, 8.dp, onNavigateUp) {
|
||||
CheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { flag = flag xor WIPE_EXTERNAL_STORAGE }
|
||||
if(VERSION.SDK_INT >= 22 && context.isDeviceOwner) CheckBoxItem(
|
||||
R.string.wipe_reset_protection_data, flag and WIPE_RESET_PROTECTION_DATA != 0) { flag = flag xor WIPE_RESET_PROTECTION_DATA }
|
||||
@@ -1612,13 +1629,15 @@ fun WipeData(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object SetSystemUpdatePolicy
|
||||
|
||||
@Composable
|
||||
fun SystemUpdatePolicy(navCtrl: NavHostController) {
|
||||
fun SystemUpdatePolicy(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
MyScaffold(R.string.system_update_policy, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.system_update_policy, 8.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 23) {
|
||||
Column {
|
||||
var selectedPolicy by remember { mutableStateOf(dpm.systemUpdatePolicy?.policyType) }
|
||||
@@ -1700,9 +1719,11 @@ fun SystemUpdatePolicy(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object InstallSystemUpdate
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Composable
|
||||
fun InstallSystemUpdate(navCtrl: NavHostController) {
|
||||
fun InstallSystemUpdateScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -1724,7 +1745,7 @@ fun InstallSystemUpdate(navCtrl: NavHostController) {
|
||||
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
uri = it.data?.data
|
||||
}
|
||||
MyScaffold(R.string.install_system_update, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.install_system_update, 8.dp, onNavigateUp) {
|
||||
Button(
|
||||
onClick = {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
|
||||
@@ -10,18 +10,17 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.ui.FunctionItem
|
||||
import com.bintianqi.owndroid.ui.MyScaffold
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Restriction(
|
||||
val id: String,
|
||||
@StringRes val name: Int,
|
||||
@@ -29,45 +28,59 @@ data class Restriction(
|
||||
val requiresApi: Int = 0
|
||||
)
|
||||
|
||||
@Serializable object UserRestriction
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun UserRestriction(navCtrl:NavHostController, vm: MyViewModel) {
|
||||
fun UserRestrictionScreen(onNavigateUp: () -> Unit, onNavigate: (Int, List<Restriction>) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
LaunchedEffect(Unit) {
|
||||
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
|
||||
}
|
||||
MyScaffold(R.string.user_restriction, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.user_restriction, 0.dp, onNavigateUp) {
|
||||
Text(text = stringResource(R.string.switch_to_disable_feature), modifier = Modifier.padding(start = 16.dp))
|
||||
if(context.isProfileOwner) { Text(text = stringResource(R.string.profile_owner_is_restricted), modifier = Modifier.padding(start = 16.dp)) }
|
||||
if(context.isProfileOwner && dpm.isManagedProfile(receiver)) {
|
||||
Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 16.dp))
|
||||
}
|
||||
Spacer(Modifier.padding(vertical = 2.dp))
|
||||
FunctionItem(R.string.network_and_internet, icon = R.drawable.wifi_fill0) { navCtrl.navigate("UR-Internet") }
|
||||
FunctionItem(R.string.connectivity, icon = R.drawable.devices_other_fill0) { navCtrl.navigate("UR-Connectivity") }
|
||||
FunctionItem(R.string.applications, icon = R.drawable.apps_fill0) { navCtrl.navigate("UR-Applications") }
|
||||
FunctionItem(R.string.users, icon = R.drawable.account_circle_fill0) { navCtrl.navigate("UR-Users") }
|
||||
FunctionItem(R.string.media, icon = R.drawable.volume_up_fill0) { navCtrl.navigate("UR-Media") }
|
||||
FunctionItem(R.string.other, icon = R.drawable.more_horiz_fill0) { navCtrl.navigate("UR-Other") }
|
||||
FunctionItem(R.string.network, icon = R.drawable.language_fill0) {
|
||||
onNavigate(R.string.network, RestrictionData.internet)
|
||||
}
|
||||
FunctionItem(R.string.connectivity, icon = R.drawable.devices_other_fill0) {
|
||||
onNavigate(R.string.connectivity, RestrictionData.connectivity)
|
||||
}
|
||||
FunctionItem(R.string.applications, icon = R.drawable.apps_fill0) {
|
||||
onNavigate(R.string.applications, RestrictionData.applications)
|
||||
}
|
||||
FunctionItem(R.string.users, icon = R.drawable.account_circle_fill0) {
|
||||
onNavigate(R.string.users, RestrictionData.users)
|
||||
}
|
||||
FunctionItem(R.string.media, icon = R.drawable.volume_up_fill0) {
|
||||
onNavigate(R.string.media, RestrictionData.media)
|
||||
}
|
||||
FunctionItem(R.string.other, icon = R.drawable.more_horiz_fill0) {
|
||||
onNavigate(R.string.other, RestrictionData.other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class UserRestrictionOptions(
|
||||
val title: Int, val items: List<Restriction>
|
||||
)
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun UserRestrictionScreen(
|
||||
title: Int, items: List<Restriction>, restrictions: Bundle,
|
||||
fun UserRestrictionOptionsScreen(
|
||||
data: UserRestrictionOptions, restrictions: Bundle,
|
||||
onRestrictionChange: (String, Boolean) -> Unit, onNavigateUp: () -> Unit
|
||||
) {
|
||||
MyScaffold(title, 0.dp, onNavigateUp, false) {
|
||||
items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction ->
|
||||
MyScaffold(data.title, 0.dp, onNavigateUp, false) {
|
||||
data.items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction ->
|
||||
SwitchItem(
|
||||
restriction.name, restriction.id, restriction.icon,
|
||||
restrictions.getBoolean(restriction.id), { onRestrictionChange(restriction.id, it) }, padding = true
|
||||
)
|
||||
/*Box(modifier = Modifier.padding(start = 22.dp, end = 16.dp)) {
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.parseTimestamp
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
@@ -85,41 +84,44 @@ import com.bintianqi.owndroid.yesOrNo
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable object Users
|
||||
|
||||
@Composable
|
||||
fun Users(navCtrl: NavHostController) {
|
||||
fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val deviceOwner = context.isDeviceOwner
|
||||
val profileOwner = context.isProfileOwner
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.users, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.users, 0.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser) {
|
||||
FunctionItem(R.string.logout, icon = R.drawable.logout_fill0) { dialog = 2 }
|
||||
}
|
||||
FunctionItem(R.string.user_info, icon = R.drawable.person_fill0) { navCtrl.navigate("UserInfo") }
|
||||
FunctionItem(R.string.user_info, icon = R.drawable.person_fill0) { onNavigate(UserInfo) }
|
||||
if(deviceOwner && VERSION.SDK_INT >= 28) {
|
||||
FunctionItem(R.string.secondary_users, icon = R.drawable.list_fill0) { dialog = 1 }
|
||||
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { navCtrl.navigate("UserOptions") }
|
||||
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(UsersOptions) }
|
||||
}
|
||||
if(deviceOwner) {
|
||||
FunctionItem(R.string.user_operation, icon = R.drawable.sync_alt_fill0) { navCtrl.navigate("UserOperation") }
|
||||
FunctionItem(R.string.user_operation, icon = R.drawable.sync_alt_fill0) { onNavigate(UserOperation) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 24 && deviceOwner) {
|
||||
FunctionItem(R.string.create_user, icon = R.drawable.person_add_fill0) { navCtrl.navigate("CreateUser") }
|
||||
FunctionItem(R.string.create_user, icon = R.drawable.person_add_fill0) { onNavigate(CreateUser) }
|
||||
}
|
||||
if(deviceOwner || profileOwner) {
|
||||
FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { navCtrl.navigate("ChangeUsername") }
|
||||
FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) {
|
||||
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { navCtrl.navigate("ChangeUserIcon") }
|
||||
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { onNavigate(ChangeUserIcon) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 28 && deviceOwner) {
|
||||
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { navCtrl.navigate("UserSessionMessage") }
|
||||
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) {
|
||||
FunctionItem(R.string.affiliation_id, icon = R.drawable.id_card_fill0) { navCtrl.navigate("AffiliationID") }
|
||||
FunctionItem(R.string.affiliation_id, icon = R.drawable.id_card_fill0) { onNavigate(AffiliationId) }
|
||||
}
|
||||
}
|
||||
if(dialog != 0 && VERSION.SDK_INT >= 28) AlertDialog(
|
||||
@@ -159,27 +161,31 @@ fun Users(navCtrl: NavHostController) {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object UsersOptions
|
||||
|
||||
@Composable
|
||||
fun UserOptions(navCtrl: NavHostController) {
|
||||
fun UsersOptionsScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
MyScaffold(R.string.options, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.options, 0.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 28) {
|
||||
SwitchItem(R.string.enable_logout, getState = { dpm.isLogoutEnabled }, onCheckedChange = { dpm.setLogoutEnabled(receiver, it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object UserInfo
|
||||
|
||||
@Composable
|
||||
fun CurrentUserInfo(navCtrl: NavHostController) {
|
||||
fun UserInfoScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
val user = Process.myUserHandle()
|
||||
var infoDialog by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.user_info, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.user_info, 8.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 24) CardItem(R.string.support_multiuser, UserManager.supportsMultipleUsers().yesOrNo)
|
||||
if(VERSION.SDK_INT >= 31) CardItem(R.string.headless_system_user_mode, UserManager.isHeadlessSystemUserMode().yesOrNo) { infoDialog = 1 }
|
||||
Spacer(Modifier.padding(vertical = 8.dp))
|
||||
@@ -208,8 +214,10 @@ fun CurrentUserInfo(navCtrl: NavHostController) {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object UserOperation
|
||||
|
||||
@Composable
|
||||
fun UserOperation(navCtrl: NavHostController) {
|
||||
fun UserOperationScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
val dpm = context.getDPM()
|
||||
@@ -230,7 +238,7 @@ fun UserOperation(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
val legalInput = input.toIntOrNull() != null
|
||||
MyScaffold(R.string.user_operation, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.user_operation, 8.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 24) SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
|
||||
SegmentedButton(!useUserId, { useUserId = false }, SegmentedButtonDefaults.itemShape(0, 2)) {
|
||||
Text(stringResource(R.string.serial_number))
|
||||
@@ -311,9 +319,11 @@ fun UserOperation(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object CreateUser
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun CreateUser(navCtrl: NavHostController) {
|
||||
fun CreateUserScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
val dpm = context.getDPM()
|
||||
@@ -324,7 +334,7 @@ fun CreateUser(navCtrl: NavHostController) {
|
||||
var createdUserSerialNumber by remember { mutableLongStateOf(-1) }
|
||||
var flag by remember { mutableIntStateOf(0) }
|
||||
val coroutine = rememberCoroutineScope()
|
||||
MyScaffold(R.string.create_user, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.create_user, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = userName,
|
||||
onValueChange = { userName= it },
|
||||
@@ -383,9 +393,11 @@ fun CreateUser(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object AffiliationId
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
fun AffiliationID(navCtrl: NavHostController) {
|
||||
fun AffiliationIdScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -397,7 +409,7 @@ fun AffiliationID(navCtrl: NavHostController) {
|
||||
list.addAll(dpm.getAffiliationIds(receiver))
|
||||
}
|
||||
LaunchedEffect(Unit) { refreshIds() }
|
||||
MyScaffold(R.string.affiliation_id, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.affiliation_id, 8.dp, onNavigateUp) {
|
||||
Column(modifier = Modifier.animateContentSize()) {
|
||||
if(list.isEmpty()) Text(stringResource(R.string.none))
|
||||
for(i in list) {
|
||||
@@ -439,14 +451,16 @@ fun AffiliationID(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object ChangeUsername
|
||||
|
||||
@Composable
|
||||
fun ChangeUsername(navCtrl: NavHostController) {
|
||||
fun ChangeUsernameScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var inputUsername by remember { mutableStateOf("") }
|
||||
MyScaffold(R.string.change_username, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.change_username, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = inputUsername,
|
||||
onValueChange = { inputUsername= it },
|
||||
@@ -474,9 +488,11 @@ fun ChangeUsername(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object UserSessionMessage
|
||||
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun UserSessionMessage(navCtrl: NavHostController) {
|
||||
fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -488,7 +504,7 @@ fun UserSessionMessage(navCtrl: NavHostController) {
|
||||
end = dpm.getEndUserSessionMessage(receiver)?.toString() ?: ""
|
||||
}
|
||||
LaunchedEffect(Unit) { refreshMsg() }
|
||||
MyScaffold(R.string.user_session_msg, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.user_session_msg, 8.dp, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = start,
|
||||
onValueChange = { start= it },
|
||||
@@ -552,9 +568,11 @@ fun UserSessionMessage(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object ChangeUserIcon
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
fun ChangeUserIcon(navCtrl: NavHostController) {
|
||||
fun ChangeUserIconScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
@@ -567,7 +585,7 @@ fun ChangeUserIcon(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.change_user_icon, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.change_user_icon, 8.dp, onNavigateUp) {
|
||||
CheckBoxItem(R.string.file_picker_instead_gallery, getContent) { getContent = it }
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
Button(
|
||||
|
||||
@@ -53,7 +53,6 @@ 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.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.CardItem
|
||||
@@ -64,39 +63,44 @@ import com.bintianqi.owndroid.ui.InfoCard
|
||||
import com.bintianqi.owndroid.ui.MyScaffold
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import com.bintianqi.owndroid.yesOrNo
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable object WorkProfile
|
||||
|
||||
@Composable
|
||||
fun WorkProfile(navCtrl: NavHostController) {
|
||||
fun WorkProfileScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val profileOwner = context.isProfileOwner
|
||||
MyScaffold(R.string.work_profile, 0.dp, navCtrl) {
|
||||
MyScaffold(R.string.work_profile, 0.dp, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 30 && profileOwner && dpm.isManagedProfile(receiver)) {
|
||||
FunctionItem(R.string.org_owned_work_profile, icon = R.drawable.corporate_fare_fill0) { navCtrl.navigate("OrgOwnedWorkProfile") }
|
||||
FunctionItem(R.string.org_owned_work_profile, icon = R.drawable.corporate_fare_fill0) { onNavigate(OrganizationOwnedProfile) }
|
||||
}
|
||||
if(VERSION.SDK_INT < 24 || dpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)) {
|
||||
FunctionItem(R.string.create_work_profile, icon = R.drawable.work_fill0) { navCtrl.navigate("CreateWorkProfile") }
|
||||
FunctionItem(R.string.create_work_profile, icon = R.drawable.work_fill0) { onNavigate(CreateWorkProfile) }
|
||||
}
|
||||
if(dpm.isOrgProfile(receiver)) {
|
||||
FunctionItem(R.string.suspend_personal_app, icon = R.drawable.block_fill0) { navCtrl.navigate("SuspendPersonalApp") }
|
||||
FunctionItem(R.string.suspend_personal_app, icon = R.drawable.block_fill0) { onNavigate(SuspendPersonalApp) }
|
||||
}
|
||||
if(profileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver))) {
|
||||
FunctionItem(R.string.intent_filter, icon = R.drawable.filter_alt_fill0) { navCtrl.navigate("IntentFilter") }
|
||||
FunctionItem(R.string.intent_filter, icon = R.drawable.filter_alt_fill0) { onNavigate(CrossProfileIntentFilter) }
|
||||
}
|
||||
if(profileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver))) {
|
||||
FunctionItem(R.string.delete_work_profile, icon = R.drawable.delete_forever_fill0) { navCtrl.navigate("DeleteWorkProfile") }
|
||||
FunctionItem(R.string.delete_work_profile, icon = R.drawable.delete_forever_fill0) { onNavigate(DeleteWorkProfile) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object CreateWorkProfile
|
||||
|
||||
@Composable
|
||||
fun CreateWorkProfile(navCtrl: NavHostController) {
|
||||
fun CreateWorkProfileScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { }
|
||||
MyScaffold(R.string.create_work_profile, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.create_work_profile, 8.dp, onNavigateUp) {
|
||||
var skipEncrypt by remember { mutableStateOf(false) }
|
||||
var offlineProvisioning by remember { mutableStateOf(true) }
|
||||
var migrateAccount by remember { mutableStateOf(false) }
|
||||
@@ -160,12 +164,14 @@ fun CreateWorkProfile(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object OrganizationOwnedProfile
|
||||
|
||||
@RequiresApi(30)
|
||||
@Composable
|
||||
fun OrgOwnedProfile(navCtrl: NavHostController) {
|
||||
fun OrganizationOwnedProfileScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
MyScaffold(R.string.org_owned_work_profile, 8.dp, navCtrl, false) {
|
||||
MyScaffold(R.string.org_owned_work_profile, 8.dp, onNavigateUp, false) {
|
||||
CardItem(R.string.org_owned_work_profile, dpm.isOrganizationOwnedDeviceWithManagedProfile.yesOrNo)
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
if(!dpm.isOrganizationOwnedDeviceWithManagedProfile) {
|
||||
@@ -180,15 +186,17 @@ fun OrgOwnedProfile(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object SuspendPersonalApp
|
||||
|
||||
@RequiresApi(30)
|
||||
@Composable
|
||||
fun SuspendPersonalApp(navCtrl: NavHostController) {
|
||||
fun SuspendPersonalAppScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var suspend by remember { mutableStateOf(dpm.getPersonalAppsSuspendedReasons(receiver) != PERSONAL_APPS_NOT_SUSPENDED) }
|
||||
MyScaffold(R.string.suspend_personal_app, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.suspend_personal_app, 8.dp, onNavigateUp) {
|
||||
SwitchItem(R.string.suspend_personal_app, state = suspend,
|
||||
onCheckedChange = {
|
||||
dpm.setPersonalAppsSuspended(receiver,it)
|
||||
@@ -226,13 +234,15 @@ fun SuspendPersonalApp(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object CrossProfileIntentFilter
|
||||
|
||||
@Composable
|
||||
fun IntentFilter(navCtrl: NavHostController) {
|
||||
fun CrossProfileIntentFilterScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
MyScaffold(R.string.intent_filter, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.intent_filter, 8.dp, onNavigateUp) {
|
||||
var action by remember { mutableStateOf("") }
|
||||
OutlinedTextField(
|
||||
value = action, onValueChange = { action = it },
|
||||
@@ -274,8 +284,10 @@ fun IntentFilter(navCtrl: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object DeleteWorkProfile
|
||||
|
||||
@Composable
|
||||
fun DeleteWorkProfile(navCtrl: NavHostController) {
|
||||
fun DeleteWorkProfileScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val focusMgr = LocalFocusManager.current
|
||||
@@ -283,7 +295,7 @@ fun DeleteWorkProfile(navCtrl: NavHostController) {
|
||||
var warning by remember { mutableStateOf(false) }
|
||||
var silent by remember { mutableStateOf(false) }
|
||||
var reason by remember { mutableStateOf("") }
|
||||
MyScaffold(R.string.delete_work_profile, 8.dp, navCtrl) {
|
||||
MyScaffold(R.string.delete_work_profile, 8.dp, onNavigateUp) {
|
||||
CheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { flag = flag xor WIPE_EXTERNAL_STORAGE }
|
||||
if(VERSION.SDK_INT >= 28) CheckBoxItem(R.string.wipe_euicc, flag and WIPE_EUICC != 0) { flag = flag xor WIPE_EUICC }
|
||||
CheckBoxItem(R.string.wipe_silently, silent) { silent = it }
|
||||
@@ -30,7 +30,6 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.writeClipBoard
|
||||
import com.bintianqi.owndroid.zhCN
|
||||
@@ -263,22 +262,13 @@ fun InfoCard(@StringRes strID: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MyScaffold(
|
||||
@StringRes title: Int,
|
||||
horizonPadding: Dp,
|
||||
navCtrl: NavHostController,
|
||||
displayTitle: Boolean = true,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) = MyScaffold(title, horizonPadding, { navCtrl.navigateUp() }, displayTitle, content)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MyScaffold(
|
||||
@StringRes title: Int,
|
||||
horizonPadding: Dp,
|
||||
onNavIconClicked: () -> Unit,
|
||||
displayTitle: Boolean,
|
||||
displayTitle: Boolean = true,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
@@ -6,14 +6,12 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.ThemeSettings
|
||||
|
||||
private val lightScheme = lightColorScheme(
|
||||
primary = primaryLight,
|
||||
@@ -93,10 +91,9 @@ private val darkScheme = darkColorScheme(
|
||||
|
||||
@Composable
|
||||
fun OwnDroidTheme(
|
||||
vm: MyViewModel,
|
||||
theme: ThemeSettings,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
||||
val darkTheme = theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())
|
||||
val context = LocalContext.current
|
||||
var colorScheme = when {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user