Skip to content

Commit e9736e9

Browse files
author
Rubin Xu
committed
Implement silent package install/uninstall functionalities
Test: manual Bug: 112982695 Change-Id: Ifb3b424b1c2fe7a3365df2ceec65cda5321d8623
1 parent 7e04f71 commit e9736e9

File tree

9 files changed

+161
-44
lines changed

9 files changed

+161
-44
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (C) 2018 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.afwsamples.testdpc.common;
18+
19+
import android.app.PendingIntent;
20+
import android.content.Context;
21+
import android.content.Intent;
22+
import android.content.IntentSender;
23+
import android.content.pm.PackageInstaller;
24+
25+
import java.io.IOException;
26+
import java.io.InputStream;
27+
import java.io.OutputStream;
28+
29+
/**
30+
* Utility class for various operations necessary to package installation.
31+
*/
32+
public class PackageInstallationUtils {
33+
34+
public static final String ACTION_INSTALL_COMPLETE
35+
= "com.afwsamples.testdpc.INSTALL_COMPLETE";
36+
private static final String ACTION_UNINSTALL_COMPLETE
37+
= "com.afwsamples.testdpc.UNINSTALL_COMPLETE";
38+
39+
public static boolean installPackage(Context context, InputStream in, String packageName)
40+
throws IOException {
41+
final PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
42+
final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
43+
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
44+
params.setAppPackageName(packageName);
45+
// set params
46+
final int sessionId = packageInstaller.createSession(params);
47+
final PackageInstaller.Session session = packageInstaller.openSession(sessionId);
48+
final OutputStream out = session.openWrite("TestDPC", 0, -1);
49+
final byte[] buffer = new byte[65536];
50+
int c;
51+
while ((c = in.read(buffer)) != -1) {
52+
out.write(buffer, 0, c);
53+
}
54+
session.fsync(out);
55+
in.close();
56+
out.close();
57+
58+
session.commit(createInstallIntentSender(context, sessionId));
59+
return true;
60+
}
61+
62+
public static void uninstallPackage(Context context, String packageName) {
63+
final PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
64+
packageInstaller.uninstall(packageName, createUninstallIntentSender(context, packageName));
65+
}
66+
67+
private static IntentSender createInstallIntentSender(Context context, int sessionId) {
68+
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, sessionId,
69+
new Intent(ACTION_INSTALL_COMPLETE), 0);
70+
return pendingIntent.getIntentSender();
71+
}
72+
73+
private static IntentSender createUninstallIntentSender(Context context, String packageName) {
74+
final Intent intent = new Intent(ACTION_UNINSTALL_COMPLETE);
75+
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
76+
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
77+
intent, 0);
78+
return pendingIntent.getIntentSender();
79+
}
80+
}
81+
82+

app/src/main/java/com/afwsamples/testdpc/common/Util.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,13 @@ public static List<UserHandle> getBindDeviceAdminTargetUsers(Context context) {
170170
return dpm.getBindDeviceAdminTargetUsers(DeviceAdminReceiver.getComponentName(context));
171171
}
172172

