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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.idea
.gradle
.kotlin
local.properties
build
capture
app/release

79 changes: 0 additions & 79 deletions app/build.gradle

This file was deleted.

89 changes: 89 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//classpath("com.google.android.gms:strict-version-matcher-plugin:1.2.4")

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
// id("com.google.android.gms.strict-version-matcher-plugin")
id("com.google.android.gms.oss-licenses-plugin")
}

android {
namespace = "com.amapi.extensibility.demo"
compileSdk = 35

defaultConfig {
applicationId = "com.amapi.extensibility.demo"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
isMinifyEnabled = false
isDebuggable = true
}

release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}

buildFeatures {
viewBinding = true
}
}

dependencies {
// implementation 'io.grpc:grpc-okhttp:1.44.0' // CURRENT_GRPC_VERSION
// implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// implementation 'com.google.android.material:material:1.12.0'
// implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
// implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
// implementation 'com.google.android.gms:play-services-oss-licenses:17.1.0'

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.constraint.layout)
api(libs.amapi)
implementation(libs.play.services.oss.licenses)
implementation(libs.material)
implementation(libs.androidx.constraint.layout)
implementation(libs.androidx.constraint.layout)
implementation(libs.coroutines)

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.rules)
androidTestImplementation(libs.androidx.runner)
androidTestImplementation(libs.androidx.espresso.core)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,75 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withSubstring
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import com.amapi.extensibility.demo.R
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests main activity from test app. */
/**
* Instrumentation tests for CommandActivity.
*/
@RunWith(AndroidJUnit4::class)
class CommandTest {
@Rule
@JvmField
var mActivityRule: ActivityTestRule<CommandActivity> =
ActivityTestRule(CommandActivity::class.java)
/**
* Replaced ActivityTestRule with ActivityScenarioRule to better manage the Activity's lifecycle.
*/
@get:Rule
val activityRule = ActivityScenarioRule(CommandActivity::class.java)

/**
* Tests retrieving a command by ID.
*/
@Test
fun getCommandById() {
fun getCommandById_displaysUnimplementedMessage() {
// Arrange: Input a command ID.
onView(withId(R.id.command_id_edittext)).perform(replaceText("1"))

// Act: Click the button to retrieve the command.
onView(withId(R.id.get_command_button)).perform(click())

// Assert: Verify the result text view displays the expected "UNIMPLEMENTED" message.
onView(withId(R.id.command_result_textview))
.check(ViewAssertions.matches(withSubstring("UNIMPLEMENTED")))
}

@Test
fun issueCommand() {
onView(withId(R.id.clear_app_package_edittext)).perform(replaceText("com.android.settings"))
fun testClearAppDataCommand_editTextIsPresent(){
onView(withId(R.id.clear_app_package_edittext))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
}

@Test
fun testClearAppDataCommand_editTextCanBeFilled(){
onView(withId(R.id.clear_app_package_edittext)).perform(replaceText(TEST_PACKAGE_NAME))
onView(withId(R.id.clear_app_package_edittext))
.check(ViewAssertions.matches(ViewMatchers.withText(TEST_PACKAGE_NAME)))
}

@Test
fun testClearAppDataCommand_buttonIsPresent(){
onView(withId(R.id.clear_app_command_button))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
}

/**
* Verifies that attempting to clear an app's data via the UI results in the correct "UNIMPLEMENTED"
* message being displayed. This checks that the command is received, but the action is not supported yet.
*/
@Test
fun testClearAppDataCommand_displaysUnimplementedMessage() {
onView(withId(R.id.clear_app_package_edittext)).perform(replaceText(TEST_PACKAGE_NAME))
onView(withId(R.id.clear_app_command_button)).perform(click())
onView(withId(R.id.command_result_textview))
.check(ViewAssertions.matches(withSubstring("UNIMPLEMENTED")))
}
}

companion object {
private const val TEST_PACKAGE_NAME = "com.android.settings"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,37 @@ package com.amapi.extensibility.demo.commands

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.amapi.extensibility.demo.R
import com.amapi.extensibility.demo.databinding.ActivityCommandBinding
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity

class CommandActivity : AppCompatActivity() {
private lateinit var commandViewModel: CommandViewModel
private lateinit var binding: ActivityCommandBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_command)
commandViewModel = ViewModelProvider(this).get(CommandViewModel::class.java)
binding = ActivityCommandBinding.inflate(layoutInflater)
setContentView(binding.root)

commandViewModel = ViewModelProvider(this)[CommandViewModel::class.java]
commandViewModel.commandResultLiveData.observe(this) { results ->
findViewById<TextView>(R.id.command_result_textview).apply {
text = results
}
}
findViewById<Button>(R.id.get_command_button).setOnClickListener {
commandViewModel.getCommand(findViewById<EditText>(R.id.command_id_edittext).text.toString())
binding.commandResultTextview.text = results
}
findViewById<Button>(R.id.clear_app_command_button).setOnClickListener {
commandViewModel.issueClearAppDataCommand(
findViewById<EditText>(R.id.clear_app_package_edittext).text.toString()
)
}
findViewById<Button>(R.id.license_button).setOnClickListener {

setupListeners()
}

private fun setupListeners() {
binding.getCommandButton.setOnClickListener {
commandViewModel.getCommand(binding.commandIdEdittext.text.toString())
}
binding.clearAppCommandButton.setOnClickListener {
commandViewModel.issueClearAppDataCommand(binding.clearAppPackageEdittext.text.toString())
}
binding.licenseButton.setOnClickListener {
startActivity(Intent(this, OssLicensesMenuActivity::class.java))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ package com.amapi.extensibility.demo.commands
import com.google.android.managementapi.commands.model.Command
import java.lang.StringBuilder

/** Contains command related utility methods. */
/**
* Utility methods for working with [Command] objects.
*/
object CommandUtils {
/**
* Returns the properly formatted string representation of the given [Command] in a way that can
* be logged and/or surfaced in UI.
* Generates a human-readable string representation of a [Command] for logging or UI display.
*
* The output includes the command's ID, creation time, completion time, and state.
* If the command's status is `CLEAR_APPS_DATA_STATUS`, it will also include a detailed breakdown
* of the status of each app, mapping app identifiers to their clear status.
*
* @param command The [Command] to parse and format.
* @return A formatted string representation of the command.
*/
fun parseCommandForPrettyPrint(command: Command): String {
val stringBuilder =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.google.android.managementapi.commands.model.Command

object InMemoryCommandRepository {
private val commandLiveData: MutableLiveData<Command> = MutableLiveData()
fun onCommandStatusChanged(command: Command?) {
fun onCommandStatusChanged(command: Command) {
commandLiveData.postValue(command)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,22 @@ import com.google.android.managementapi.commands.CommandListener
import com.google.android.managementapi.commands.model.Command
import com.google.android.managementapi.notification.NotificationReceiverService

/**
* Service responsible for receiving and processing notifications from Android Policy Controller.
*
* This service extends [NotificationReceiverService] to handle incoming notifications.
* It provides a custom [CommandListener] to listen for changes in command status.
*/
class NotificationReceiverService : NotificationReceiverService() {

/**
* Returns a [CommandListener] that listens for command status changes.
*
* This listener is notified when the status of a command changes. When a command's status
* changes, the listener logs the change and updates the in-memory command repository.
*
* @return A [CommandListener] instance.
*/
override fun getCommandListener(): CommandListener {
return object : CommandListener {
override fun onCommandStatusChanged(command: Command) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@
*/
package com.amapi.extensibility.demo.util

/**
* The tag used for logging throughout the Amapi Extensibility Demo application.
*/
const val TAG = "AmapiExtensibilityDemo"
Loading