diff --git a/Readme.md b/Readme.md
index fd664c6..3f5eb0f 100644
--- a/Readme.md
+++ b/Readme.md
@@ -85,7 +85,7 @@ java.lang.SecurityException: Neither user 2000 nor current process has android.p
```
解决办法:
-- 在开发者设置中打开`USB debugging (Security setting)`。
+- 在开发者设置中打开`USB调试(安全设置)`。
- 在root命令行中执行激活命令
#### ColorOS
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ef75dfe..0aa7b3e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,7 +25,8 @@
android:name=".MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize|stateHidden"
- android:theme="@style/Theme.OwnDroid">
+ android:theme="@style/Theme.OwnDroid"
+ android:launchMode="singleInstance">
@@ -55,6 +56,11 @@
+
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 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);
- }
- }
-}
diff --git a/app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt b/app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt
index 25e7772..a45f3af 100644
--- a/app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt
@@ -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 ?: "")
diff --git a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt
index 619e4d7..c2e60aa 100644
--- a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt
@@ -73,7 +73,8 @@ class AppInstallerActivity:FragmentActivity() {
val vm by viewModels()
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()
diff --git a/app/src/main/java/com/bintianqi/owndroid/Auth.kt b/app/src/main/java/com/bintianqi/owndroid/Auth.kt
index cae524c..6771a3c 100644
--- a/app/src/main/java/com/bintianqi/owndroid/Auth.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/Auth.kt
@@ -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)
diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt
index 65ca8e2..5c9810c 100644
--- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt
@@ -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()
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 { 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 {
+ 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(it.arguments!!, ::navigateUp) { navCtrl.navigate(it) } }
+ composable(mapOf(serializableNavTypePair>())) { AccountsScreen(it.toRoute(), ::navigateUp) }
+ composable { DeviceAdminScreen(::navigateUp) }
+ composable { ProfileOwnerScreen(::navigateUp) }
+ composable { DeviceOwnerScreen(::navigateUp) }
+ composable { DelegatedAdminsScreen(::navigateUp) }
+ composable { DeviceInfoScreen(::navigateUp) }
+ composable { LockScreenInfoScreen(::navigateUp) }
+ composable { SupportMessageScreen(::navigateUp) }
+ composable { TransferOwnershipScreen(::navigateUp) }
+
+ composable { SystemManagerScreen(::navigateUp) { navCtrl.navigate(it) } }
+ composable { SystemOptionsScreen(::navigateUp) }
+ composable { KeyguardScreen(::navigateUp) }
+ composable { HardwareMonitorScreen(::navigateUp) }
+ composable { ChangeTimeScreen(::navigateUp) }
+ composable { ChangeTimeZoneScreen(::navigateUp) }
+ //composable<> { KeyPairs(::navigateUp) }
+ composable { ContentProtectionPolicyScreen(::navigateUp) }
+ composable { PermissionPolicyScreen(::navigateUp) }
+ composable { MtePolicyScreen(::navigateUp) }
+ composable { NearbyStreamingPolicyScreen(::navigateUp) }
+ composable { LockTaskModeScreen(::navigateUp) }
+ composable { CaCertScreen(::navigateUp) }
+ composable { SecurityLoggingScreen(::navigateUp) }
+ composable { DisableAccountManagementScreen(::navigateUp) }
+ composable { SystemUpdatePolicy(::navigateUp) }
+ composable { InstallSystemUpdateScreen(::navigateUp) }
+ composable { FrpPolicyScreen(::navigateUp) }
+ composable { WipeDataScreen(::navigateUp) }
+
+ composable { NetworkScreen(::navigateUp) { navCtrl.navigate(it) } }
+ composable {
+ 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 { NetworkOptionsScreen(::navigateUp) }
+ composable { AddNetworkScreen(it.arguments!!, ::navigateUp) }
+ composable { WifiSecurityLevelScreen(::navigateUp) }
+ composable { WifiSsidPolicyScreen(::navigateUp) }
+ composable { NetworkStatsScreen(::navigateUp) { navCtrl.navigate(it) } }
+ composable(mapOf(serializableNavTypePair>())) {
+ NetworkStatsViewerScreen(it.toRoute()) { navCtrl.navigateUp() }
+ }
+ composable { PrivateDnsScreen(::navigateUp) }
+ composable { AlwaysOnVpnPackageScreen(::navigateUp) }
+ composable { RecommendedGlobalProxyScreen(::navigateUp) }
+ composable { NetworkLoggingScreen(::navigateUp) }
+ composable { WifiAuthKeypairScreen(::navigateUp) }
+ composable { PreferentialNetworkServiceScreen(::navigateUp) }
+ composable { OverrideApnScreen(::navigateUp) }
+
+ composable { WorkProfileScreen(::navigateUp) { navCtrl.navigate(it) } }
+ composable { OrganizationOwnedProfileScreen(::navigateUp) }
+ composable { CreateWorkProfileScreen(::navigateUp) }
+ composable { SuspendPersonalAppScreen(::navigateUp) }
+ composable { CrossProfileIntentFilterScreen(::navigateUp) }
+ composable { DeleteWorkProfileScreen(::navigateUp) }
+
+ composable { ApplicationsScreen(::navigateUp) }
+
+ composable {
+ 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(mapOf(serializableNavTypePair>())) {
+ 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 { UsersScreen(::navigateUp) { navCtrl.navigate(it) } }
+ composable { UserInfoScreen(::navigateUp) }
+ composable { UsersOptionsScreen(::navigateUp) }
+ composable { UserOperationScreen(::navigateUp) }
+ composable { CreateUserScreen(::navigateUp) }
+ composable { ChangeUsernameScreen(::navigateUp) }
+ composable { ChangeUserIconScreen(::navigateUp) }
+ composable { UserSessionMessageScreen(::navigateUp) }
+ composable { 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 { PasswordScreen(::navigateUp) { navCtrl.navigate(it) } }
+ composable { PasswordInfoScreen(::navigateUp) }
+ composable { ResetPasswordTokenScreen(::navigateUp) }
+ composable { ResetPasswordScreen(::navigateUp) }
+ composable { RequiredPasswordComplexityScreen(::navigateUp) }
+ composable { KeyguardDisabledFeaturesScreen(::navigateUp) }
+ composable { 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 { SettingsScreen(::navigateUp) { navCtrl.navigate(it) } }
+ composable { SettingsOptionsScreen(::navigateUp) }
+ composable {
+ val theme by vm.theme.collectAsStateWithLifecycle()
+ AppearanceScreen(::navigateUp, theme) { vm.theme.value = it }
+ }
+ composable { AuthSettingsScreen(::navigateUp) }
+ composable { ApiSettings(::navigateUp) }
+ composable { AboutScreen(::navigateUp) }
- composable(route = "PackageSelector") { PackageSelector(navCtrl, vm) }
-
- composable(
- route = "Authenticate",
+ composable(
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
) {
diff --git a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt b/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt
index 5279221..1bf3f21 100644
--- a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt
@@ -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))
diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt
index efeb6a9..f4f0eea 100644
--- a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt
@@ -9,8 +9,6 @@ import kotlinx.coroutines.launch
class MyViewModel(application: Application): AndroidViewModel(application) {
val theme = MutableStateFlow(ThemeSettings())
- val installedPackages = mutableListOf()
- val selectedPackage = MutableStateFlow("")
val userRestrictions = MutableStateFlow(Bundle())
init {
diff --git a/app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt b/app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt
new file mode 100644
index 0000000..62bb659
--- /dev/null
+++ b/app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt
@@ -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()
+ val vm by viewModels()
+ 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())
+ 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().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, 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)) }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/PackageSelector.kt b/app/src/main/java/com/bintianqi/owndroid/PackageSelector.kt
deleted file mode 100644
index 08098ba..0000000
--- a/app/src/main/java/com/bintianqi/owndroid/PackageSelector.kt
+++ /dev/null
@@ -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))
- }
- }
-}
diff --git a/app/src/main/java/com/bintianqi/owndroid/Settings.kt b/app/src/main/java/com/bintianqi/owndroid/Settings.kt
index 6d9c23e..4fd2de7 100644
--- a/app/src/main/java/com/bintianqi/owndroid/Settings.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/Settings.kt
@@ -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") }
diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt
index 3fd33c1..7bc9bf4 100644
--- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt
@@ -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 serializableNavTypePair() =
+ typeOf() to object : NavType(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() {
+ override fun createIntent(context: Context, input: Nothing?): Intent =
+ Intent(context, PackageChooserActivity::class.java)
+ override fun parseResult(resultCode: Int, intent: Intent?): String? =
+ intent?.getStringExtra("package")
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt
index 45e5180..3976e42 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt
@@ -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 { HomeScreen(pkgName) { navController.navigate(it) } }
+ composable { UserControlDisabledPackagesScreen(pkgName) }
+ composable { PermissionManagerScreen(pkgName) }
+ composable { CrossProfilePackagesScreen(pkgName) }
+ composable { CrossProfileWidgetProvidersScreen(pkgName) }
+ composable { CredentialManagerPolicyScreen(pkgName) }
+ composable { PermittedAccessibilityServicesScreen(pkgName) }
+ composable { PermittedInputMethodsScreen(pkgName) }
+ composable { KeepUninstalledPackagesScreen(pkgName) }
+ composable { 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))
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt
index 9863495..24f4a64 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt
@@ -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() }
@@ -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() }
val refreshPolicy = {
@@ -862,10 +871,12 @@ fun NetworkStats.toBucketList(): List {
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
+) {
+ @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(
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt
index 7dc106f..102bf9f 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt
@@ -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 }
}
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt
index d7142f8..8b41727 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt
@@ -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(),
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt
index 59bc771..aa46716 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt
@@ -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
+) {
+ @Serializable data class Account(val type: String, val name: String)
+}
+
@Composable
-fun AccountsViewer(navCtrl: NavHostController, navArgs: Bundle) {
- val accounts = navArgs.getParcelableArray("accounts") as Array
- 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)
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt
index 130780c..c3e9c4f 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt
@@ -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() }
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() }
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)
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt
index 1ab8e24..221ec6c 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt
@@ -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) -> 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
+)
+
@RequiresApi(24)
@Composable
-fun UserRestrictionScreen(
- title: Int, items: List, 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)) {
- }*/
}
}
}
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt
index 94704d2..69887bb 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt
@@ -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(
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt
similarity index 91%
rename from app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt
rename to app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt
index 0d45c2b..f24a933 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt
@@ -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 }
diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt
index 741d5d6..e469dff 100644
--- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt
@@ -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()
diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt b/app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt
index 0e1c8e0..02bac15 100644
--- a/app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt
@@ -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 {
diff --git a/app/src/main/java/com/github/fishb1/apkinfo/ApkInfo.kt b/app/src/main/java/com/github/fishb1/apkinfo/ApkInfo.kt
deleted file mode 100644
index 716e55f..0000000
--- a/app/src/main/java/com/github/fishb1/apkinfo/ApkInfo.kt
+++ /dev/null
@@ -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
- }
- }
-}
diff --git a/app/src/main/java/com/github/fishb1/apkinfo/ApkInfoBuilder.kt b/app/src/main/java/com/github/fishb1/apkinfo/ApkInfoBuilder.kt
deleted file mode 100644
index b932e11..0000000
--- a/app/src/main/java/com/github/fishb1/apkinfo/ApkInfoBuilder.kt
+++ /dev/null
@@ -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,
- )
- }
-}
diff --git a/app/src/main/java/com/github/fishb1/apkinfo/ManifestUtils.kt b/app/src/main/java/com/github/fishb1/apkinfo/ManifestUtils.kt
deleted file mode 100644
index f178e45..0000000
--- a/app/src/main/java/com/github/fishb1/apkinfo/ManifestUtils.kt
+++ /dev/null
@@ -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()
- }
-}
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 598a85c..e949d81 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -369,8 +369,6 @@
Приложения
- Выбор пакета
- Загрузка
Показывать пользовательские приложения
Показывать системные приложения
Приостановить
@@ -419,7 +417,6 @@
Владелец профиля может использовать ограниченные функции
Включите переключатель, чтобы отключить эту функцию.
Функции в рабочем профиле ограничены.
- Сеть
Другие подключения
Медиа
Другое
@@ -646,6 +643,8 @@
Доступ к датчикам тела в фоновом режиме
Распознавание активности
+ Package chooser
+
Установщик приложений
Режим
Полная установка
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 0ed2fed..431c5a1 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -373,8 +373,6 @@
Uygulamalar
- Paket seçici
- Yükleniyor
Kullanıcı uygulamalarını göster
Sistem uygulamalarını göster
Askıya al
@@ -424,7 +422,6 @@
Profil sahibi sınırlı işlev kullanabilir
Bu işlevi devre dışı bırakmak için bir anahtar açın.
İş profilindeki işlevler sınırlıdır.
- Ağ
Diğer bağlantı
Medya
Diğer
@@ -648,6 +645,8 @@
Arka planda vücut sensörlerine eriş
Aktivite tanıma
+ Package chooser
+
App installer
Mode
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index d461170..fe761ca 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -362,8 +362,6 @@
应用程序
- 应用选择器
- 加载中
显示用户应用
显示系统应用
挂起
@@ -411,7 +409,6 @@
Profile owner无法使用部分功能
打开开关后会禁用对应的功能
工作资料中部分功能无效
- 网络和互联网
更多连接
媒体
其他
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9774d6b..dc1e3ca 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -398,8 +398,6 @@
Applications
- Package selector
- Loading
Show user apps
Show system apps
Suspend
@@ -448,7 +446,6 @@
Profile owner can use limited function
Turn on a switch to disable that function.
Functions in work profile is limited.
- Network
Other connection
Media
Other
@@ -673,6 +670,8 @@
Access body sensors in background
Activity recognition
+ Package chooser
+
App installer
Mode
Full install