173-
public static void showFileViewerForImportingCertificate(PreferenceFragment fragment,
174-
int requestCode) {
173+
public static void showFileViewer(PreferenceFragment fragment, int requestCode) {
175174
Intent certIntent = new Intent(Intent.ACTION_GET_CONTENT);
176175
certIntent.setTypeAndNormalize("*/*");
177176
try {
178177
fragment.startActivityForResult(certIntent, requestCode);
179178
} catch (ActivityNotFoundException e) {
180-
Log.e(TAG, "showFileViewerForImportingCertificate: ", e);
179+
Log.e(TAG, "showFileViewer: ", e);
181180
}
182181
}
183182

app/src/main/java/com/afwsamples/testdpc/comp/BindDeviceAdminFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
8585
KEY_INSTALL_CA_CERTIFICATE);
8686
mInstallCaCertificatePreference.setOnPreferenceClickListener(
8787
preference -> {
88-
Util.showFileViewerForImportingCertificate(this,
88+
Util.showFileViewer(this,
8989
INSTALL_CA_CERTIFICATE_REQUEST_CODE);
9090
return true;
9191
}

app/src/main/java/com/afwsamples/testdpc/cosu/CosuConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import android.util.Log;
2828
import android.util.Xml;
2929

30+
import com.afwsamples.testdpc.common.PackageInstallationUtils;
31+
3032
import org.xmlpull.v1.XmlPullParser;
3133
import org.xmlpull.v1.XmlPullParserException;
3234

@@ -203,7 +205,7 @@ public Long onDownloadComplete(Long id) {
203205
try {
204206
ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(id);
205207
InputStream in = new FileInputStream(pfd.getFileDescriptor());
206-
CosuUtils.installPackage(mContext, in, ai.packageName);
208+
PackageInstallationUtils.installPackage(mContext, in, ai.packageName);
207209
} catch (IOException e) {
208210
Log.e(CosuUtils.TAG, "Error installing package: " + ai.packageName, e);
209211
// We are still marking the package as "installed", just so we don't block the

app/src/main/java/com/afwsamples/testdpc/cosu/CosuUtils.java

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@
4343

4444
private static final int DOWNLOAD_TIMEOUT_MILLIS = 120_000;
4545

46-
public static final String ACTION_INSTALL_COMPLETE
47-
= "com.afwsamples.testdpc.INSTALL_COMPLETE";
48-
4946
public static Long startDownload(DownloadManager dm, Handler handler, String location) {
5047
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(location));
5148
Long id = dm.enqueue(request);
@@ -54,38 +51,6 @@ public static Long startDownload(DownloadManager dm, Handler handler, String loc
5451
if (DEBUG) Log.d(TAG, "Starting download: DownloadId=" + id);
5552
return id;
5653
}
57-
58-
public static boolean installPackage(Context context, InputStream in, String packageName)
59-
throws IOException {
60-
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
61-
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
62-
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
63-
params.setAppPackageName(packageName);
64-
// set params
65-
int sessionId = packageInstaller.createSession(params);
66-
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
67-
OutputStream out = session.openWrite("COSU", 0, -1);
68-
byte[] buffer = new byte[65536];
69-
int c;
70-
while ((c = in.read(buffer)) != -1) {
71-
out.write(buffer, 0, c);
72-
}
73-
session.fsync(out);
74-
in.close();
75-
out.close();
76-
77-
session.commit(createIntentSender(context, sessionId));
78-
return true;
79-
}
80-
81-
private static IntentSender createIntentSender(Context context, int sessionId) {
82-
PendingIntent pendingIntent = PendingIntent.getBroadcast(
83-
context,
84-
sessionId,
85-
new Intent(ACTION_INSTALL_COMPLETE),
86-
0);
87-
return pendingIntent.getIntentSender();
88-
}
8954
}
9055

9156

app/src/main/java/com/afwsamples/testdpc/cosu/EnableCosuActivity.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import android.widget.TextView;
3737
import android.widget.Toast;
3838

39+
import com.afwsamples.testdpc.common.PackageInstallationUtils;
3940
import com.android.setupwizardlib.SetupWizardLayout;
4041
import com.android.setupwizardlib.view.NavigationBar;
4142
import com.afwsamples.testdpc.DeviceAdminReceiver;
@@ -99,7 +100,8 @@ protected void onCreate(Bundle savedInstanceState) {
99100
// register the download and install receiver
100101
registerReceiver(mDownloadReceiver, new IntentFilter(
101102
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
102-
registerReceiver(mInstallReceiver, new IntentFilter(CosuUtils.ACTION_INSTALL_COMPLETE));
103+
registerReceiver(mInstallReceiver, new IntentFilter(
104+
PackageInstallationUtils.ACTION_INSTALL_COMPLETE));
103105

104106
// download the config file
105107
String configDownloadLocation = (String) persistableBundle.get(BUNDLE_KEY_COSU_CONFIG);
@@ -267,7 +269,7 @@ public void onReceive(Context context, Intent intent) {
267269
private BroadcastReceiver mInstallReceiver = new BroadcastReceiver() {
268270
@Override
269271
public void onReceive(Context context, Intent intent) {
270-
if (!CosuUtils.ACTION_INSTALL_COMPLETE.equals(intent.getAction())) {
272+
if (!PackageInstallationUtils.ACTION_INSTALL_COMPLETE.equals(intent.getAction())) {
271273
return;
272274
}
273275

app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
import com.afwsamples.testdpc.common.preference.DpcPreferenceHelper;
9898
import com.afwsamples.testdpc.common.preference.DpcSwitchPreference;
9999
import com.afwsamples.testdpc.comp.BindDeviceAdminFragment;
100+
import com.afwsamples.testdpc.common.PackageInstallationUtils;
100101
import com.afwsamples.testdpc.policy.blockuninstallation.BlockUninstallationInfoArrayAdapter;
101102
import com.afwsamples.testdpc.policy.certificate.DelegatedCertInstallerFragment;
102103
import com.afwsamples.testdpc.policy.keyguard.LockScreenPolicyFragment;
@@ -233,6 +234,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
233234
private static final int INSTALL_CA_CERTIFICATE_REQUEST_CODE = 7690;
234235
private static final int CAPTURE_IMAGE_REQUEST_CODE = 7691;
235236
private static final int CAPTURE_VIDEO_REQUEST_CODE = 7692;
237+
private static final int INSTALL_APK_PACKAGE_REQUEST_CODE = 7693;
236238

237239
public static final String X509_CERT_TYPE = "X.509";
238240
public static final String TAG = "PolicyManagement";
@@ -268,6 +270,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
268270
= "enable_system_apps_by_package_name";
269271
private static final String ENABLE_SYSTEM_APPS_KEY = "enable_system_apps";
270272
private static final String INSTALL_EXISTING_PACKAGE_KEY = "install_existing_packages";
273+
private static final String INSTALL_APK_PACKAGE_KEY = "install_apk_package";
274+
private static final String UNINSTALL_PACKAGE_KEY = "uninstall_package";
271275
private static final String GENERATE_KEY_CERTIFICATE_KEY = "generate_key_and_certificate";
272276
private static final String GET_CA_CERTIFICATES_KEY = "get_ca_certificates";
273277
private static final String GET_DISABLE_ACCOUNT_MANAGEMENT_KEY
@@ -562,6 +566,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
562566
(DpcPreference) findPreference(INSTALL_EXISTING_PACKAGE_KEY);
563567
mInstallExistingPackagePreference.setOnPreferenceClickListener(this);
564568
mInstallExistingPackagePreference.setCustomConstraint(this::validateAffiliatedUserAfterP);
569+
findPreference(INSTALL_APK_PACKAGE_KEY).setOnPreferenceClickListener(this);
570+
findPreference(UNINSTALL_PACKAGE_KEY).setOnPreferenceClickListener(this);
565571
findPreference(HIDE_APPS_KEY).setOnPreferenceClickListener(this);
566572
findPreference(UNHIDE_APPS_KEY).setOnPreferenceClickListener(this);
567573
findPreference(SUSPEND_APPS_KEY).setOnPreferenceClickListener(this);
@@ -859,6 +865,12 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
859865
case INSTALL_EXISTING_PACKAGE_KEY:
860866
showInstallExistingPackagePrompt();
861867
return true;
868+
case INSTALL_APK_PACKAGE_KEY:
869+
Util.showFileViewer(this, INSTALL_APK_PACKAGE_REQUEST_CODE);
870+
return true;
871+
case UNINSTALL_PACKAGE_KEY:
872+
showUninstallPackagePrompt();
873+
return true;
862874
case HIDE_APPS_KEY:
863875
showHideAppsPrompt(false);
864876
return true;
@@ -896,7 +908,7 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
896908
showFragment(new ManageAppPermissionsFragment());
897909
return true;
898910
case INSTALL_KEY_CERTIFICATE_KEY:
899-
Util.showFileViewerForImportingCertificate(this,
911+
Util.showFileViewer(this,
900912
INSTALL_KEY_CERTIFICATE_REQUEST_CODE);
901913
return true;
902914
case REMOVE_KEY_CERTIFICATE_KEY:
@@ -909,7 +921,7 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
909921
testKeyCanBeUsedForSigning();
910922
return true;
911923
case INSTALL_CA_CERTIFICATE_KEY:
912-
Util.showFileViewerForImportingCertificate(this,
924+
Util.showFileViewer(this,
913925
INSTALL_CA_CERTIFICATE_REQUEST_CODE);
914926
return true;
915927
case GET_CA_CERTIFICATES_KEY:
@@ -2444,6 +2456,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
24442456
showFragment(MediaDisplayFragment.newInstance(
24452457
MediaDisplayFragment.REQUEST_DISPLAY_VIDEO, mVideoUri));
24462458
break;
2459+
case INSTALL_APK_PACKAGE_REQUEST_CODE:
2460+
installApkPackageFromIntent(data);
24472461
}
24482462
}
24492463
}
@@ -2626,6 +2640,45 @@ private void showInstallExistingPackagePrompt() {
26262640
.show();
26272641
}
26282642

2643+
@TargetApi(23)
2644+
private void installApkPackageFromIntent(Intent intent) {
2645+
if (getActivity() == null || getActivity().isFinishing()) {
2646+
return;
2647+
}
2648+
Uri data;
2649+
if (intent != null && (data = intent.getData()) != null) {
2650+
try {
2651+
InputStream inputStream = getActivity().getContentResolver().openInputStream(data);
2652+
PackageInstallationUtils.installPackage(getActivity(), inputStream, null);
2653+
} catch (IOException e) {
2654+
showToast("Failed to open APK file");
2655+
Log.e(TAG, "Failed to open APK file", e);
2656+
}
2657+
}
2658+
}
2659+
2660+
@TargetApi(23)
2661+
private void showUninstallPackagePrompt() {
2662+
final List<String> installedApps = new ArrayList<>();
2663+
for (ResolveInfo res : getAllLauncherIntentResolversSorted()) {
2664+
if (!installedApps.contains(res.activityInfo.packageName)) { // O(N^2) but not critical
2665+
installedApps.add(res.activityInfo.packageName);
2666+
}
2667+
}
2668+
AppInfoArrayAdapter appInfoArrayAdapter = new AppInfoArrayAdapter(getActivity(),
2669+
R.id.pkg_name, installedApps, true);
2670+
new AlertDialog.Builder(getActivity())
2671+
.setTitle(getString(R.string.uninstall_packages_title))
2672+
.setAdapter(appInfoArrayAdapter, new DialogInterface.OnClickListener() {
2673+
@Override
2674+
public void onClick(DialogInterface dialog, int position) {
2675+
String packageName = installedApps.get(position);
2676+
PackageInstallationUtils.uninstallPackage(getContext(), packageName);
2677+
}
2678+
})
2679+
.show();
2680+
}
2681+
26292682
/**
26302683
* Shows an alert dialog which displays a list hidden / non-hidden apps. Clicking an app in the
26312684
* dialog enables the app.

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@
346346
<string name="no_uninstalled_apps">All cached apps have been installed.</string>
347347
<string name="enable">Enable</string>
348348
<string name="invalid_system_apps_action">Invalid action</string>
349+
<string name="install_apk_package_title">Install package from APK file</string>
350+
<string name="uninstall_packages_title">Uninstall package</string>
349351
<string name="hide_apps_title">Hide apps</string>
350352
<string name="hide_apps_empty">All apps are hidden.</string>
351353
<string name="hide_apps_success">%s is now hidden.</string>

app/src/main/res/xml/device_policy_header.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@
113113
android:title="@string/install_existing_packages_title"
114114
testdpc:minSdkVersion="P"
115115
testdpc:delegation="delegation-install-existing-package" />
116+
<com.afwsamples.testdpc.common.preference.DpcPreference
117+
android:key="install_apk_package"
118+
android:title="@string/install_apk_package_title"
119+
testdpc:minSdkVersion="M"
120+
testdpc:admin="deviceOwner"
121+
testdpc:delegation="delegation-package-installation" />
122+
<com.afwsamples.testdpc.common.preference.DpcPreference
123+
android:key="uninstall_package"
124+
android:title="@string/uninstall_packages_title"
125+
testdpc:minSdkVersion="M"
126+
testdpc:admin="deviceOwner"
127+
testdpc:delegation="delegation-package-installation" />
116128
<com.afwsamples.testdpc.common.preference.DpcPreference
117129
android:key="hide_apps"
118130
android:title="@string/hide_apps_title"

0 commit comments

Comments
 (0)