diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 6553eb648..0b803370d 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -65,6 +65,7 @@ jobs:
##########################################
# Upload APK for Java version
- name: Upload APK Debug for Java
+ if: always()
uses: actions/upload-artifact@v4
with:
name: Java-Debug-APK
diff --git a/README.md b/README.md
index 956bf0940..3a03ee41c 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,6 @@ The Inventory Agent for Android allows you to collect a complete inventory of yo
Now you can choose (from the server information) whether this inventory should create a ```Phone``` or a ```Computer``` on GLPI
-
## Compatibility Matrix
### GLPI Android Inventory Agent
@@ -66,6 +65,31 @@ GLPI Android Inventory Agent is compatible with Android 4.1 and higher (to Andro
Are you having trouble installing our GLPI Android Agent? You can subscribe to our professional support GLPI Network [here](https://services.glpi-network.com).
+## Configuring the Agent with an EMM / MDM Tool
+
+The GLPI agent can be deployed/configured from an **MDM** / **EMM** tool
+
+- Samsung Knox
+- AirWatch
+- InTunes
+- MobileIron
+- etc.
+
+As long as the **MDM** / **EMM** tool is compatible with [managed configurations](https://developer.android.com/work/managed-configurations), you can configure the GLPI Agent (at deployment or on the fly).
+
+Here is the list of configurable settings:
+
+- **`auto_start_on_boot`** => Run an inventory at startup (`Bool` `true` / `false`)
+- **`automatic_inventory`** => Enable automatic inventory (`Bool` `true` / `false`)
+- **`frequency`** => Frequency of automatic inventory (`String` `Day` / `Week` / `Month` default `Day`)
+- **`server_configuration_list`** => (`Bundle`)
+ - **`server_url`** => GLPI server URL (`String`)
+ - **`server_tag`** => TAG (`String`)
+ - **`server_login`** => Username for basic authentication (`String`)
+ - **`server_password`** => Password for basic authentication (`String`)
+ - **`server_itemtype`** => Asset type in GLPI (`String` `Computer` / `Phone` default `Computer`)
+ - **`server_custom_asset_serial`** => Custom serial number to replace the one generated by the agent (`String`)
+
## Documentation
We maintain a detailed documentation of the project on the website, check the [How-tos](http://glpi-project.github.io/android-inventory-agent/howtos/) and [Development](http://glpi-project.github.io/android-inventory-agent/) section.
diff --git a/app/build.gradle b/app/build.gradle
index 213cf8e54..7864d68ef 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,7 +7,7 @@ android {
defaultConfig {
applicationId "org.glpi.inventory.agent"
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -53,11 +53,16 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
+ /* app restriction */
+ implementation 'androidx.enterprise:enterprise-feedback:1.0.0'
+
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
exclude group: 'com.androidx', module: 'support-annotations'
})
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
+ androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
+
testImplementation 'org.mockito:mockito-core:2.18.3'
androidTestImplementation 'org.mockito:mockito-android:2.18.3'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9ea13b5bc..e88950e3a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -39,6 +39,8 @@ android:versionName="">
+
+
diff --git a/app/src/main/assets/inventory.xml b/app/src/main/assets/inventory.xml
index 3295eb971..21882e2cb 100644
--- a/app/src/main/assets/inventory.xml
+++ b/app/src/main/assets/inventory.xml
@@ -2712,4 +2712,4 @@
wp
-
\ No newline at end of file
+
diff --git a/app/src/main/java/org/glpi/inventory/agent/broadcast/TimeAlarm.java b/app/src/main/java/org/glpi/inventory/agent/broadcast/TimeAlarm.java
index 623871378..360d92aba 100644
--- a/app/src/main/java/org/glpi/inventory/agent/broadcast/TimeAlarm.java
+++ b/app/src/main/java/org/glpi/inventory/agent/broadcast/TimeAlarm.java
@@ -41,9 +41,12 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.Build;
import android.os.PowerManager;
import android.preference.PreferenceManager;
+import androidx.annotation.RequiresApi;
+
import org.flyve.inventory.InventoryTask;
import org.glpi.inventory.agent.R;
import org.glpi.inventory.agent.schema.ServerSchema;
@@ -126,6 +129,7 @@ public void onTaskError(Throwable error) {
* Schedules the alarm
* @param context
*/
+ @RequiresApi(api = Build.VERSION_CODES.M)
public void setAlarm(Context context) {
AgentLog.d("Set Alarm");
@@ -133,7 +137,7 @@ public void setAlarm(Context context) {
AlarmManager am =(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(context, TimeAlarm.class);
i.setAction("org.glpi.inventory.agent.ALARM");
- PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
+ PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_IMMUTABLE);
SharedPreferences customSharedPreference = PreferenceManager.getDefaultSharedPreferences(context);
String timeInventory = customSharedPreference.getString("timeInventory", "Week");
diff --git a/app/src/main/java/org/glpi/inventory/agent/ui/ActivityMain.java b/app/src/main/java/org/glpi/inventory/agent/ui/ActivityMain.java
index 9815100af..a78c87e3d 100644
--- a/app/src/main/java/org/glpi/inventory/agent/ui/ActivityMain.java
+++ b/app/src/main/java/org/glpi/inventory/agent/ui/ActivityMain.java
@@ -40,11 +40,13 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.RestrictionsManager;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Parcelable;
import android.preference.PreferenceManager;
import androidx.appcompat.app.ActionBarDrawerToggle;
@@ -52,6 +54,8 @@
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.enterprise.feedback.KeyedAppState;
+import androidx.enterprise.feedback.KeyedAppStatesReporter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -66,18 +70,27 @@
import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import org.flyve.inventory.InventoryTask;
import org.glpi.inventory.agent.R;
+import org.glpi.inventory.agent.core.detailserver.DetailServer;
+import org.glpi.inventory.agent.core.detailserver.DetailServerPresenter;
import org.glpi.inventory.agent.core.main.Main;
import org.glpi.inventory.agent.core.main.MainPresenter;
import org.glpi.inventory.agent.preference.GlobalParametersPreference;
import org.glpi.inventory.agent.preference.InventoryParametersPreference;
+import org.glpi.inventory.agent.schema.ServerSchema;
import org.glpi.inventory.agent.service.InventoryService;
+import org.glpi.inventory.agent.utils.AgentLog;
import org.glpi.inventory.agent.utils.Helpers;
+import org.glpi.inventory.agent.utils.HttpInventory;
import org.glpi.inventory.agent.utils.LocalPreferences;
import org.glpi.inventory.agent.utils.LocalStorage;
+import org.json.JSONException;
+import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
public class ActivityMain extends AppCompatActivity
@@ -115,11 +128,150 @@ public void onReceive(Context context, Intent intent) {
}
};
+ private BroadcastReceiver appRestrictionChange = null;
+ private KeyedAppStatesReporter appRestrictionChangeReporter = null;
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ IntentFilter restrictionsFilter =
+ new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+
+ BroadcastReceiver appRestrictionChange = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ resolveRestrictions();
+ }
+ };
+
+ registerReceiver(appRestrictionChange, restrictionsFilter);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (appRestrictionChange != null) {
+ unregisterReceiver(appRestrictionChange);
+ appRestrictionChange = null;
+ }
+ }
+
+ public static void enterpriseFeedback(Context context,
+ String key,
+ String message,
+ String data,
+ int severity) {
+ KeyedAppStatesReporter keyedAppStatesReporter = KeyedAppStatesReporter.create(context);
+ KeyedAppState keyedAppStateMessage = KeyedAppState.builder()
+ .setSeverity(severity)
+ .setKey(key)
+ .setMessage(message)
+ .setData(data)
+ .build();
+ List list = new ArrayList<>();
+ list.add(keyedAppStateMessage);
+ keyedAppStatesReporter.setStates(list);
+ }
+
+ private void resolveRestrictions() {
+ AgentLog.e("EMM - START resolve restrictions");
+ RestrictionsManager myRestrictionsMgr = null;
+ myRestrictionsMgr = (RestrictionsManager) getSystemService(Context.RESTRICTIONS_SERVICE);
+ Bundle appRestrictions = myRestrictionsMgr.getApplicationRestrictions();
+
+ SharedPreferences customSharedPreference = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ SharedPreferences.Editor editor = customSharedPreference.edit();
+
+ if (appRestrictions.containsKey("automatic_inventory")) {
+ editor.putBoolean("autoStartInventory", appRestrictions.getBoolean("automatic_inventory"));
+ enterpriseFeedback(getApplicationContext(), "automatic_inventory", "automatic_inventory option set successfully", appRestrictions.getBoolean("automatic_inventory") ? "true" : "false", KeyedAppState.SEVERITY_INFO);
+ AgentLog.e("EMM - set automatic inventory to " + appRestrictions.getBoolean("automatic_inventory"));
+ editor.apply();
+ }
+
+ if (appRestrictions.containsKey("frequency")) {
+ editor.putString("timeInventory", appRestrictions.getString("frequency"));
+ enterpriseFeedback(getApplicationContext(), "frequency", "frequency option set successfully", appRestrictions.getString("frequency"), KeyedAppState.SEVERITY_INFO);
+ AgentLog.e("EMM - set frequency to " + appRestrictions.getString("frequency"));
+ editor.apply();
+ }
+
+ if (appRestrictions.containsKey("auto_start_on_boot")) {
+ editor.putBoolean("boot", appRestrictions.getBoolean("auto_start_on_boot"));
+ enterpriseFeedback(getApplicationContext(), "auto_start_on_boot", "auto_start_on_boot option set successfully", appRestrictions.getBoolean("auto_start_on_boot") ? "true" : "false", KeyedAppState.SEVERITY_INFO);
+ AgentLog.e("EMM - set auto start on boot to " + appRestrictions.getBoolean("auto_start_on_boot"));
+ editor.apply();
+ }
+
+ Parcelable[] parcelables = appRestrictions.getParcelableArray("server_configuration_list");
+ if (parcelables != null && parcelables.length > 0) {
+ final Context context = getApplicationContext();
+ for (int i = 0; i < parcelables.length; i++) {
+ Bundle serverConfig = (Bundle) parcelables[i];
+ JSONObject jsonServerConfig = new JSONObject();
+ LocalPreferences preferences = new LocalPreferences(context);
+
+ if (serverConfig.getString("server_url").isEmpty()) {
+ enterpriseFeedback(getApplicationContext(), "server_url", "Error server URL is mandatory -> ", serverConfig.getString("server_url"), KeyedAppState.SEVERITY_ERROR);
+ AgentLog.e("EMM - server url is mandatory");
+ continue;
+ }
+
+ try {
+ jsonServerConfig.put("address", serverConfig.getString("server_url"));
+ jsonServerConfig.put("tag", serverConfig.getString("server_tag"));
+ jsonServerConfig.put("login", serverConfig.getString("server_login"));
+ jsonServerConfig.put("pass", serverConfig.getString("server_password"));
+ jsonServerConfig.put("itemtype", serverConfig.getString("server_itemtype"));
+ jsonServerConfig.put("serial", serverConfig.getString("server_custom_asset_serial"));
+
+ AgentLog.e("EMM - Receive the following configuration '" + jsonServerConfig.toString());
+
+ JSONObject local_server = preferences.loadJSONObject(serverConfig.getString("server_url"));
+ AgentLog.e("EMM - Try to load '" + serverConfig.getString("server_url") + "' server if exist");
+ AgentLog.e("EMM - Found '" + local_server.toString() + "'");
+ AgentLog.e("EMM - Exist ? -> '" + !local_server.toString().equals("{}") + "'");
+
+ if (local_server.toString().equals("{}")) {
+ ArrayList serverArray = preferences.loadServer();
+ serverArray.add(serverConfig.getString("server_url"));
+ preferences.saveServer(serverArray);
+ preferences.saveJSONObject(serverConfig.getString("server_url"), jsonServerConfig);
+ enterpriseFeedback(getApplicationContext(), "server_url", "server_url added successfully", serverConfig.getString("server_url"), KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_tag", "server_tag added successfully", serverConfig.getString("server_tag"), KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_login", "server_login added successfully", serverConfig.getString("server_login"), KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_password", "server_password added successfully", "***", KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_itemtype", "server_itemtype added successfully", serverConfig.getString("server_itemtype"), KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_custom_asset_serial", "server_custom_asset_serial added successfully", serverConfig.getString("server_custom_asset_serial"), KeyedAppState.SEVERITY_INFO);
+ AgentLog.e("EMM - Server added successfully");
+ } else {
+ preferences.deletePreferences(serverConfig.getString("server_url"));
+ preferences.saveJSONObject(serverConfig.getString("server_url"), jsonServerConfig);
+ enterpriseFeedback(getApplicationContext(), "server_url", "server_url updated successfully", serverConfig.getString("server_url"), KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_tag", "server_tag updated successfully", serverConfig.getString("server_tag"), KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_login", "server_login updated successfully", serverConfig.getString("server_login"), KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_password", "server_password updated successfully", "***", KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_itemtype", "server_itemtype updated successfully", serverConfig.getString("server_itemtype"), KeyedAppState.SEVERITY_INFO);
+ enterpriseFeedback(getApplicationContext(), "server_custom_asset_serial", "server_custom_asset_serial updated successfully", serverConfig.getString("server_custom_asset_serial"), KeyedAppState.SEVERITY_INFO);
+ AgentLog.e("EMM - Server updated successfully");
+ }
+
+ } catch (JSONException e) {
+ enterpriseFeedback(getApplicationContext(), "server_url", "error while adding/updating server -> " + e.getMessage(), serverConfig.getString("server_url"), KeyedAppState.SEVERITY_ERROR);
+ AgentLog.e("EMM - error while adding/updating server");
+ AgentLog.e("EMM - " + e.getMessage());
+ }
+ }
+ } else {
+ AgentLog.e("EMM - 'server_configuration_list' key is empty");
+ }
+ AgentLog.e("EMM - END resolve restrictions");
+ }
@Override
protected void onResume() {
super.onResume();
registerReceiver(broadcastReceiver,new IntentFilter(InventoryService.TIMER_RECEIVER));
+ resolveRestrictions();
}
@Override
@@ -235,6 +387,10 @@ public void onClick(View view) {
}
});
+ //app restriction change
+ KeyedAppStatesReporter appRestrictionChangeReporter = KeyedAppStatesReporter.create(getApplicationContext());
+
+
}
private void disableFab(){
diff --git a/app/src/main/res/values/restrictions_values.xml b/app/src/main/res/values/restrictions_values.xml
new file mode 100644
index 000000000..3442829be
--- /dev/null
+++ b/app/src/main/res/values/restrictions_values.xml
@@ -0,0 +1,38 @@
+
+
+
+
+ Type
+ Type of asset
+ Computer
+ Phone
+
+ - @string/entry_server_itemtype_computer
+ - @string/entry_server_itemtype_phone
+
+
+ - Computer
+ - Phone
+
+ Computer
+
+
+ Frequency
+ Frequency
+ Day
+ Week
+ Month
+
+ - @string/frequency_day
+ - @string/frequency_week
+ - @string/frequency_month
+
+
+ - Day
+ - Week
+ - Month
+
+ Day
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ee528166c..fcbc8140b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -302,5 +302,17 @@
To perform schedule inventory app is running in background
Disable notification
+
+
+ Startup from system boot?
+ Automatic inventory?
+ Server list
+ Server configuration
+ Server URL
+ Server TAG
+ Server login
+ Server password
+ Custom asset serial
+ Asset itemtype
diff --git a/app/src/main/res/xml/app_restrictions.xml b/app/src/main/res/xml/app_restrictions.xml
new file mode 100644
index 000000000..825380b55
--- /dev/null
+++ b/app/src/main/res/xml/app_restrictions.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+