Refactor code related to navigation, close #104

New PackageChooserActivity
Delete code of parsing apk metadata
This commit is contained in:
BinTianqi
2025-02-12 17:27:07 +08:00
parent 94da5ac4b2
commit 44d2ab7e2e
31 changed files with 1048 additions and 1621 deletions

View File

@@ -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);
}
}
}

View File

@@ -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 ?: "")

View File

@@ -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()

View File

@@ -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)

View File

@@ -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
) {

View File

@@ -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))

View File

@@ -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 {

View 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)) }
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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") }

View File

@@ -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")
}

View File

@@ -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))

View File

@@ -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(

View File

@@ -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 }
}

View File

@@ -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(),

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)) {
}*/
}
}
}

View File

@@ -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(

View File

@@ -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 }

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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
}
}
}

View File

@@ -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,
)
}
}

View File

@@ -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()
}
}