Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ android_app {
"androidx.core_core",
"setupcompat",
"setupdesign",
"setupwizard2-jackson-core",
"setupwizard2-jackson-databind",
"setupwizard2-jackson-annotations",
"setupwizard2-zxing-android",
"setupwizard2-zxing-core",
],

required: ["privapp_whitelist_app.grapheneos.setupwizard"],
Expand Down
15 changes: 15 additions & 0 deletions AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="com.stevesoltys.seedvault.RESTORE_BACKUP" />
<uses-permission android:name="android.permission.INTERNET"/>
<!-- To start the device owner provisioning workflow -->
<uses-permission android:name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
<!-- To install the admin app -->
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<!-- To factory reset if provisioning failed -->
<uses-permission android:name="android.permission.MASTER_CLEAR"/>

<application
android:name=".App"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.SetupWizard"
android:hardwareAccelerated="true"
tools:ignore="MissingApplicationIcon">

<activity
Expand All @@ -42,6 +50,13 @@
<activity android:name=".view.activity.MigrationActivity" />
<activity android:name=".view.activity.GesturesActivity" />
<activity android:name=".view.activity.FinishActivity" />
<activity android:name=".view.activity.MdmInstallActivity" />
<activity android:name=".view.activity.ProvisionActivity" />
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="portrait"
tools:replace="screenOrientation"
android:exported="false"/>
</application>

</manifest>
95 changes: 95 additions & 0 deletions java/app/grapheneos/setupwizard/action/AppInstaller.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package app.grapheneos.setupwizard.action

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.util.Log
import java.io.File
import java.io.FileInputStream

object AppInstaller {
private const val TAG = "AppInstaller"

const val ACTION_INSTALL_COMPLETE = "INSTALL_COMPLETE"
const val PACKAGE_NAME = "PACKAGE_NAME"

fun silentInstallApplication(
context: Context,
file: File
) : String? {
try {
val packageManager: PackageManager = context.packageManager
val packageInfo: PackageInfo = packageManager.getPackageArchiveInfo(file.path, 0)
?: throw Exception("Failed to parse the admin app package")

val packageName = packageInfo.packageName

Log.i(TAG, "Installing $packageName")
val `in` = FileInputStream(file)
val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL
)
params.setAppPackageName(packageName)
// set params
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)
val out = session.openWrite("COSU", 0, -1)
val buffer = ByteArray(65536)
var c: Int
while (`in`.read(buffer).also { c = it } != -1) {
out.write(buffer, 0, c)
}
session.fsync(out)
`in`.close()
out.close()
session.commit(
createIntentSender(
context,
sessionId,
packageName
)
)
Log.i(TAG, "Installation session committed")
return null
} catch (e: java.lang.Exception) {
Log.w(TAG, "PackageInstaller error: " + e.message)
e.printStackTrace()
return e.message
}
}


private fun createIntentSender(context: Context?, sessionId: Int, packageName: String?): IntentSender {
val intent: Intent = Intent(ACTION_INSTALL_COMPLETE)
if (packageName != null) {
intent.putExtra(PACKAGE_NAME, packageName)
}
val pendingIntent = PendingIntent.getBroadcast(
context,
sessionId,
intent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
)
return pendingIntent.intentSender
}

fun getPackageInstallerStatusMessage(status: Int): String {
when (status) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> return "PENDING_USER_ACTION"
PackageInstaller.STATUS_SUCCESS -> return "SUCCESS"
PackageInstaller.STATUS_FAILURE -> return "FAILURE_UNKNOWN"
PackageInstaller.STATUS_FAILURE_BLOCKED -> return "BLOCKED"
PackageInstaller.STATUS_FAILURE_ABORTED -> return "ABORTED"
PackageInstaller.STATUS_FAILURE_INVALID -> return "INVALID"
PackageInstaller.STATUS_FAILURE_CONFLICT -> return "CONFLICT"
PackageInstaller.STATUS_FAILURE_STORAGE -> return "STORAGE"
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> return "INCOMPATIBLE"
}
return "UNKNOWN"
}
}
Loading