Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(Core): handle EMM managed configurations #396

Merged
merged 2 commits into from
Sep 18, 2024
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
7 changes: 6 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ android {

defaultConfig {
applicationId "org.glpi.inventory.agent"
minSdkVersion 19
minSdkVersion 21
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -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'

Expand Down
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ android:versionName="">

<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="${crashlyticsEnabled}"/>

<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions"/>

<activity android:exported="true" android:name="org.glpi.inventory.agent.ui.ActivitySplash" android:theme="@style/NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/assets/inventory.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2712,4 +2712,4 @@
<DRIVER>wp</DRIVER>
</CONTROLLERS>
</CONTENT>
</REQUEST>
</REQUEST>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -126,14 +129,15 @@ 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");

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");
Expand Down
156 changes: 156 additions & 0 deletions app/src/main/java/org/glpi/inventory/agent/ui/ActivityMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,22 @@
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;
import androidx.appcompat.app.AppCompatActivity;
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;
Expand All @@ -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
Expand Down Expand Up @@ -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<KeyedAppState> 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<String> 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
Expand Down Expand Up @@ -235,6 +387,10 @@ public void onClick(View view) {
}
});

//app restriction change
KeyedAppStatesReporter appRestrictionChangeReporter = KeyedAppStatesReporter.create(getApplicationContext());


}

private void disableFab(){
Expand Down
38 changes: 38 additions & 0 deletions app/src/main/res/values/restrictions_values.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<!-- Choice restriction for itemtype -->
<string name="title_server_itemtype">Type</string>
<string name="description_server_itemtype">Type of asset</string>
<string name="entry_server_itemtype_computer">Computer</string>
<string name="entry_server_itemtype_phone">Phone</string>
<string-array name="entries_server_itemtype">
<item>@string/entry_server_itemtype_computer</item>
<item>@string/entry_server_itemtype_phone</item>
</string-array>
<string-array name="entry_values_server_itemtype">
<item>Computer</item>
<item>Phone</item>
</string-array>
<string name="default_server_itemtype">Computer</string>

<!-- Choice restriction for frequency -->
<string name="frequency">Frequency</string>
<string name="frequency_description">Frequency</string>
<string name="frequency_day">Day</string>
<string name="frequency_week">Week</string>
<string name="frequency_month">Month</string>
<string-array name="entries_frequency">
<item>@string/frequency_day</item>
<item>@string/frequency_week</item>
<item>@string/frequency_month</item>
</string-array>
<string-array name="entry_values_frequency">
<item>Day</item>
<item>Week</item>
<item>Month</item>
</string-array>
<string name="default_frequency">Day</string>


</resources>
12 changes: 12 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -302,5 +302,17 @@
<string name="app_is_running_extend">To perform schedule inventory app is running in background</string>
<string name="disable_notification">Disable notification</string>

<!-- message on app restrictions -->

<string name="auto_start_on_boot">Startup from system boot?</string>
<string name="automatic_inventory">Automatic inventory?</string>
<string name="server_list">Server list</string>
<string name="server_configuration">Server configuration</string>
<string name="server_url">Server URL</string>
<string name="server_tag">Server TAG</string>
<string name="server_login">Server login</string>
<string name="server_password">Server password</string>
<string name="server_custom_asset_serial">Custom asset serial</string>
<string name="server_itemtype">Asset itemtype</string>

</resources>
Loading