Skip to content

Commit 023c843

Browse files
Rubin XuAndroid (Google) Code Review
authored andcommitted
Merge "Implement silent package install/uninstall functionalities" into ub-testdpc-qt
2 parents 8e6c07b + e9736e9 commit 023c843

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
@@ -100,6 +100,7 @@
100100
import com.afwsamples.testdpc.common.preference.DpcPreferenceHelper;
101101
import com.afwsamples.testdpc.common.preference.DpcSwitchPreference;
102102
import com.afwsamples.testdpc.comp.BindDeviceAdminFragment;
103+
import com.afwsamples.testdpc.common.PackageInstallationUtils;
103104
import com.afwsamples.testdpc.policy.blockuninstallation.BlockUninstallationInfoArrayAdapter;
104105
import com.afwsamples.testdpc.policy.certificate.DelegatedCertInstallerFragment;
105106
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
@@ -563,6 +567,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
563567
(DpcPreference) findPreference(INSTALL_EXISTING_PACKAGE_KEY);
564568
mInstallExistingPackagePreference.setOnPreferenceClickListener(this);
565569
mInstallExistingPackagePreference.setCustomConstraint(this::validateAffiliatedUserAfterP);
570+
findPreference(INSTALL_APK_PACKAGE_KEY).setOnPreferenceClickListener(this);
571+
findPreference(UNINSTALL_PACKAGE_KEY).setOnPreferenceClickListener(this);
566572
findPreference(HIDE_APPS_KEY).setOnPreferenceClickListener(this);
567573
findPreference(UNHIDE_APPS_KEY).setOnPreferenceClickListener(this);
568574
findPreference(SUSPEND_APPS_KEY).setOnPreferenceClickListener(this);
@@ -862,6 +868,12 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
862868
case INSTALL_EXISTING_PACKAGE_KEY:
863869
showInstallExistingPackagePrompt();
864870
return true;
871+
case INSTALL_APK_PACKAGE_KEY:
872+
Util.showFileViewer(this, INSTALL_APK_PACKAGE_REQUEST_CODE);
873+
return true;
874+
case UNINSTALL_PACKAGE_KEY:
875+
showUninstallPackagePrompt();
876+
return true;
865877
case HIDE_APPS_KEY:
866878
showHideAppsPrompt(false);
867879
return true;
@@ -899,7 +911,7 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
899911
showFragment(new ManageAppPermissionsFragment());
900912
return true;
901913
case INSTALL_KEY_CERTIFICATE_KEY:
902-
Util.showFileViewerForImportingCertificate(this,
914+
Util.showFileViewer(this,
903915
INSTALL_KEY_CERTIFICATE_REQUEST_CODE);
904916
return true;
905917
case REMOVE_KEY_CERTIFICATE_KEY:
@@ -912,7 +924,7 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
912924
testKeyCanBeUsedForSigning();
913925
return true;
914926
case INSTALL_CA_CERTIFICATE_KEY:
915-
Util.showFileViewerForImportingCertificate(this,
927+
Util.showFileViewer(this,
916928
INSTALL_CA_CERTIFICATE_REQUEST_CODE);
917929
return true;
918930
case GET_CA_CERTIFICATES_KEY:
@@ -2456,6 +2468,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
24562468
showFragment(MediaDisplayFragment.newInstance(
24572469
MediaDisplayFragment.REQUEST_DISPLAY_VIDEO, mVideoUri));
24582470
break;
2471+
case INSTALL_APK_PACKAGE_REQUEST_CODE:
2472+
installApkPackageFromIntent(data);
24592473
}
24602474
}
24612475
}
@@ -2638,6 +2652,45 @@ private void showInstallExistingPackagePrompt() {
26382652
.show();
26392653
}
26402654

2655+
@TargetApi(23)
2656+
private void installApkPackageFromIntent(Intent intent) {
2657+
if (getActivity() == null || getActivity().isFinishing()) {
2658+
return;
2659+
}
2660+
Uri data;
2661+
if (intent != null && (data = intent.getData()) != null) {
2662+
try {
2663+
InputStream inputStream = getActivity().getContentResolver().openInputStream(data);
2664+
PackageInstallationUtils.installPackage(getActivity(), inputStream, null);
2665+
} catch (IOException e) {
2666+
showToast("Failed to open APK file");
2667+
Log.e(TAG, "Failed to open APK file", e);
2668+
}
2669+
}
2670+
}
2671+
2672+
@TargetApi(23)
2673+
private void showUninstallPackagePrompt() {
2674+
final List<String> installedApps = new ArrayList<>();
2675+
for (ResolveInfo res : getAllLauncherIntentResolversSorted()) {
2676+
if (!installedApps.contains(res.activityInfo.packageName)) { // O(N^2) but not critical
2677+
installedApps.add(res.activityInfo.packageName);
2678+
}
2679+
}
2680+
AppInfoArrayAdapter appInfoArrayAdapter = new AppInfoArrayAdapter(getActivity(),
2681+
R.id.pkg_name, installedApps, true);
2682+
new AlertDialog.Builder(getActivity())
2683+
.setTitle(getString(R.string.uninstall_packages_title))
2684+
.setAdapter(appInfoArrayAdapter, new DialogInterface.OnClickListener() {
2685+
@Override
2686+
public void onClick(DialogInterface dialog, int position) {
2687+
String packageName = installedApps.get(position);
2688+
PackageInstallationUtils.uninstallPackage(getContext(), packageName);
2689+
}
2690+
})
2691+
.show();
2692+
}
2693+
26412694
/**
26422695
* Shows an alert dialog which displays a list hidden / non-hidden apps. Clicking an app in the
26432696
* 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
@@ -355,6 +355,8 @@
355355
<string name="no_uninstalled_apps">All cached apps have been installed.</string>
356356
<string name="enable">Enable</string>
357357
<string name="invalid_system_apps_action">Invalid action</string>
358+
<string name="install_apk_package_title">Install package from APK file</string>
359+
<string name="uninstall_packages_title">Uninstall package</string>
358360
<string name="hide_apps_title">Hide apps</string>
359361
<string name="hide_apps_empty">All apps are hidden.</string>
360362
<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
@@ -121,6 +121,18 @@
121121
android:title="@string/install_existing_packages_title"
122122
testdpc:minSdkVersion="P"
123123
testdpc:delegation="delegation-install-existing-package" />
124+
<com.afwsamples.testdpc.common.preference.DpcPreference
125+
android:key="install_apk_package"
126+
android:title="@string/install_apk_package_title"
127+
testdpc:minSdkVersion="M"
128+
testdpc:admin="deviceOwner"
129+
testdpc:delegation="delegation-package-installation" />
130+
<com.afwsamples.testdpc.common.preference.DpcPreference
131+
android:key="uninstall_package"
132+
android:title="@string/uninstall_packages_title"
133+
testdpc:minSdkVersion="M"
134+
testdpc:admin="deviceOwner"
135+
testdpc:delegation="delegation-package-installation" />
124136
<com.afwsamples.testdpc.common.preference.DpcPreference
125137
android:key="hide_apps"
126138
android:title="@string/hide_apps_title"

0 commit comments

Comments
 (0)