summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/packageinstaller/EventLogTags.logtags6
-rwxr-xr-xsrc/com/android/packageinstaller/InstallAppProgress.java261
-rw-r--r--src/com/android/packageinstaller/InstallFlowAnalytics.java608
-rw-r--r--src/com/android/packageinstaller/PackageInstallerActivity.java423
-rw-r--r--src/com/android/packageinstaller/PackageUtil.java17
-rw-r--r--src/com/android/packageinstaller/TabsAdapter.java8
-rwxr-xr-xsrc/com/android/packageinstaller/UninstallAppProgress.java143
-rwxr-xr-xsrc/com/android/packageinstaller/UninstallerActivity.java32
-rw-r--r--src/com/android/packageinstaller/permission/model/AppPermissionGroup.java177
-rw-r--r--src/com/android/packageinstaller/permission/model/AppPermissions.java40
-rw-r--r--src/com/android/packageinstaller/permission/model/Permission.java17
-rw-r--r--src/com/android/packageinstaller/permission/model/PermissionApps.java48
-rw-r--r--src/com/android/packageinstaller/permission/model/PermissionGroups.java2
-rw-r--r--src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java116
-rw-r--r--src/com/android/packageinstaller/permission/service/RuntimePermissionPresenterServiceImpl.java107
-rw-r--r--src/com/android/packageinstaller/permission/ui/ConfirmActionDialogFragment.java63
-rw-r--r--src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java71
-rw-r--r--src/com/android/packageinstaller/permission/ui/ManualLayoutFrame.java60
-rw-r--r--src/com/android/packageinstaller/permission/ui/OverlayTouchActivity.java38
-rw-r--r--src/com/android/packageinstaller/permission/ui/ReviewPermissionsActivity.java412
-rw-r--r--src/com/android/packageinstaller/permission/ui/SecureButtonView.java56
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java2
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java48
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java316
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java24
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java60
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/RestrictedSwitchPreference.java86
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java1
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java213
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java97
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java46
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java57
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java52
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java38
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/AppPermissionsFragmentWear.java29
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java21
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java2
-rw-r--r--src/com/android/packageinstaller/permission/utils/ArrayUtils.java45
-rw-r--r--src/com/android/packageinstaller/permission/utils/IoUtils.java36
-rw-r--r--src/com/android/packageinstaller/permission/utils/Utils.java3
-rw-r--r--src/com/android/packageinstaller/wear/WearPackageArgs.java14
-rw-r--r--src/com/android/packageinstaller/wear/WearPackageInstallerService.java61
-rw-r--r--src/com/android/packageinstaller/wear/WearPackageUtil.java16
43 files changed, 2270 insertions, 1702 deletions
diff --git a/src/com/android/packageinstaller/EventLogTags.logtags b/src/com/android/packageinstaller/EventLogTags.logtags
deleted file mode 100644
index 8cbb1ccd..00000000
--- a/src/com/android/packageinstaller/EventLogTags.logtags
+++ /dev/null
@@ -1,6 +0,0 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
-
-option java_package com.android.packageinstaller
-
-# APK install attempt via PackageInstaller (see InstallFlowAnalytics for format)
-90300 install_package_attempt (result_and_flags|1),(total_time|1|3),(time_till_pkg_info_obtained|1|3),(time_till_install_clicked|1|3),(package_digest|3)
diff --git a/src/com/android/packageinstaller/InstallAppProgress.java b/src/com/android/packageinstaller/InstallAppProgress.java
index d51cab1d..7554704b 100755
--- a/src/com/android/packageinstaller/InstallAppProgress.java
+++ b/src/com/android/packageinstaller/InstallAppProgress.java
@@ -16,32 +16,43 @@
*/
package com.android.packageinstaller;
+import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageInstallObserver;
-import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
-import android.content.pm.VerificationParams;
-import android.graphics.drawable.LevelListDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
+import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.packageinstaller.permission.utils.IoUtils;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.List;
/**
@@ -54,14 +65,12 @@ import java.util.List;
*/
public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener {
private final String TAG="InstallAppProgress";
- private boolean localLOGV = false;
- static final String EXTRA_MANIFEST_DIGEST =
- "com.android.packageinstaller.extras.manifest_digest";
- static final String EXTRA_INSTALL_FLOW_ANALYTICS =
- "com.android.packageinstaller.extras.install_flow_analytics";
+ private static final String BROADCAST_ACTION =
+ "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
+ private static final String BROADCAST_SENDER_PERMISSION =
+ "android.permission.INSTALL_PACKAGES";
private ApplicationInfo mAppInfo;
private Uri mPackageURI;
- private InstallFlowAnalytics mInstallFlowAnalytics;
private ProgressBar mProgressBar;
private View mOkPanel;
private TextView mStatusTextView;
@@ -72,31 +81,31 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
private Intent mLaunchIntent;
private static final int DLG_OUT_OF_SPACE = 1;
private CharSequence mLabel;
+ private HandlerThread mInstallThread;
+ private Handler mInstallHandler;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case INSTALL_COMPLETE:
- mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(msg.arg1);
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT, msg.arg1);
- setResult(msg.arg1 == PackageManager.INSTALL_SUCCEEDED
+ setResult(msg.arg1 == PackageInstaller.STATUS_SUCCESS
? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
result);
- finish();
+ clearCachedApkIfNeededAndFinish();
return;
}
// Update the status text
- mProgressBar.setVisibility(View.INVISIBLE);
+ mProgressBar.setVisibility(View.GONE);
// Show the ok button
int centerTextLabel;
int centerExplanationLabel = -1;
- LevelListDrawable centerTextDrawable =
- (LevelListDrawable) getDrawable(R.drawable.ic_result_status);
- if (msg.arg1 == PackageManager.INSTALL_SUCCEEDED) {
+ if (msg.arg1 == PackageInstaller.STATUS_SUCCESS) {
mLaunchButton.setVisibility(View.VISIBLE);
- centerTextDrawable.setLevel(0);
+ ((ImageView)findViewById(R.id.center_icon))
+ .setImageDrawable(getDrawable(R.drawable.ic_done_92));
centerTextLabel = R.string.install_done;
// Enable or disable launch button
mLaunchIntent = getPackageManager().getLaunchIntentForPackage(
@@ -114,29 +123,26 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
} else {
mLaunchButton.setEnabled(false);
}
- } else if (msg.arg1 == PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE){
+ } else if (msg.arg1 == PackageInstaller.STATUS_FAILURE_STORAGE){
showDialogInner(DLG_OUT_OF_SPACE);
return;
} else {
// Generic error handling for all other error codes.
- centerTextDrawable.setLevel(1);
+ ((ImageView)findViewById(R.id.center_icon))
+ .setImageDrawable(getDrawable(R.drawable.ic_report_problem_92));
centerExplanationLabel = getExplanationFromErrorCode(msg.arg1);
centerTextLabel = R.string.install_failed;
- mLaunchButton.setVisibility(View.INVISIBLE);
- }
- if (centerTextDrawable != null) {
- centerTextDrawable.setBounds(0, 0,
- centerTextDrawable.getIntrinsicWidth(),
- centerTextDrawable.getIntrinsicHeight());
- mStatusTextView.setCompoundDrawablesRelative(centerTextDrawable, null,
- null, null);
+ mLaunchButton.setVisibility(View.GONE);
}
- mStatusTextView.setText(centerTextLabel);
if (centerExplanationLabel != -1) {
mExplanationTextView.setText(centerExplanationLabel);
- mExplanationTextView.setVisibility(View.VISIBLE);
+ findViewById(R.id.center_view).setVisibility(View.GONE);
+ ((TextView)findViewById(R.id.explanation_status)).setText(centerTextLabel);
+ findViewById(R.id.explanation_view).setVisibility(View.VISIBLE);
} else {
- mExplanationTextView.setVisibility(View.GONE);
+ ((TextView)findViewById(R.id.center_text)).setText(centerTextLabel);
+ findViewById(R.id.center_view).setVisibility(View.VISIBLE);
+ findViewById(R.id.explanation_view).setVisibility(View.GONE);
}
mDoneButton.setOnClickListener(InstallAppProgress.this);
mOkPanel.setVisibility(View.VISIBLE);
@@ -146,18 +152,31 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
}
}
};
-
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int statusCode = intent.getIntExtra(
+ PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
+ if (statusCode == PackageInstaller.STATUS_PENDING_USER_ACTION) {
+ context.startActivity((Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT));
+ } else {
+ onPackageInstalled(statusCode);
+ }
+ }
+ };
+
private int getExplanationFromErrorCode(int errCode) {
Log.d(TAG, "Installation error code: " + errCode);
switch (errCode) {
- case PackageManager.INSTALL_FAILED_INVALID_APK:
+ case PackageInstaller.STATUS_FAILURE_BLOCKED:
+ return R.string.install_failed_blocked;
+ case PackageInstaller.STATUS_FAILURE_CONFLICT:
+ return R.string.install_failed_conflict;
+ case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
+ return R.string.install_failed_incompatible;
+ case PackageInstaller.STATUS_FAILURE_INVALID:
return R.string.install_failed_invalid_apk;
- case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:
- return R.string.install_failed_inconsistent_certificates;
- case PackageManager.INSTALL_FAILED_OLDER_SDK:
- return R.string.install_failed_older_sdk;
- case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE:
- return R.string.install_failed_cpu_abi_incompatible;
default:
return -1;
}
@@ -168,21 +187,32 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
super.onCreate(icicle);
Intent intent = getIntent();
mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
- mInstallFlowAnalytics = intent.getParcelableExtra(EXTRA_INSTALL_FLOW_ANALYTICS);
- mInstallFlowAnalytics.setContext(this);
mPackageURI = intent.getData();
final String scheme = mPackageURI.getScheme();
if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
throw new IllegalArgumentException("unexpected scheme " + scheme);
}
+ mInstallThread = new HandlerThread("InstallThread");
+ mInstallThread.start();
+ mInstallHandler = new Handler(mInstallThread.getLooper());
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(BROADCAST_ACTION);
+ registerReceiver(
+ mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null /*scheduler*/);
+
initView();
}
@Override
+ public void onBackPressed() {
+ clearCachedApkIfNeededAndFinish();
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
public Dialog onCreateDialog(int id, Bundle bundle) {
switch (id) {
case DLG_OUT_OF_SPACE:
@@ -195,13 +225,13 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
//launch manage applications
Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
startActivity(intent);
- finish();
+ clearCachedApkIfNeededAndFinish();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "Canceling installation");
- finish();
+ clearCachedApkIfNeededAndFinish();
}
})
.setOnCancelListener(this)
@@ -210,36 +240,85 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
return null;
}
+ @SuppressWarnings("deprecation")
private void showDialogInner(int id) {
removeDialog(id);
showDialog(id);
}
- class PackageInstallObserver extends IPackageInstallObserver.Stub {
- public void packageInstalled(String packageName, int returnCode) {
- Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
- msg.arg1 = returnCode;
- mHandler.sendMessage(msg);
- }
+ void onPackageInstalled(int statusCode) {
+ Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
+ msg.arg1 = statusCode;
+ mHandler.sendMessage(msg);
}
- public void initView() {
- setContentView(R.layout.op_progress);
- int installFlags = 0;
+ int getInstallFlags(String packageName) {
PackageManager pm = getPackageManager();
try {
- PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES);
- if(pi != null) {
- installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+ PackageInfo pi =
+ pm.getPackageInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
+ if (pi != null) {
+ return PackageManager.INSTALL_REPLACE_EXISTING;
}
} catch (NameNotFoundException e) {
}
- if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
- Log.w(TAG, "Replacing package:" + mAppInfo.packageName);
+ return 0;
+ }
+
+ private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
+ final PackageInstaller packageInstaller = pm.getPackageInstaller();
+ PackageInstaller.Session session = null;
+ try {
+ final String packageLocation = mPackageURI.getPath();
+ final File file = new File(packageLocation);
+ final int sessionId = packageInstaller.createSession(params);
+ final byte[] buffer = new byte[65536];
+
+ session = packageInstaller.openSession(sessionId);
+
+ final InputStream in = new FileInputStream(file);
+ final long sizeBytes = file.length();
+ final OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes);
+ try {
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ out.write(buffer, 0, c);
+ if (sizeBytes > 0) {
+ final float fraction = ((float) c / (float) sizeBytes);
+ session.addProgress(fraction);
+ }
+ }
+ session.fsync(out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+
+ // Create a PendingIntent and use it to generate the IntentSender
+ Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ InstallAppProgress.this /*context*/,
+ sessionId,
+ broadcastIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ session.commit(pendingIntent.getIntentSender());
+ } catch (IOException e) {
+ onPackageInstalled(PackageInstaller.STATUS_FAILURE);
+ } finally {
+ IoUtils.closeQuietly(session);
}
+ }
+
+ void initView() {
+ setContentView(R.layout.op_progress);
final PackageUtil.AppSnippet as;
+ final PackageManager pm = getPackageManager();
+ final int installFlags = getInstallFlags(mAppInfo.packageName);
+
+ if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
+ Log.w(TAG, "Replacing package:" + mAppInfo.packageName);
+ }
if ("package".equals(mPackageURI.getScheme())) {
as = new PackageUtil.AppSnippet(pm.getApplicationLabel(mAppInfo),
pm.getApplicationIcon(mAppInfo));
@@ -250,45 +329,44 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
mLabel = as.label;
PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
mStatusTextView = (TextView)findViewById(R.id.center_text);
- mStatusTextView.setText(R.string.installing);
- mExplanationTextView = (TextView) findViewById(R.id.center_explanation);
+ mExplanationTextView = (TextView) findViewById(R.id.explanation);
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
mProgressBar.setIndeterminate(true);
// Hide button till progress is being displayed
- mOkPanel = (View)findViewById(R.id.buttons_panel);
+ mOkPanel = findViewById(R.id.buttons_panel);
mDoneButton = (Button)findViewById(R.id.done_button);
mLaunchButton = (Button)findViewById(R.id.launch_button);
mOkPanel.setVisibility(View.INVISIBLE);
- String installerPackageName = getIntent().getStringExtra(
- Intent.EXTRA_INSTALLER_PACKAGE_NAME);
- Uri originatingURI = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
- Uri referrer = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
- int originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- VerificationParams.NO_UID);
- ManifestDigest manifestDigest = getIntent().getParcelableExtra(EXTRA_MANIFEST_DIGEST);
- VerificationParams verificationParams = new VerificationParams(null, originatingURI,
- referrer, originatingUid, manifestDigest);
- PackageInstallObserver observer = new PackageInstallObserver();
-
if ("package".equals(mPackageURI.getScheme())) {
try {
pm.installExistingPackage(mAppInfo.packageName);
- observer.packageInstalled(mAppInfo.packageName,
- PackageManager.INSTALL_SUCCEEDED);
+ onPackageInstalled(PackageInstaller.STATUS_SUCCESS);
} catch (PackageManager.NameNotFoundException e) {
- observer.packageInstalled(mAppInfo.packageName,
- PackageManager.INSTALL_FAILED_INVALID_APK);
+ onPackageInstalled(PackageInstaller.STATUS_FAILURE_INVALID);
}
} else {
- pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
- installerPackageName, verificationParams, null);
+ final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
+ params.originatingUri = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
+ params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
+ UID_UNKNOWN);
+
+ mInstallHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ doPackageStage(pm, params);
+ }
+ });
}
}
@Override
protected void onDestroy() {
super.onDestroy();
+ unregisterReceiver(mBroadcastReceiver);
+ mInstallThread.getLooper().quitSafely();
}
public void onClick(View v) {
@@ -296,14 +374,31 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
if (mAppInfo.packageName != null) {
Log.i(TAG, "Finished installing "+mAppInfo.packageName);
}
- finish();
+ clearCachedApkIfNeededAndFinish();
} else if(v == mLaunchButton) {
- startActivity(mLaunchIntent);
- finish();
+ try {
+ startActivity(mLaunchIntent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Could not start activity", e);
+ }
+ clearCachedApkIfNeededAndFinish();
}
}
public void onCancel(DialogInterface dialog) {
+ clearCachedApkIfNeededAndFinish();
+ }
+
+ private void clearCachedApkIfNeededAndFinish() {
+ // If we are installing from a content:// the apk is copied in the cache
+ // dir and passed in here. As we aren't started for a result because our
+ // caller needs to be able to forward the result, here we make sure the
+ // staging file in the cache dir is removed.
+ if ("file".equals(mPackageURI.getScheme()) && mPackageURI.getPath() != null
+ && mPackageURI.getPath().startsWith(getCacheDir().toString())) {
+ File file = new File(mPackageURI.getPath());
+ file.delete();
+ }
finish();
}
}
diff --git a/src/com/android/packageinstaller/InstallFlowAnalytics.java b/src/com/android/packageinstaller/InstallFlowAnalytics.java
deleted file mode 100644
index 4591f31c..00000000
--- a/src/com/android/packageinstaller/InstallFlowAnalytics.java
+++ /dev/null
@@ -1,608 +0,0 @@
-/*
-**
-** Copyright 2013, The Android Open Source Project
-**
-** 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
-**
-** http://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.
-*/
-package com.android.packageinstaller;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.EventLog;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-import libcore.io.IoUtils;
-
-/**
- * Analytics about an attempt to install a package via {@link PackageInstallerActivity}.
- *
- * <p>An instance of this class is created at the beginning of the install flow and gradually filled
- * as the user progresses through the flow. When the flow terminates (regardless of the reason),
- * {@link #setFlowFinished(byte)} is invoked which reports the installation attempt as an event
- * to the Event Log.
- */
-public class InstallFlowAnalytics implements Parcelable {
-
- private static final String TAG = "InstallFlowAnalytics";
-
- /** Installation has not yet terminated. */
- static final byte RESULT_NOT_YET_AVAILABLE = -1;
-
- /** Package successfully installed. */
- static final byte RESULT_SUCCESS = 0;
-
- /** Installation failed because scheme unsupported. */
- static final byte RESULT_FAILED_UNSUPPORTED_SCHEME = 1;
-
- /**
- * Installation of an APK failed because of a failure to obtain information from the provided
- * APK.
- */
- static final byte RESULT_FAILED_TO_GET_PACKAGE_INFO = 2;
-
- /**
- * Installation of an already installed package into the current user profile failed because the
- * specified package is not installed.
- */
- static final byte RESULT_FAILED_PACKAGE_MISSING = 3;
-
- /**
- * Installation failed because installation from unknown sources is prohibited by the Unknown
- * Sources setting.
- */
- static final byte RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING = 4;
-
- /** Installation cancelled by the user. */
- static final byte RESULT_CANCELLED_BY_USER = 5;
-
- /**
- * Installation failed due to {@code PackageManager} failure. PackageManager error code is
- * provided in {@link #mPackageManagerInstallResult}).
- */
- static final byte RESULT_PACKAGE_MANAGER_INSTALL_FAILED = 6;
-
- /**
- * Installation blocked since this feature is not allowed on Android Wear devices yet.
- */
- static final byte RESULT_NOT_ALLOWED_ON_WEAR = 7;
-
- private static final int FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED = 1 << 0;
- private static final int FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE = 1 << 1;
- private static final int FLAG_VERIFY_APPS_ENABLED = 1 << 2;
- private static final int FLAG_APP_VERIFIER_INSTALLED = 1 << 3;
- private static final int FLAG_FILE_URI = 1 << 4;
- private static final int FLAG_REPLACE = 1 << 5;
- private static final int FLAG_SYSTEM_APP = 1 << 6;
- private static final int FLAG_PACKAGE_INFO_OBTAINED = 1 << 7;
- private static final int FLAG_INSTALL_BUTTON_CLICKED = 1 << 8;
- private static final int FLAG_NEW_PERMISSIONS_FOUND = 1 << 9;
- private static final int FLAG_PERMISSIONS_DISPLAYED = 1 << 10;
- private static final int FLAG_NEW_PERMISSIONS_DISPLAYED = 1 << 11;
- private static final int FLAG_ALL_PERMISSIONS_DISPLAYED = 1 << 12;
-
- /**
- * Information about this flow expressed as a collection of flags. See {@code FLAG_...}
- * constants.
- */
- private int mFlags;
-
- /** Outcome of the flow. See {@code RESULT_...} constants. */
- private byte mResult = RESULT_NOT_YET_AVAILABLE;
-
- /**
- * Result code returned by {@code PackageManager} to install the package or {@code 0} if
- * {@code PackageManager} has not yet been invoked to install the package.
- */
- private int mPackageManagerInstallResult;
-
- /**
- * Time instant when the installation request arrived, measured in elapsed realtime
- * milliseconds. See {@link SystemClock#elapsedRealtime()}.
- */
- private long mStartTimestampMillis;
-
- /**
- * Time instant when the information about the package being installed was obtained, measured in
- * elapsed realtime milliseconds. See {@link SystemClock#elapsedRealtime()}.
- */
- private long mPackageInfoObtainedTimestampMillis;
-
- /**
- * Time instant when the user clicked the Install button, measured in elapsed realtime
- * milliseconds. See {@link SystemClock#elapsedRealtime()}. This field is only valid if the
- * Install button has been clicked, as signaled by {@link #FLAG_INSTALL_BUTTON_CLICKED}.
- */
- private long mInstallButtonClickTimestampMillis;
-
- /**
- * Time instant when this flow terminated, measured in elapsed realtime milliseconds. See
- * {@link SystemClock#elapsedRealtime()}.
- */
- private long mEndTimestampMillis;
-
- /** URI of the package being installed. */
- private String mPackageUri;
-
- /** Whether this attempt has been logged to the Event Log. */
- private boolean mLogged;
-
- private Context mContext;
-
- public static final Parcelable.Creator<InstallFlowAnalytics> CREATOR =
- new Parcelable.Creator<InstallFlowAnalytics>() {
- @Override
- public InstallFlowAnalytics createFromParcel(Parcel in) {
- return new InstallFlowAnalytics(in);
- }
-
- @Override
- public InstallFlowAnalytics[] newArray(int size) {
- return new InstallFlowAnalytics[size];
- }
- };
-
- public InstallFlowAnalytics() {}
-
- public InstallFlowAnalytics(Parcel in) {
- mFlags = in.readInt();
- mResult = in.readByte();
- mPackageManagerInstallResult = in.readInt();
- mStartTimestampMillis = in.readLong();
- mPackageInfoObtainedTimestampMillis = in.readLong();
- mInstallButtonClickTimestampMillis = in.readLong();
- mEndTimestampMillis = in.readLong();
- mPackageUri = in.readString();
- mLogged = readBoolean(in);
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mFlags);
- dest.writeByte(mResult);
- dest.writeInt(mPackageManagerInstallResult);
- dest.writeLong(mStartTimestampMillis);
- dest.writeLong(mPackageInfoObtainedTimestampMillis);
- dest.writeLong(mInstallButtonClickTimestampMillis);
- dest.writeLong(mEndTimestampMillis);
- dest.writeString(mPackageUri);
- writeBoolean(dest, mLogged);
- }
-
- private static void writeBoolean(Parcel dest, boolean value) {
- dest.writeByte((byte) (value ? 1 : 0));
- }
-
- private static boolean readBoolean(Parcel dest) {
- return dest.readByte() != 0;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- void setContext(Context context) {
- mContext = context;
- }
-
- /** Sets whether the Unknown Sources setting is checked. */
- void setInstallsFromUnknownSourcesPermitted(boolean permitted) {
- setFlagState(FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED, permitted);
- }
-
- /** Gets whether the Unknown Sources setting is checked. */
- private boolean isInstallsFromUnknownSourcesPermitted() {
- return isFlagSet(FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED);
- }
-
- /** Sets whether this install attempt is from an unknown source. */
- void setInstallRequestFromUnknownSource(boolean unknownSource) {
- setFlagState(FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE, unknownSource);
- }
-
- /** Gets whether this install attempt is from an unknown source. */
- private boolean isInstallRequestFromUnknownSource() {
- return isFlagSet(FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE);
- }
-
- /** Sets whether app verification is enabled. */
- void setVerifyAppsEnabled(boolean enabled) {
- setFlagState(FLAG_VERIFY_APPS_ENABLED, enabled);
- }
-
- /** Gets whether app verification is enabled. */
- private boolean isVerifyAppsEnabled() {
- return isFlagSet(FLAG_VERIFY_APPS_ENABLED);
- }
-
- /** Sets whether at least one app verifier is installed. */
- void setAppVerifierInstalled(boolean installed) {
- setFlagState(FLAG_APP_VERIFIER_INSTALLED, installed);
- }
-
- /** Gets whether at least one app verifier is installed. */
- private boolean isAppVerifierInstalled() {
- return isFlagSet(FLAG_APP_VERIFIER_INSTALLED);
- }
-
- /**
- * Sets whether an APK file is being installed.
- *
- * @param fileUri {@code true} if an APK file is being installed, {@code false} if an already
- * installed package is being installed to this user profile.
- */
- void setFileUri(boolean fileUri) {
- setFlagState(FLAG_FILE_URI, fileUri);
- }
-
- /**
- * Sets the URI of the package being installed.
- */
- void setPackageUri(String packageUri) {
- mPackageUri = packageUri;
- }
-
- /**
- * Gets whether an APK file is being installed.
- *
- * @return {@code true} if an APK file is being installed, {@code false} if an already
- * installed package is being installed to this user profile.
- */
- private boolean isFileUri() {
- return isFlagSet(FLAG_FILE_URI);
- }
-
- /** Sets whether this is an attempt to replace an existing package. */
- void setReplace(boolean replace) {
- setFlagState(FLAG_REPLACE, replace);
- }
-
- /** Gets whether this is an attempt to replace an existing package. */
- private boolean isReplace() {
- return isFlagSet(FLAG_REPLACE);
- }
-
- /** Sets whether the package being updated is a system package. */
- void setSystemApp(boolean systemApp) {
- setFlagState(FLAG_SYSTEM_APP, systemApp);
- }
-
- /** Gets whether the package being updated is a system package. */
- private boolean isSystemApp() {
- return isFlagSet(FLAG_SYSTEM_APP);
- }
-
- /**
- * Sets whether the package being installed is requesting more permissions than the already
- * installed version of the package.
- */
- void setNewPermissionsFound(boolean found) {
- setFlagState(FLAG_NEW_PERMISSIONS_FOUND, found);
- }
-
- /**
- * Gets whether the package being installed is requesting more permissions than the already
- * installed version of the package.
- */
- private boolean isNewPermissionsFound() {
- return isFlagSet(FLAG_NEW_PERMISSIONS_FOUND);
- }
-
- /** Sets whether permissions were displayed to the user. */
- void setPermissionsDisplayed(boolean displayed) {
- setFlagState(FLAG_PERMISSIONS_DISPLAYED, displayed);
- }
-
- /** Gets whether permissions were displayed to the user. */
- private boolean isPermissionsDisplayed() {
- return isFlagSet(FLAG_PERMISSIONS_DISPLAYED);
- }
-
- /**
- * Sets whether new permissions were displayed to the user (if permissions were displayed at
- * all).
- */
- void setNewPermissionsDisplayed(boolean displayed) {
- setFlagState(FLAG_NEW_PERMISSIONS_DISPLAYED, displayed);
- }
-
- /**
- * Gets whether new permissions were displayed to the user (if permissions were displayed at
- * all).
- */
- private boolean isNewPermissionsDisplayed() {
- return isFlagSet(FLAG_NEW_PERMISSIONS_DISPLAYED);
- }
-
- /**
- * Sets whether all permissions were displayed to the user (if permissions were displayed at
- * all).
- */
- void setAllPermissionsDisplayed(boolean displayed) {
- setFlagState(FLAG_ALL_PERMISSIONS_DISPLAYED, displayed);
- }
-
- /**
- * Gets whether all permissions were displayed to the user (if permissions were displayed at
- * all).
- */
- private boolean isAllPermissionsDisplayed() {
- return isFlagSet(FLAG_ALL_PERMISSIONS_DISPLAYED);
- }
-
- /**
- * Sets the time instant when the installation request arrived, measured in elapsed realtime
- * milliseconds. See {@link SystemClock#elapsedRealtime()}.
- */
- void setStartTimestampMillis(long timestampMillis) {
- mStartTimestampMillis = timestampMillis;
- }
-
- /**
- * Records that the information about the package info has been obtained or that there has been
- * a failure to obtain the information.
- */
- void setPackageInfoObtained() {
- setFlagState(FLAG_PACKAGE_INFO_OBTAINED, true);
- mPackageInfoObtainedTimestampMillis = SystemClock.elapsedRealtime();
- }
-
- /**
- * Checks whether the information about the package info has been obtained or that there has
- * been a failure to obtain the information.
- */
- private boolean isPackageInfoObtained() {
- return isFlagSet(FLAG_PACKAGE_INFO_OBTAINED);
- }
-
- /**
- * Records that the Install button has been clicked.
- */
- void setInstallButtonClicked() {
- setFlagState(FLAG_INSTALL_BUTTON_CLICKED, true);
- mInstallButtonClickTimestampMillis = SystemClock.elapsedRealtime();
- }
-
- /**
- * Checks whether the Install button has been clicked.
- */
- private boolean isInstallButtonClicked() {
- return isFlagSet(FLAG_INSTALL_BUTTON_CLICKED);
- }
-
- /**
- * Marks this flow as finished due to {@code PackageManager} succeeding or failing to install
- * the package and reports this to the Event Log.
- */
- void setFlowFinishedWithPackageManagerResult(int packageManagerResult) {
- mPackageManagerInstallResult = packageManagerResult;
- if (packageManagerResult == PackageManager.INSTALL_SUCCEEDED) {
- setFlowFinished(
- InstallFlowAnalytics.RESULT_SUCCESS);
- } else {
- setFlowFinished(
- InstallFlowAnalytics.RESULT_PACKAGE_MANAGER_INSTALL_FAILED);
- }
- }
-
- /**
- * Marks this flow as finished and reports this to the Event Log.
- */
- void setFlowFinished(byte result) {
- if (mLogged) {
- return;
- }
- mResult = result;
- mEndTimestampMillis = SystemClock.elapsedRealtime();
- writeToEventLog();
- }
-
- private void writeToEventLog() {
- byte packageManagerInstallResultByte = 0;
- if (mResult == RESULT_PACKAGE_MANAGER_INSTALL_FAILED) {
- // PackageManager install error codes are negative, starting from -1 and going to
- // -111 (at the moment). We thus store them in negated form.
- packageManagerInstallResultByte = clipUnsignedValueToUnsignedByte(
- -mPackageManagerInstallResult);
- }
-
- final int resultAndFlags = (mResult & 0xff)
- | ((packageManagerInstallResultByte & 0xff) << 8)
- | ((mFlags & 0xffff) << 16);
-
- // Total elapsed time from start to end, in milliseconds.
- final int totalElapsedTime =
- clipUnsignedLongToUnsignedInt(mEndTimestampMillis - mStartTimestampMillis);
-
- // Total elapsed time from start till information about the package being installed was
- // obtained, in milliseconds.
- final int elapsedTimeTillPackageInfoObtained = (isPackageInfoObtained())
- ? clipUnsignedLongToUnsignedInt(
- mPackageInfoObtainedTimestampMillis - mStartTimestampMillis)
- : 0;
-
- // Total elapsed time from start till Install button clicked, in milliseconds
- // milliseconds.
- final int elapsedTimeTillInstallButtonClick = (isInstallButtonClicked())
- ? clipUnsignedLongToUnsignedInt(
- mInstallButtonClickTimestampMillis - mStartTimestampMillis)
- : 0;
-
- // If this user has consented to app verification, augment the logged event with the hash of
- // the contents of the APK.
- if (((mFlags & FLAG_FILE_URI) != 0)
- && ((mFlags & FLAG_VERIFY_APPS_ENABLED) != 0)
- && (isUserConsentToVerifyAppsGranted())) {
- // Log the hash of the APK's contents.
- // Reading the APK may take a while -- perform in background.
- AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
- @Override
- public void run() {
- byte[] digest = null;
- try {
- digest = getPackageContentsDigest();
- } catch (IOException e) {
- Log.w(TAG, "Failed to hash APK contents", e);
- } finally {
- String digestHex = (digest != null)
- ? IntegralToString.bytesToHexString(digest, false)
- : "";
- EventLogTags.writeInstallPackageAttempt(
- resultAndFlags,
- totalElapsedTime,
- elapsedTimeTillPackageInfoObtained,
- elapsedTimeTillInstallButtonClick,
- digestHex);
- }
- }
- });
- } else {
- // Do not log the hash of the APK's contents
- EventLogTags.writeInstallPackageAttempt(
- resultAndFlags,
- totalElapsedTime,
- elapsedTimeTillPackageInfoObtained,
- elapsedTimeTillInstallButtonClick,
- "");
- }
- mLogged = true;
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Analytics:"
- + "\n\tinstallsFromUnknownSourcesPermitted: "
- + isInstallsFromUnknownSourcesPermitted()
- + "\n\tinstallRequestFromUnknownSource: " + isInstallRequestFromUnknownSource()
- + "\n\tverifyAppsEnabled: " + isVerifyAppsEnabled()
- + "\n\tappVerifierInstalled: " + isAppVerifierInstalled()
- + "\n\tfileUri: " + isFileUri()
- + "\n\treplace: " + isReplace()
- + "\n\tsystemApp: " + isSystemApp()
- + "\n\tpackageInfoObtained: " + isPackageInfoObtained()
- + "\n\tinstallButtonClicked: " + isInstallButtonClicked()
- + "\n\tpermissionsDisplayed: " + isPermissionsDisplayed()
- + "\n\tnewPermissionsDisplayed: " + isNewPermissionsDisplayed()
- + "\n\tallPermissionsDisplayed: " + isAllPermissionsDisplayed()
- + "\n\tnewPermissionsFound: " + isNewPermissionsFound()
- + "\n\tresult: " + mResult
- + "\n\tpackageManagerInstallResult: " + mPackageManagerInstallResult
- + "\n\ttotalDuration: " + (mEndTimestampMillis - mStartTimestampMillis) + " ms"
- + "\n\ttimeTillPackageInfoObtained: "
- + ((isPackageInfoObtained())
- ? ((mPackageInfoObtainedTimestampMillis - mStartTimestampMillis)
- + " ms")
- : "n/a")
- + "\n\ttimeTillInstallButtonClick: "
- + ((isInstallButtonClicked())
- ? ((mInstallButtonClickTimestampMillis - mStartTimestampMillis) + " ms")
- : "n/a"));
- Log.v(TAG, "Wrote to Event Log: 0x" + Long.toString(resultAndFlags & 0xffffffffL, 16)
- + ", " + totalElapsedTime
- + ", " + elapsedTimeTillPackageInfoObtained
- + ", " + elapsedTimeTillInstallButtonClick);
- }
- }
-
- private static final byte clipUnsignedValueToUnsignedByte(long value) {
- if (value < 0) {
- return 0;
- } else if (value > 0xff) {
- return (byte) 0xff;
- } else {
- return (byte) value;
- }
- }
-
- private static final int clipUnsignedLongToUnsignedInt(long value) {
- if (value < 0) {
- return 0;
- } else if (value > 0xffffffffL) {
- return 0xffffffff;
- } else {
- return (int) value;
- }
- }
-
- /**
- * Sets or clears the specified flag in the {@link #mFlags} field.
- */
- private void setFlagState(int flag, boolean set) {
- if (set) {
- mFlags |= flag;
- } else {
- mFlags &= ~flag;
- }
- }
-
- /**
- * Checks whether the specified flag is set in the {@link #mFlags} field.
- */
- private boolean isFlagSet(int flag) {
- return (mFlags & flag) == flag;
- }
-
- /**
- * Checks whether the user has consented to app verification.
- */
- private boolean isUserConsentToVerifyAppsGranted() {
- return Settings.Secure.getInt(
- mContext.getContentResolver(),
- Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT, 0) != 0;
- }
-
- /**
- * Gets the digest of the contents of the package being installed.
- */
- private byte[] getPackageContentsDigest() throws IOException {
- File file = new File(Uri.parse(mPackageUri).getPath());
- return getSha256ContentsDigest(file);
- }
-
- /**
- * Gets the SHA-256 digest of the contents of the specified file.
- */
- private static byte[] getSha256ContentsDigest(File file) throws IOException {
- MessageDigest digest;
- try {
- digest = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("SHA-256 not available", e);
- }
-
- byte[] buf = new byte[8192];
- InputStream in = null;
- try {
- in = new BufferedInputStream(new FileInputStream(file), buf.length);
- int chunkSize;
- while ((chunkSize = in.read(buf)) != -1) {
- digest.update(buf, 0, chunkSize);
- }
- } finally {
- IoUtils.closeQuietly(in);
- }
- return digest.digest();
- }
-}
diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java
index 868872a9..1903f917 100644
--- a/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -24,21 +24,19 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
-import android.content.pm.ResolveInfo;
import android.content.pm.VerificationParams;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.os.SystemClock;
import android.os.UserManager;
import android.provider.Settings;
import android.support.v4.view.ViewPager;
@@ -49,11 +47,16 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AppSecurityPermissions;
import android.widget.Button;
+import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;
+import com.android.packageinstaller.permission.utils.Utils;
import java.io.File;
-import java.util.List;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
/*
* This activity is launched when a new application is installed via side loading
@@ -68,12 +71,20 @@ import java.util.List;
public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener {
private static final String TAG = "PackageInstaller";
+ private static final int REQUEST_ENABLE_UNKNOWN_SOURCES = 1;
+
+ private static final String SCHEME_FILE = "file";
+ private static final String SCHEME_CONTENT = "content";
+ private static final String SCHEME_PACKAGE = "package";
+
private int mSessionId = -1;
private Uri mPackageURI;
private Uri mOriginatingURI;
private Uri mReferrerURI;
private int mOriginatingUid = VerificationParams.NO_UID;
- private ManifestDigest mPkgDigest;
+ private File mContentUriApkStagingFile;
+
+ private AsyncTask<Uri, Void, File> mStagingAsynTask;
private boolean localLOGV = false;
PackageManager mPm;
@@ -85,8 +96,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
// ApplicationInfo object primarily used for already existing applications
private ApplicationInfo mAppInfo = null;
- private InstallFlowAnalytics mInstallFlowAnalytics;
-
// View for install progress
View mInstallConfirm;
// Buttons to indicate user acceptance
@@ -97,8 +106,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
static final String PREFS_ALLOWED_SOURCES = "allowed_sources";
- private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
-
private static final String TAB_ID_ALL = "all";
private static final String TAB_ID_NEW = "new";
@@ -108,25 +115,15 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
- private static final int DLG_ALLOW_SOURCE = DLG_BASE + 5;
private static final int DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES = DLG_BASE + 6;
private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
private void startInstallConfirm() {
TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
tabHost.setup();
+ tabHost.setVisibility(View.VISIBLE);
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
- adapter.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
- @Override
- public void onTabChanged(String tabId) {
- if (TAB_ID_ALL.equals(tabId)) {
- mInstallFlowAnalytics.setAllPermissionsDisplayed(true);
- } else if (TAB_ID_NEW.equals(tabId)) {
- mInstallFlowAnalytics.setNewPermissionsDisplayed(true);
- }
- }
- });
// If the app supports runtime permissions the new permissions will
// be requested at runtime, hence we do not show them at install.
boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
@@ -148,7 +145,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
if (!supportsRuntimePermissions) {
newPermissionsFound =
(perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
- mInstallFlowAnalytics.setNewPermissionsFound(newPermissionsFound);
if (newPermissionsFound) {
permVisible = true;
mScrollView.addView(perms.getPermissionsView(
@@ -166,7 +162,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
getText(R.string.newPerms)), mScrollView);
} else {
findViewById(R.id.tabscontainer).setVisibility(View.GONE);
- findViewById(R.id.divider).setVisibility(View.VISIBLE);
+ findViewById(R.id.spacer).setVisibility(View.VISIBLE);
}
if (!supportsRuntimePermissions && N > 0) {
permVisible = true;
@@ -181,7 +177,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
getText(R.string.allPerms)), root);
}
- mInstallFlowAnalytics.setPermissionsDisplayed(permVisible);
if (!permVisible) {
if (mAppInfo != null) {
// This is an update to an application, but there are no
@@ -189,25 +184,20 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system_no_perms
: R.string.install_confirm_question_update_no_perms;
+
+ findViewById(R.id.spacer).setVisibility(View.VISIBLE);
} else {
// This is a new application with no permissions.
msg = R.string.install_confirm_question_no_perms;
}
- tabHost.setVisibility(View.GONE);
- mInstallFlowAnalytics.setAllPermissionsDisplayed(false);
- mInstallFlowAnalytics.setNewPermissionsDisplayed(false);
- findViewById(R.id.filler).setVisibility(View.VISIBLE);
- findViewById(R.id.divider).setVisibility(View.GONE);
+ tabHost.setVisibility(View.INVISIBLE);
mScrollView = null;
}
if (msg != 0) {
((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
}
mInstallConfirm.setVisibility(View.VISIBLE);
- mOk = (Button)findViewById(R.id.ok_button);
- mCancel = (Button)findViewById(R.id.cancel_button);
- mOk.setOnClickListener(this);
- mCancel.setOnClickListener(this);
+ mOk.setEnabled(true);
if (mScrollView == null) {
// There is nothing to scroll view, so the ok button is immediately
// set to install.
@@ -235,24 +225,22 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
switch (id) {
case DLG_UNKNOWN_SOURCES:
return new AlertDialog.Builder(this)
- .setTitle(R.string.unknown_apps_dlg_title)
.setMessage(R.string.unknown_apps_dlg_text)
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "Finishing off activity so that user can navigate to settings manually");
- finish();
+ finishAffinity();
}})
.setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "Launching settings");
- launchSettingsAppAndFinish();
+ launchSecuritySettings();
}
})
.setOnCancelListener(this)
.create();
case DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES:
return new AlertDialog.Builder(this)
- .setTitle(R.string.unknown_apps_dlg_title)
.setMessage(R.string.unknown_apps_admin_dlg_text)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@@ -263,7 +251,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
.create();
case DLG_PACKAGE_ERROR :
return new AlertDialog.Builder(this)
- .setTitle(R.string.Parse_error_dlg_title)
.setMessage(R.string.Parse_error_dlg_text)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@@ -278,7 +265,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
String dlgText = getString(R.string.out_of_space_dlg_text,
appTitle.toString());
return new AlertDialog.Builder(this)
- .setTitle(R.string.out_of_space_dlg_title)
.setMessage(dlgText)
.setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@@ -303,7 +289,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
String dlgText1 = getString(R.string.install_failed_msg,
appTitle1.toString());
return new AlertDialog.Builder(this)
- .setTitle(R.string.install_failed)
.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
@@ -312,50 +297,36 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
.setMessage(dlgText1)
.setOnCancelListener(this)
.create();
- case DLG_ALLOW_SOURCE:
- CharSequence appTitle2 = mPm.getApplicationLabel(mSourceInfo);
- String dlgText2 = getString(R.string.allow_source_dlg_text,
- appTitle2.toString());
- return new AlertDialog.Builder(this)
- .setTitle(R.string.allow_source_dlg_title)
- .setMessage(dlgText2)
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- setResult(RESULT_CANCELED);
- finish();
- }})
- .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES,
- Context.MODE_PRIVATE);
- prefs.edit().putBoolean(mSourceInfo.packageName, true).apply();
- startInstallConfirm();
- }
- })
- .setOnCancelListener(this)
- .create();
case DLG_NOT_SUPPORTED_ON_WEAR:
return new AlertDialog.Builder(this)
- .setTitle(R.string.wear_not_allowed_dlg_title)
.setMessage(R.string.wear_not_allowed_dlg_text)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setResult(RESULT_OK);
- finish();
+ clearCachedApkIfNeededAndFinish();
}
})
.setOnCancelListener(this)
.create();
}
return null;
- }
+ }
- private void launchSettingsAppAndFinish() {
- // Create an intent to launch SettingsTwo activity
+ private void launchSecuritySettings() {
Intent launchSettingsIntent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
- launchSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(launchSettingsIntent);
- finish();
+ startActivityForResult(launchSettingsIntent, REQUEST_ENABLE_UNKNOWN_SOURCES);
+ }
+
+ @Override
+ public void onActivityResult(int request, int result, Intent data) {
+ // If the settings app approved the install we are good to go regardless
+ // whether the untrusted sources setting is on. This allows partners to
+ // implement a "allow untrusted source once" feature.
+ if (request == REQUEST_ENABLE_UNKNOWN_SOURCES && result == RESULT_OK) {
+ initiateInstall();
+ } else {
+ clearCachedApkIfNeededAndFinish();
+ }
}
private boolean isInstallRequestFromUnknownSource(Intent intent) {
@@ -378,23 +349,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
return true;
}
- private boolean isVerifyAppsEnabled() {
- if (mUserManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS)) {
- return true;
- }
- return Settings.Global.getInt(getContentResolver(),
- Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) > 0;
- }
-
- private boolean isAppVerifierInstalled() {
- final PackageManager pm = getPackageManager();
- final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
- verification.setType(PACKAGE_MIME_TYPE);
- verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(verification, 0);
- return (receivers.size() > 0) ? true : false;
- }
-
/**
* @return whether unknown sources is enabled by user in Settings
*/
@@ -434,10 +388,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
mAppInfo = null;
}
- mInstallFlowAnalytics.setReplace(mAppInfo != null);
- mInstallFlowAnalytics.setSystemApp(
- (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));
-
startInstallConfirm();
}
@@ -457,6 +407,10 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
+ mOriginatingUid = getOriginatingUid(intent);
+
+ final Uri packageUri;
+
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
@@ -467,120 +421,130 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
}
mSessionId = sessionId;
- mPackageURI = Uri.fromFile(new File(info.resolvedBaseCodePath));
+ packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
- mPackageURI = intent.getData();
+ packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
- final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin();
- final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled();
-
- boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
- mInstallFlowAnalytics = new InstallFlowAnalytics();
- mInstallFlowAnalytics.setContext(this);
- mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime());
- mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(unknownSourcesAllowedByAdmin
- && unknownSourcesAllowedByUser);
- mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource);
- mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled());
- mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled());
- mInstallFlowAnalytics.setPackageUri(mPackageURI.toString());
-
- if (DeviceUtils.isWear(this)) {
- showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_NOT_ALLOWED_ON_WEAR);
- return;
- }
-
- final String scheme = mPackageURI.getScheme();
- if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
- Log.w(TAG, "Unsupported scheme " + scheme);
+ // if there's nothing to do, quietly slip into the ether
+ if (packageUri == null) {
+ Log.w(TAG, "Unspecified source");
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
finish();
return;
}
- final PackageUtil.AppSnippet as;
- if ("package".equals(mPackageURI.getScheme())) {
- mInstallFlowAnalytics.setFileUri(false);
- try {
- mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
- PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
- } catch (NameNotFoundException e) {
- }
- if (mPkgInfo == null) {
- Log.w(TAG, "Requested package " + mPackageURI.getScheme()
- + " not available. Discontinuing installation");
- showDialogInner(DLG_PACKAGE_ERROR);
- setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
- mInstallFlowAnalytics.setPackageInfoObtained();
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
- return;
- }
- as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
- mPm.getApplicationIcon(mPkgInfo.applicationInfo));
- } else {
- mInstallFlowAnalytics.setFileUri(true);
- final File sourceFile = new File(mPackageURI.getPath());
- PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
-
- // Check for parse errors
- if (parsed == null) {
- Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
- showDialogInner(DLG_PACKAGE_ERROR);
- setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
- mInstallFlowAnalytics.setPackageInfoObtained();
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
- return;
- }
- mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
- PackageManager.GET_PERMISSIONS, 0, 0, null,
- new PackageUserState());
- mPkgDigest = parsed.manifestDigest;
- as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
+ if (DeviceUtils.isWear(this)) {
+ showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
+ return;
}
- mInstallFlowAnalytics.setPackageInfoObtained();
//set view
setContentView(R.layout.install_start);
mInstallConfirm = findViewById(R.id.install_confirm_panel);
mInstallConfirm.setVisibility(View.INVISIBLE);
- PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
-
- mOriginatingUid = getOriginatingUid(intent);
+ mOk = (Button)findViewById(R.id.ok_button);
+ mCancel = (Button)findViewById(R.id.cancel_button);
+ mOk.setOnClickListener(this);
+ mCancel.setOnClickListener(this);
// Block the install attempt on the Unknown Sources setting if necessary.
+ final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
if (!requestFromUnknownSource) {
- initiateInstall();
+ processPackageUri(packageUri);
return;
}
// If the admin prohibits it, or we're running in a managed profile, just show error
// and exit. Otherwise show an option to take the user to Settings to change the setting.
final boolean isManagedProfile = mUserManager.isManagedProfile();
- if (!unknownSourcesAllowedByAdmin
- || (!unknownSourcesAllowedByUser && isManagedProfile)) {
+ if (!isUnknownSourcesAllowedByAdmin()) {
+ startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
+ clearCachedApkIfNeededAndFinish();
+ } else if (!isUnknownSourcesEnabled() && isManagedProfile) {
showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
- } else if (!unknownSourcesAllowedByUser) {
+ } else if (!isUnknownSourcesEnabled()) {
// Ask user to enable setting first
+
showDialogInner(DLG_UNKNOWN_SOURCES);
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
} else {
- initiateInstall();
+ processPackageUri(packageUri);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mStagingAsynTask != null) {
+ mStagingAsynTask.cancel(true);
+ mStagingAsynTask = null;
}
+ super.onDestroy();
+ }
+
+ private void processPackageUri(final Uri packageUri) {
+ mPackageURI = packageUri;
+
+ final String scheme = packageUri.getScheme();
+ final PackageUtil.AppSnippet as;
+
+ switch (scheme) {
+ case SCHEME_PACKAGE: {
+ try {
+ mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
+ PackageManager.GET_PERMISSIONS
+ | PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (NameNotFoundException e) {
+ }
+ if (mPkgInfo == null) {
+ Log.w(TAG, "Requested package " + packageUri.getScheme()
+ + " not available. Discontinuing installation");
+ showDialogInner(DLG_PACKAGE_ERROR);
+ setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
+ return;
+ }
+ as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
+ mPm.getApplicationIcon(mPkgInfo.applicationInfo));
+ } break;
+
+ case SCHEME_FILE: {
+ File sourceFile = new File(packageUri.getPath());
+ PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
+
+ // Check for parse errors
+ if (parsed == null) {
+ Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
+ showDialogInner(DLG_PACKAGE_ERROR);
+ setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
+ return;
+ }
+ mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
+ PackageManager.GET_PERMISSIONS, 0, 0, null,
+ new PackageUserState());
+ as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
+ } break;
+
+ case SCHEME_CONTENT: {
+ mStagingAsynTask = new StagingAsyncTask();
+ mStagingAsynTask.execute(packageUri);
+ return;
+ }
+
+ default: {
+ Log.w(TAG, "Unsupported scheme " + scheme);
+ setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
+ clearCachedApkIfNeededAndFinish();
+ return;
+ }
+ }
+
+ PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
+
+ initiateInstall();
}
/** Get the ApplicationInfo for the calling package, if available */
@@ -660,43 +624,33 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
super.onBackPressed();
}
// Generic handling when pressing back key
public void onCancel(DialogInterface dialog) {
- finish();
+ clearCachedApkIfNeededAndFinish();
}
public void onClick(View v) {
if (v == mOk) {
if (mOkCanInstall || mScrollView == null) {
- mInstallFlowAnalytics.setInstallButtonClicked();
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
-
- // We're only confirming permissions, so we don't really know how the
- // story ends; assume success.
- mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(
- PackageManager.INSTALL_SUCCEEDED);
- finish();
+ clearCachedApkIfNeededAndFinish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
- } else if(v == mCancel) {
+ } else if (v == mCancel) {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
- finish();
+ clearCachedApkIfNeededAndFinish();
}
}
@@ -707,9 +661,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
- newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
- newIntent.putExtra(
- InstallAppProgress.EXTRA_INSTALL_FLOW_ANALYTICS, mInstallFlowAnalytics);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
@@ -733,4 +684,98 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
startActivity(newIntent);
finish();
}
+
+ private void clearCachedApkIfNeededAndFinish() {
+ if (mContentUriApkStagingFile != null) {
+ mContentUriApkStagingFile.delete();
+ mContentUriApkStagingFile = null;
+ }
+ finish();
+ }
+
+ private final class StagingAsyncTask extends AsyncTask<Uri, Void, File> {
+ private static final long SHOW_EMPTY_STATE_DELAY_MILLIS = 300;
+
+ private final Runnable mEmptyStateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ ((TextView) findViewById(R.id.app_name)).setText(R.string.app_name_unknown);
+ ((TextView) findViewById(R.id.install_confirm_question))
+ .setText(R.string.message_staging);
+ mInstallConfirm.setVisibility(View.VISIBLE);
+ findViewById(android.R.id.tabhost).setVisibility(View.INVISIBLE);
+ findViewById(R.id.spacer).setVisibility(View.VISIBLE);
+ findViewById(R.id.ok_button).setEnabled(false);
+ Drawable icon = getDrawable(R.drawable.ic_file_download);
+ Utils.applyTint(PackageInstallerActivity.this,
+ icon, android.R.attr.colorControlNormal);
+ ((ImageView) findViewById(R.id.app_icon)).setImageDrawable(icon);
+ }
+ };
+
+ @Override
+ protected void onPreExecute() {
+ getWindow().getDecorView().postDelayed(mEmptyStateRunnable,
+ SHOW_EMPTY_STATE_DELAY_MILLIS);
+ }
+
+ @Override
+ protected File doInBackground(Uri... params) {
+ if (params == null || params.length <= 0) {
+ return null;
+ }
+ Uri packageUri = params[0];
+ File sourceFile = null;
+ try {
+ sourceFile = File.createTempFile("package", ".apk", getCacheDir());
+ try (
+ InputStream in = getContentResolver().openInputStream(packageUri);
+ OutputStream out = (in != null) ? new FileOutputStream(
+ sourceFile) : null;
+ ) {
+ // Despite the comments in ContentResolver#openInputStream
+ // the returned stream can be null.
+ if (in == null) {
+ return null;
+ }
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = in.read(buffer)) >= 0) {
+ // Be nice and respond to a cancellation
+ if (isCancelled()) {
+ return null;
+ }
+ out.write(buffer, 0, bytesRead);
+ }
+ }
+ } catch (IOException ioe) {
+ Log.w(TAG, "Error staging apk from content URI", ioe);
+ if (sourceFile != null) {
+ sourceFile.delete();
+ }
+ }
+ return sourceFile;
+ }
+
+ @Override
+ protected void onPostExecute(File file) {
+ getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable);
+ if (isFinishing() || isDestroyed()) {
+ return;
+ }
+ if (file == null) {
+ showDialogInner(DLG_PACKAGE_ERROR);
+ setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
+ return;
+ }
+ mContentUriApkStagingFile = file;
+ Uri fileUri = Uri.fromFile(file);
+ processPackageUri(fileUri);
+ }
+
+ @Override
+ protected void onCancelled(File file) {
+ getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable);
+ }
+ };
}
diff --git a/src/com/android/packageinstaller/PackageUtil.java b/src/com/android/packageinstaller/PackageUtil.java
index 37e96f0a..330cbbce 100644
--- a/src/com/android/packageinstaller/PackageUtil.java
+++ b/src/com/android/packageinstaller/PackageUtil.java
@@ -47,27 +47,12 @@ public class PackageUtil {
public static final String INTENT_ATTR_PACKAGE_NAME=PREFIX+"PackageName";
/**
- * Utility method to get application information for a given {@link File}
- */
- public static ApplicationInfo getApplicationInfo(File sourcePath) {
- final PackageParser parser = new PackageParser();
- try {
- PackageParser.Package pkg = parser.parseMonolithicPackage(sourcePath, 0);
- return pkg.applicationInfo;
- } catch (PackageParserException e) {
- return null;
- }
- }
-
- /**
* Utility method to get package information for a given {@link File}
*/
public static PackageParser.Package getPackageInfo(File sourceFile) {
final PackageParser parser = new PackageParser();
try {
- PackageParser.Package pkg = parser.parseMonolithicPackage(sourceFile, 0);
- parser.collectManifestDigest(pkg);
- return pkg;
+ return parser.parsePackage(sourceFile, 0);
} catch (PackageParserException e) {
return null;
}
diff --git a/src/com/android/packageinstaller/TabsAdapter.java b/src/com/android/packageinstaller/TabsAdapter.java
index 699cbed3..3509e092 100644
--- a/src/com/android/packageinstaller/TabsAdapter.java
+++ b/src/com/android/packageinstaller/TabsAdapter.java
@@ -46,7 +46,6 @@ public class TabsAdapter extends PagerAdapter
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
private final Rect mTempRect = new Rect();
- private TabHost.OnTabChangeListener mOnTabChangeListener;
static final class TabInfo {
private final String tag;
@@ -115,17 +114,10 @@ public class TabsAdapter extends PagerAdapter
return view == object;
}
- public void setOnTabChangedListener(TabHost.OnTabChangeListener listener) {
- mOnTabChangeListener = listener;
- }
-
@Override
public void onTabChanged(String tabId) {
int position = mTabHost.getCurrentTab();
mViewPager.setCurrentItem(position);
- if (mOnTabChangeListener != null) {
- mOnTabChangeListener.onTabChanged(tabId);
- }
}
@Override
diff --git a/src/com/android/packageinstaller/UninstallAppProgress.java b/src/com/android/packageinstaller/UninstallAppProgress.java
index d6b788d4..63a243b2 100755
--- a/src/com/android/packageinstaller/UninstallAppProgress.java
+++ b/src/com/android/packageinstaller/UninstallAppProgress.java
@@ -27,6 +27,9 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -35,7 +38,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.Log;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
@@ -61,14 +66,22 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
private UserHandle mUser;
private IBinder mCallback;
- private TextView mStatusTextView;
private Button mOkButton;
private Button mDeviceManagerButton;
- private ProgressBar mProgressBar;
- private View mOkPanel;
+ private Button mUsersButton;
private volatile int mResultCode = -1;
+ /**
+ * If initView was called. We delay this call to not have to call it at all if the uninstall is
+ * quick
+ */
+ private boolean mIsViewInitialized;
+
+ /** Amount of time to wait until we show the UI */
+ private static final int QUICK_INSTALL_DELAY_MILLIS = 500;
+
private static final int UNINSTALL_COMPLETE = 1;
+ private static final int UNINSTALL_IS_SLOW = 2;
private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) {
if (userId == profileId) {
@@ -80,8 +93,21 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
+ if (isFinishing() || isDestroyed()) {
+ return;
+ }
+
switch (msg.what) {
+ case UNINSTALL_IS_SLOW:
+ initView();
+ break;
case UNINSTALL_COMPLETE:
+ mHandler.removeMessages(UNINSTALL_IS_SLOW);
+
+ if (msg.arg1 != PackageManager.DELETE_SUCCEEDED) {
+ initView();
+ }
+
mResultCode = msg.arg1;
final String packageName = (String) msg.obj;
@@ -180,15 +206,19 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
mDeviceManagerButton.setVisibility(View.VISIBLE);
} else {
mDeviceManagerButton.setVisibility(View.GONE);
+ mUsersButton.setVisibility(View.VISIBLE);
}
- if (blockingUserId == UserHandle.USER_OWNER) {
+ // TODO: b/25442806
+ if (blockingUserId == UserHandle.USER_SYSTEM) {
statusText = getString(R.string.uninstall_blocked_device_owner);
} else if (blockingUserId == UserHandle.USER_NULL) {
Log.d(TAG, "Uninstall failed for " + packageName + " with code "
+ msg.arg1 + " no blocking user");
statusText = getString(R.string.uninstall_failed);
} else {
- statusText = getString(R.string.uninstall_blocked_profile_owner);
+ statusText = mAllUsers
+ ? getString(R.string.uninstall_all_blocked_profile_owner) :
+ getString(R.string.uninstall_blocked_profile_owner);
}
break;
}
@@ -198,11 +228,10 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
statusText = getString(R.string.uninstall_failed);
break;
}
- mStatusTextView.setText(statusText);
-
- // Hide the progress bar; Show the ok button
- mProgressBar.setVisibility(View.INVISIBLE);
- mOkPanel.setVisibility(View.VISIBLE);
+ findViewById(R.id.progress_view).setVisibility(View.GONE);
+ findViewById(R.id.status_view).setVisibility(View.VISIBLE);
+ ((TextView)findViewById(R.id.status_text)).setText(statusText);
+ findViewById(R.id.ok_panel).setVisibility(View.VISIBLE);
break;
default:
break;
@@ -213,11 +242,34 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+
Intent intent = getIntent();
mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
+ mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+
+ // This currently does not support going through a onDestroy->onCreate cycle. Hence if that
+ // happened, just fail the operation for mysterious reasons.
+ if (icicle != null) {
+ mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR;
+
+ if (mCallback != null) {
+ final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
+ .asInterface(mCallback);
+ try {
+ observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null);
+ } catch (RemoteException ignored) {
+ }
+ finish();
+ } else {
+ setResultAndFinish(mResultCode);
+ }
+
+ return;
+ }
+
mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
- if (mAllUsers && UserHandle.myUserId() != UserHandle.USER_OWNER) {
- throw new SecurityException("Only owner user can request uninstall for all users");
+ if (mAllUsers && !UserManager.get(this).isAdminUser()) {
+ throw new SecurityException("Only admin user can request uninstall for all users");
}
mUser = intent.getParcelableExtra(Intent.EXTRA_USER);
if (mUser == null) {
@@ -230,8 +282,21 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
+ "request uninstall for user " + mUser);
}
}
- mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
- initView();
+
+ PackageDeleteObserver observer = new PackageDeleteObserver();
+
+ // Make window transparent until initView is called. In many cases we can avoid showing the
+ // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove
+ // it, it just looks like a flicker.
+ getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ getWindow().setStatusBarColor(Color.TRANSPARENT);
+ getWindow().setNavigationBarColor(Color.TRANSPARENT);
+
+ getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer,
+ mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, mUser.getIdentifier());
+
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW),
+ QUICK_INSTALL_DELAY_MILLIS);
}
class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
@@ -249,6 +314,28 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
}
public void initView() {
+ if (mIsViewInitialized) {
+ return;
+ }
+ mIsViewInitialized = true;
+
+ // We set the window background to translucent in constructor, revert this
+ TypedValue attribute = new TypedValue();
+ getTheme().resolveAttribute(android.R.attr.windowBackground, attribute, true);
+ if (attribute.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ attribute.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ getWindow().setBackgroundDrawable(new ColorDrawable(attribute.data));
+ } else {
+ getWindow().setBackgroundDrawable(getResources().getDrawable(attribute.resourceId,
+ getTheme()));
+ }
+
+ getTheme().resolveAttribute(android.R.attr.navigationBarColor, attribute, true);
+ getWindow().setNavigationBarColor(attribute.data);
+
+ getTheme().resolveAttribute(android.R.attr.statusBarColor, attribute, true);
+ getWindow().setStatusBarColor(attribute.data);
+
boolean isUpdate = ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
setTitle(isUpdate ? R.string.uninstall_update_title : R.string.uninstall_application_title);
@@ -256,9 +343,8 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
// Initialize views
View snippetView = findViewById(R.id.app_snippet);
PackageUtil.initSnippetForInstalledApp(this, mAppInfo, snippetView);
- mStatusTextView = (TextView) findViewById(R.id.center_text);
- mStatusTextView.setText(R.string.uninstalling);
mDeviceManagerButton = (Button) findViewById(R.id.device_manager_button);
+ mUsersButton = (Button) findViewById(R.id.users_button);
mDeviceManagerButton.setVisibility(View.GONE);
mDeviceManagerButton.setOnClickListener(new OnClickListener() {
@Override
@@ -271,24 +357,19 @@ public class UninstallAppProgress extends Activity implements OnClickListener {
finish();
}
});
- mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
- mProgressBar.setIndeterminate(true);
+ mUsersButton.setVisibility(View.GONE);
+ mUsersButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ finish();
+ }
+ });
// Hide button till progress is being displayed
- mOkPanel = (View) findViewById(R.id.ok_panel);
mOkButton = (Button) findViewById(R.id.ok_button);
mOkButton.setOnClickListener(this);
- mOkPanel.setVisibility(View.INVISIBLE);
- IPackageManager packageManager =
- IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
- PackageDeleteObserver observer = new PackageDeleteObserver();
- try {
- packageManager.deletePackageAsUser(mAppInfo.packageName, observer,
- mUser.getIdentifier(),
- mAllUsers ? PackageManager.DELETE_ALL_USERS : 0);
- } catch (RemoteException e) {
- // Shouldn't happen.
- Log.e(TAG, "Failed to talk to package manager", e);
- }
}
public void onClick(View v) {
diff --git a/src/com/android/packageinstaller/UninstallerActivity.java b/src/com/android/packageinstaller/UninstallerActivity.java
index e277d48f..ae1659f4 100755
--- a/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/src/com/android/packageinstaller/UninstallerActivity.java
@@ -74,11 +74,15 @@ public class UninstallerActivity extends Activity {
final boolean isUpdate =
((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+ UserManager userManager = UserManager.get(getActivity());
if (isUpdate) {
- messageBuilder.append(getString(R.string.uninstall_update_text));
+ if (isSingleUser(userManager)) {
+ messageBuilder.append(getString(R.string.uninstall_update_text));
+ } else {
+ messageBuilder.append(getString(R.string.uninstall_update_text_multiuser));
+ }
} else {
- UserManager userManager = UserManager.get(getActivity());
- if (dialogInfo.allUsers && userManager.getUserCount() >= 2) {
+ if (dialogInfo.allUsers && !isSingleUser(userManager)) {
messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
} else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) {
UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
@@ -109,7 +113,19 @@ public class UninstallerActivity extends Activity {
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
- getActivity().finish();
+ if (isAdded()) {
+ getActivity().finish();
+ }
+ }
+
+ /**
+ * Returns whether there is only one user on this device, not including
+ * the system-only user.
+ */
+ private boolean isSingleUser(UserManager userManager) {
+ final int userCount = userManager.getUserCount();
+ return userCount == 1
+ || (UserManager.isSplitSystemUser() && userCount == 2);
}
}
@@ -127,9 +143,11 @@ public class UninstallerActivity extends Activity {
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
- ((UninstallerActivity) getActivity()).dispatchAborted();
- getActivity().setResult(Activity.RESULT_FIRST_USER);
- getActivity().finish();
+ if (isAdded()) {
+ ((UninstallerActivity) getActivity()).dispatchAborted();
+ getActivity().setResult(Activity.RESULT_FIRST_USER);
+ getActivity().finish();
+ }
}
}
diff --git a/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java b/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java
index e54b7029..a6601165 100644
--- a/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java
+++ b/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java
@@ -25,10 +25,12 @@ import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.os.Build;
+import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.utils.ArrayUtils;
import com.android.packageinstaller.permission.utils.LocationUtils;
import java.util.ArrayList;
@@ -67,7 +69,7 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS
|| (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0
- || (permissionInfo.flags & PermissionInfo.FLAG_HIDDEN) != 0) {
+ || (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) {
return null;
}
@@ -92,7 +94,7 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
}
return create(context, packageInfo, groupInfo, permissionInfos,
- new UserHandle(context.getUserId()));
+ Process.myUserHandle());
}
public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
@@ -135,21 +137,20 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
continue;
}
- // Don't allow toggle of non platform defined permissions for legacy apps via app ops.
+ // Don't allow toggling non-platform permission groups for legacy apps via app ops.
if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
- && !PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)) {
+ && !PLATFORM_PACKAGE_NAME.equals(groupInfo.packageName)) {
continue;
}
final boolean granted = (packageInfo.requestedPermissionsFlags[i]
& PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
- final int appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
- ? AppOpsManager.permissionToOpCode(requestedPermissionInfo.name)
- : AppOpsManager.OP_NONE;
+ final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
+ ? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null;
- final boolean appOpAllowed = appOp != AppOpsManager.OP_NONE
- && context.getSystemService(AppOpsManager.class).checkOp(appOp,
+ final boolean appOpAllowed = appOp != null
+ && context.getSystemService(AppOpsManager.class).checkOpNoThrow(appOp,
packageInfo.applicationInfo.uid, packageInfo.packageName)
== AppOpsManager.MODE_ALLOWED;
@@ -209,23 +210,39 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
return mAppSupportsRuntimePermissions;
}
-
- public boolean hasGrantedByDefaultPermission() {
+ public boolean isReviewRequired() {
+ if (mAppSupportsRuntimePermissions) {
+ return false;
+ }
final int permissionCount = mPermissions.size();
for (int i = 0; i < permissionCount; i++) {
Permission permission = mPermissions.valueAt(i);
- if (permission.isGrantedByDefault()) {
+ if (permission.isReviewRequired()) {
return true;
}
}
return false;
}
- public boolean hasAppOpPermission() {
+ public void resetReviewRequired() {
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ Permission permission = mPermissions.valueAt(i);
+ if (permission.isReviewRequired()) {
+ permission.resetReviewRequired();
+ mPackageManager.updatePermissionFlags(permission.getName(),
+ mPackageInfo.packageName,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED,
+ 0, mUserHandle);
+ }
+ }
+ }
+
+ public boolean hasGrantedByDefaultPermission() {
final int permissionCount = mPermissions.size();
for (int i = 0; i < permissionCount; i++) {
Permission permission = mPermissions.valueAt(i);
- if (permission.getAppOp() != AppOpsManager.OP_NONE) {
+ if (permission.isGrantedByDefault()) {
return true;
}
}
@@ -260,24 +277,35 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
return mDescription;
}
+ public int getUserId() {
+ return mUserHandle.getIdentifier();
+ }
+
public boolean hasPermission(String permission) {
return mPermissions.get(permission) != null;
}
public boolean areRuntimePermissionsGranted() {
+ return areRuntimePermissionsGranted(null);
+ }
+
+ public boolean areRuntimePermissionsGranted(String[] filterPermissions) {
if (LocationUtils.isLocationGroupAndProvider(mName, mPackageInfo.packageName)) {
return LocationUtils.isLocationEnabled(mContext);
}
final int permissionCount = mPermissions.size();
for (int i = 0; i < permissionCount; i++) {
Permission permission = mPermissions.valueAt(i);
+ if (filterPermissions != null
+ && !ArrayUtils.contains(filterPermissions, permission.getName())) {
+ continue;
+ }
if (mAppSupportsRuntimePermissions) {
if (permission.isGranted()) {
return true;
}
- } else if (permission.isGranted() && ((permission.getAppOp()
- != AppOpsManager.OP_NONE && permission.isAppOpAllowed())
- || permission.getAppOp() == AppOpsManager.OP_NONE)) {
+ } else if (permission.isGranted() && (permission.getAppOp() == null
+ || permission.isAppOpAllowed())) {
return true;
}
}
@@ -285,13 +313,21 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
}
public boolean grantRuntimePermissions(boolean fixedByTheUser) {
- final boolean isSharedUser = mPackageInfo.sharedUserId != null;
+ return grantRuntimePermissions(fixedByTheUser, null);
+ }
+
+ public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
final int uid = mPackageInfo.applicationInfo.uid;
// We toggle permissions only to apps that support runtime
// permissions, otherwise we toggle the app op corresponding
// to the permission if the permission is granted to the app.
for (Permission permission : mPermissions.values()) {
+ if (filterPermissions != null
+ && !ArrayUtils.contains(filterPermissions, permission.getName())) {
+ continue;
+ }
+
if (mAppSupportsRuntimePermissions) {
// Do not touch permissions fixed by the system.
if (permission.isSystemFixed()) {
@@ -327,43 +363,42 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
}
} else {
// Legacy apps cannot have a not granted permission but just in case.
- // Also if the permissions has no corresponding app op, then it is a
- // third-party one and we do not offer toggling of such permissions.
- if (!permission.isGranted() || !permission.hasAppOp()) {
+ if (!permission.isGranted()) {
continue;
}
- if (!permission.isAppOpAllowed()) {
- permission.setAppOpAllowed(true);
- // It this is a shared user we want to enable the app op for all
- // packages in the shared user to match the behavior of this
- // shared user having a runtime permission.
- if (isSharedUser) {
- // Enable the app op.
- String[] packageNames = mPackageManager.getPackagesForUid(uid);
- for (String packageName : packageNames) {
- mAppOps.setUidMode(permission.getAppOp(), uid,
- AppOpsManager.MODE_ALLOWED);
- }
- } else {
+ int killUid = -1;
+ int mask = 0;
+
+ // If the permissions has no corresponding app op, then it is a
+ // third-party one and we do not offer toggling of such permissions.
+ if (permission.hasAppOp()) {
+ if (!permission.isAppOpAllowed()) {
+ permission.setAppOpAllowed(true);
// Enable the app op.
mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
+
+ // Legacy apps do not know that they have to retry access to a
+ // resource due to changes in runtime permissions (app ops in this
+ // case). Therefore, we restart them on app op change, so they
+ // can pick up the change.
+ killUid = uid;
}
// Mark that the permission should not be be granted on upgrade
// when the app begins supporting runtime permissions.
if (permission.shouldRevokeOnUpgrade()) {
permission.setRevokeOnUpgrade(false);
- mPackageManager.updatePermissionFlags(permission.getName(),
- mPackageInfo.packageName,
- PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
- 0, mUserHandle);
+ mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
}
+ }
+
+ if (mask != 0) {
+ mPackageManager.updatePermissionFlags(permission.getName(),
+ mPackageInfo.packageName, mask, 0, mUserHandle);
+ }
- // Legacy apps do not know that they have to retry access to a
- // resource due to changes in runtime permissions (app ops in this
- // case). Therefore, we restart them on app op change, so they
- // can pick up the change.
+ if (killUid != -1) {
mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
}
}
@@ -373,13 +408,21 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
}
public boolean revokeRuntimePermissions(boolean fixedByTheUser) {
- final boolean isSharedUser = mPackageInfo.sharedUserId != null;
+ return revokeRuntimePermissions(fixedByTheUser, null);
+ }
+
+ public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
final int uid = mPackageInfo.applicationInfo.uid;
// We toggle permissions only to apps that support runtime
// permissions, otherwise we toggle the app op corresponding
// to the permission if the permission is granted to the app.
for (Permission permission : mPermissions.values()) {
+ if (filterPermissions != null
+ && !ArrayUtils.contains(filterPermissions, permission.getName())) {
+ continue;
+ }
+
if (mAppSupportsRuntimePermissions) {
// Do not touch permissions fixed by the system.
if (permission.isSystemFixed()) {
@@ -419,43 +462,43 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
}
} else {
// Legacy apps cannot have a non-granted permission but just in case.
- // Also if the permission has no corresponding app op, then it is a
- // third-party one and we do not offer toggling of such permissions.
- if (!permission.isGranted() || !permission.hasAppOp()) {
+ if (!permission.isGranted()) {
continue;
}
- if (permission.isAppOpAllowed()) {
- permission.setAppOpAllowed(false);
- // It this is a shared user we want to enable the app op for all
- // packages the the shared user to match the behavior of this
- // shared user having a runtime permission.
- if (isSharedUser) {
- String[] packageNames = mPackageManager.getPackagesForUid(uid);
- for (String packageName : packageNames) {
- // Disable the app op.
- mAppOps.setUidMode(permission.getAppOp(), uid,
- AppOpsManager.MODE_IGNORED);
- }
- } else {
+ int mask = 0;
+ int flags = 0;
+ int killUid = -1;
+
+ // If the permission has no corresponding app op, then it is a
+ // third-party one and we do not offer toggling of such permissions.
+ if (permission.hasAppOp()) {
+ if (permission.isAppOpAllowed()) {
+ permission.setAppOpAllowed(false);
// Disable the app op.
mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED);
+
+ // Disabling an app op may put the app in a situation in which it
+ // has a handle to state it shouldn't have, so we have to kill the
+ // app. This matches the revoke runtime permission behavior.
+ killUid = uid;
}
// Mark that the permission should not be granted on upgrade
// when the app begins supporting runtime permissions.
if (!permission.shouldRevokeOnUpgrade()) {
permission.setRevokeOnUpgrade(true);
- mPackageManager.updatePermissionFlags(permission.getName(),
- mPackageInfo.packageName,
- PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
- PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
- mUserHandle);
+ mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ flags |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
}
+ }
+
+ if (mask != 0) {
+ mPackageManager.updatePermissionFlags(permission.getName(),
+ mPackageInfo.packageName, mask, flags, mUserHandle);
+ }
- // Disabling an app op may put the app in a situation in which it
- // has a handle to state it shouldn't have, so we have to kill the
- // app. This matches the revoke runtime permission behavior.
+ if (killUid != -1) {
mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
}
}
diff --git a/src/com/android/packageinstaller/permission/model/AppPermissions.java b/src/com/android/packageinstaller/permission/model/AppPermissions.java
index a0f23d64..e455ef13 100644
--- a/src/com/android/packageinstaller/permission/model/AppPermissions.java
+++ b/src/com/android/packageinstaller/permission/model/AppPermissions.java
@@ -19,6 +19,7 @@ package com.android.packageinstaller.permission.model;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.text.BidiFormatter;
import android.text.TextPaint;
import android.text.TextUtils;
@@ -31,16 +32,6 @@ import java.util.LinkedHashMap;
import java.util.List;
public final class AppPermissions {
- private static final float MAX_APP_LABEL_LENGTH_PIXELS = 500;
-
- private static final TextPaint sAppLabelEllipsizePaint = new TextPaint();
- static {
- sAppLabelEllipsizePaint.setAntiAlias(true);
- // Both text size and width are given in absolute pixels, for consistent truncation
- // across devices; this value corresponds to the default 14dip size on an xdhpi device.
- sAppLabelEllipsizePaint.setTextSize(42);
- }
-
private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>();
private final LinkedHashMap<String, AppPermissionGroup> mNameToGroupMap = new LinkedHashMap<>();
@@ -62,7 +53,9 @@ public final class AppPermissions {
mContext = context;
mPackageInfo = packageInfo;
mFilterPermissions = permissions;
- mAppLabel = loadEllipsizedAppLabel(context, packageInfo);
+ mAppLabel = BidiFormatter.getInstance().unicodeWrap(
+ packageInfo.applicationInfo.loadSafeLabel(
+ context.getPackageManager()).toString());
mSortGroups = sortGroups;
mOnErrorCallback = onErrorCallback;
loadPermissionGroups();
@@ -89,6 +82,19 @@ public final class AppPermissions {
return mGroups;
}
+ public boolean isReviewRequired() {
+ if (!Build.PERMISSIONS_REVIEW_REQUIRED) {
+ return false;
+ }
+ final int groupCount = mGroups.size();
+ for (int i = 0; i < groupCount; i++) {
+ AppPermissionGroup group = mGroups.get(i);
+ if (group.isReviewRequired()) {
+ return true;
+ }
+ }
+ return false;
+ }
private void loadPackageInfo() {
try {
@@ -163,16 +169,4 @@ public final class AppPermissions {
}
return false;
}
-
- private static CharSequence loadEllipsizedAppLabel(Context context, PackageInfo packageInfo) {
- String label = packageInfo.applicationInfo.loadLabel(
- context.getPackageManager()).toString();
- String ellipsizedLabel = label.replace("\n", " ");
- if (!DeviceUtils.isWear(context)) {
- // Only ellipsize for non-Wear devices.
- ellipsizedLabel = TextUtils.ellipsize(ellipsizedLabel, sAppLabelEllipsizePaint,
- MAX_APP_LABEL_LENGTH_PIXELS, TextUtils.TruncateAt.END).toString();
- }
- return BidiFormatter.getInstance().unicodeWrap(ellipsizedLabel);
- }
}
diff --git a/src/com/android/packageinstaller/permission/model/Permission.java b/src/com/android/packageinstaller/permission/model/Permission.java
index 1be4e75b..f9dc6e8e 100644
--- a/src/com/android/packageinstaller/permission/model/Permission.java
+++ b/src/com/android/packageinstaller/permission/model/Permission.java
@@ -16,19 +16,18 @@
package com.android.packageinstaller.permission.model;
-import android.app.AppOpsManager;
import android.content.pm.PackageManager;
public final class Permission {
private final String mName;
- private final int mAppOp;
+ private final String mAppOp;
private boolean mGranted;
private boolean mAppOpAllowed;
private int mFlags;
public Permission(String name, boolean granted,
- int appOp, boolean appOpAllowed, int flags) {
+ String appOp, boolean appOpAllowed, int flags) {
mName = name;
mGranted = granted;
mAppOp = appOp;
@@ -40,7 +39,7 @@ public final class Permission {
return mName;
}
- public int getAppOp() {
+ public String getAppOp() {
return mAppOp;
}
@@ -49,13 +48,21 @@ public final class Permission {
}
public boolean hasAppOp() {
- return mAppOp != AppOpsManager.OP_NONE;
+ return mAppOp != null;
}
public boolean isGranted() {
return mGranted;
}
+ public boolean isReviewRequired() {
+ return (mFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
+ }
+
+ public void resetReviewRequired() {
+ mFlags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+ }
+
public void setGranted(boolean mGranted) {
this.mGranted = mGranted;
}
diff --git a/src/com/android/packageinstaller/permission/model/PermissionApps.java b/src/com/android/packageinstaller/permission/model/PermissionApps.java
index e5d96d55..be32f2ac 100644
--- a/src/com/android/packageinstaller/permission/model/PermissionApps.java
+++ b/src/com/android/packageinstaller/permission/model/PermissionApps.java
@@ -24,6 +24,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -35,7 +36,6 @@ import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.utils.Utils;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -120,7 +120,7 @@ public class PermissionApps {
return count;
}
- public Collection<PermissionApp> getApps() {
+ public List<PermissionApp> getApps() {
return mPermApps;
}
@@ -149,9 +149,10 @@ public class PermissionApps {
ArrayList<PermissionApp> permApps = new ArrayList<>();
- for (UserHandle user : UserManager.get(mContext).getUserProfiles()) {
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ for (UserHandle user : userManager.getUserProfiles()) {
List<PackageInfo> apps = mCache != null ? mCache.getPackages(user.getIdentifier())
- : mPm.getInstalledPackages(PackageManager.GET_PERMISSIONS,
+ : mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS,
user.getIdentifier());
final int N = apps.size();
@@ -182,17 +183,31 @@ public class PermissionApps {
|| (requestedPermissionInfo.flags
& PermissionInfo.FLAG_INSTALLED) == 0
|| (requestedPermissionInfo.flags
- & PermissionInfo.FLAG_HIDDEN) != 0) {
+ & PermissionInfo.FLAG_REMOVED) != 0) {
continue;
}
AppPermissionGroup group = AppPermissionGroup.create(mContext,
app, groupInfo, groupPermInfos, user);
+ if (group == null) {
+ continue;
+ }
+
String label = mSkipUi ? app.packageName
: app.applicationInfo.loadLabel(mPm).toString();
- PermissionApp permApp = new PermissionApp(app.packageName,
- group, label, getBadgedIcon(app.applicationInfo),
+
+ Drawable icon = null;
+ if (!mSkipUi) {
+ UserHandle userHandle = new UserHandle(
+ UserHandle.getUserId(group.getApp().applicationInfo.uid));
+
+ icon = mPm.getUserBadgedIcon(
+ mPm.loadUnbadgedItemIcon(app.applicationInfo, app.applicationInfo),
+ userHandle);
+ }
+
+ PermissionApp permApp = new PermissionApp(app.packageName, group, label, icon,
app.applicationInfo);
permApps.add(permApp);
@@ -246,15 +261,6 @@ public class PermissionApps {
return null;
}
- private Drawable getBadgedIcon(ApplicationInfo appInfo) {
- if (mSkipUi) {
- return null;
- }
- Drawable unbadged = appInfo.loadUnbadgedIcon(mPm);
- return mPm.getUserBadgedIcon(unbadged,
- new UserHandle(UserHandle.getUserId(appInfo.uid)));
- }
-
private void loadGroupInfo() {
PackageItemInfo info;
try {
@@ -317,6 +323,10 @@ public class PermissionApps {
return mAppPermissionGroup.areRuntimePermissionsGranted();
}
+ public boolean isReviewRequired() {
+ return mAppPermissionGroup.isReviewRequired();
+ }
+
public void grantRuntimePermissions() {
mAppPermissionGroup.grantRuntimePermissions(false);
}
@@ -341,8 +351,8 @@ public class PermissionApps {
return mAppPermissionGroup.hasRuntimePermission();
}
- public boolean hasAppOpPermissions() {
- return mAppPermissionGroup.hasAppOpPermission();
+ public int getUserId() {
+ return mAppPermissionGroup.getUserId();
}
public String getPackageName() {
@@ -401,7 +411,7 @@ public class PermissionApps {
public synchronized List<PackageInfo> getPackages(int userId) {
List<PackageInfo> ret = mPackageInfoCache.get(userId);
if (ret == null) {
- ret = mPm.getInstalledPackages(PackageManager.GET_PERMISSIONS, userId);
+ ret = mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, userId);
mPackageInfoCache.put(userId, ret);
}
return ret;
diff --git a/src/com/android/packageinstaller/permission/model/PermissionGroups.java b/src/com/android/packageinstaller/permission/model/PermissionGroups.java
index c496e898..8ca69f24 100644
--- a/src/com/android/packageinstaller/permission/model/PermissionGroups.java
+++ b/src/com/android/packageinstaller/permission/model/PermissionGroups.java
@@ -131,7 +131,7 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
seenPermissions.add(groupPermission.name);
if (groupPermission.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS
&& (groupPermission.flags & PermissionInfo.FLAG_INSTALLED) != 0
- && (groupPermission.flags & PermissionInfo.FLAG_HIDDEN) == 0) {
+ && (groupPermission.flags & PermissionInfo.FLAG_REMOVED) == 0) {
hasRuntimePermissions = true;
}
}
diff --git a/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java b/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java
index 810ae8ec..a87976a6 100644
--- a/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java
+++ b/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java
@@ -33,12 +33,110 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+/**
+ * This class handles backwards compatibility for M. Don't remove
+ * until we decide to drop M support altogether.
+ */
public class PermissionStatusReceiver extends BroadcastReceiver {
+
+ /**
+ * Broadcast action that requests current permission granted information. It will respond
+ * to the request by sending a broadcast with action defined by
+ * {@link #EXTRA_GET_PERMISSIONS_RESPONSE_INTENT}. The response will contain
+ * {@link #EXTRA_GET_PERMISSIONS_COUNT_RESULT}, as well as
+ * {@link #EXTRA_GET_PERMISSIONS_GROUP_LIST_RESULT}, with contents described below or
+ * a null upon failure.
+ *
+ * <p>If {@link Intent#EXTRA_PACKAGE_NAME} is included then the number of permissions granted, the
+ * number of permissions requested and the number of granted additional permissions
+ * by that package will be calculated and included as the first
+ * and second elements respectively of an int[] in the response as
+ * {@link #EXTRA_GET_PERMISSIONS_COUNT_RESULT}. The response will also deliver the list
+ * of localized permission group names that are granted in
+ * {@link #EXTRA_GET_PERMISSIONS_GROUP_LIST_RESULT}.
+ *
+ * <p>If {@link #EXTRA_PACKAGE_NAME} is not included then the number of apps granted any runtime
+ * permissions and the total number of apps requesting runtime permissions will be the first
+ * and second elements respectively of an int[] in the response as
+ * {@link #EXTRA_GET_PERMISSIONS_COUNT_RESULT}.
+ *
+ * @hide
+ */
+ public static final String ACTION_GET_PERMISSIONS_COUNT
+ = "android.intent.action.GET_PERMISSIONS_COUNT";
+
+ /**
+ * Broadcast action that requests list of all apps that have runtime permissions. It will
+ * respond to the request by sending a broadcast with action defined by
+ * {@link #EXTRA_GET_PERMISSIONS_PACKAGES_RESPONSE_INTENT}. The response will contain
+ * {@link #EXTRA_GET_PERMISSIONS_APP_LIST_RESULT}, as well as
+ * {@link #EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT}, with contents described below or
+ * a null upon failure.
+ *
+ * <p>{@link #EXTRA_GET_PERMISSIONS_APP_LIST_RESULT} will contain a list of package names of
+ * apps that have runtime permissions. {@link #EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT}
+ * will contain the list of app labels corresponding ot the apps in the first list.
+ *
+ * @hide
+ */
+ public static final String ACTION_GET_PERMISSIONS_PACKAGES
+ = "android.intent.action.GET_PERMISSIONS_PACKAGES";
+
+ /**
+ * Extra included in response to {@link #ACTION_GET_PERMISSIONS_COUNT}.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_COUNT_RESULT
+ = "android.intent.extra.GET_PERMISSIONS_COUNT_RESULT";
+
+ /**
+ * List of CharSequence of localized permission group labels.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_GROUP_LIST_RESULT
+ = "android.intent.extra.GET_PERMISSIONS_GROUP_LIST_RESULT";
+
+ /**
+ * String list of apps that have one or more runtime permissions.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_APP_LIST_RESULT
+ = "android.intent.extra.GET_PERMISSIONS_APP_LIST_RESULT";
+
+ /**
+ * String list of app labels for apps that have one or more runtime permissions.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT
+ = "android.intent.extra.GET_PERMISSIONS_APP_LABEL_LIST_RESULT";
+
+ /**
+ * Boolean list describing if the app is a system app for apps that have one or more runtime
+ * permissions.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT
+ = "android.intent.extra.GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT";
+
+ /**
+ * Required extra to be sent with {@link #ACTION_GET_PERMISSIONS_COUNT} broadcasts.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_RESPONSE_INTENT
+ = "android.intent.extra.GET_PERMISSIONS_RESONSE_INTENT";
+
+ /**
+ * Required extra to be sent with {@link #ACTION_GET_PERMISSIONS_PACKAGES} broadcasts.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_PACKAGES_RESPONSE_INTENT
+ = "android.intent.extra.GET_PERMISSIONS_PACKAGES_RESONSE_INTENT";
+
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_GET_PERMISSIONS_COUNT.equals(intent.getAction())) {
+ if (ACTION_GET_PERMISSIONS_COUNT.equals(intent.getAction())) {
Intent responseIntent = new Intent(intent.getStringExtra(
- Intent.EXTRA_GET_PERMISSIONS_RESPONSE_INTENT));
+ EXTRA_GET_PERMISSIONS_RESPONSE_INTENT));
responseIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
int[] counts = new int[3];
@@ -54,28 +152,28 @@ public class PermissionStatusReceiver extends BroadcastReceiver {
succeeded = getAppsWithPermissionsCount(context, counts);
}
if (succeeded) {
- responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_COUNT_RESULT, counts);
+ responseIntent.putExtra(EXTRA_GET_PERMISSIONS_COUNT_RESULT, counts);
if (isForPackage) {
- responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_GROUP_LIST_RESULT,
+ responseIntent.putExtra(EXTRA_GET_PERMISSIONS_GROUP_LIST_RESULT,
grantedGroups.toArray(new CharSequence[grantedGroups.size()]));
}
}
context.sendBroadcast(responseIntent);
- } else if (Intent.ACTION_GET_PERMISSIONS_PACKAGES.equals(intent.getAction())) {
+ } else if (ACTION_GET_PERMISSIONS_PACKAGES.equals(intent.getAction())) {
Intent responseIntent = new Intent(intent.getStringExtra(
- Intent.EXTRA_GET_PERMISSIONS_PACKAGES_RESPONSE_INTENT));
+ EXTRA_GET_PERMISSIONS_PACKAGES_RESPONSE_INTENT));
responseIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
List<String> appsList = new ArrayList<>();
List<CharSequence> appLabelsList = new ArrayList<>();
List<Boolean> isSystemAppList = new ArrayList<>();
if (getAppsWithRuntimePermissions(context, appsList, appLabelsList, isSystemAppList)) {
- responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_APP_LIST_RESULT,
+ responseIntent.putExtra(EXTRA_GET_PERMISSIONS_APP_LIST_RESULT,
appsList.toArray(new String[appsList.size()]));
- responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT,
+ responseIntent.putExtra(EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT,
appLabelsList.toArray(new String[appLabelsList.size()]));
- responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT,
+ responseIntent.putExtra(EXTRA_GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT,
toPrimitiveBoolArray(isSystemAppList));
}
context.sendBroadcast(responseIntent);
diff --git a/src/com/android/packageinstaller/permission/service/RuntimePermissionPresenterServiceImpl.java b/src/com/android/packageinstaller/permission/service/RuntimePermissionPresenterServiceImpl.java
new file mode 100644
index 00000000..fbd4f10c
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/service/RuntimePermissionPresenterServiceImpl.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+package com.android.packageinstaller.permission.service;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.permission.RuntimePermissionPresentationInfo;
+import android.permissionpresenterservice.RuntimePermissionPresenterService;
+import android.util.ArraySet;
+import android.util.Log;
+import com.android.packageinstaller.permission.model.AppPermissionGroup;
+import com.android.packageinstaller.permission.model.AppPermissions;
+import com.android.packageinstaller.permission.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service that provides presentation information for runtime permissions.
+ */
+public final class RuntimePermissionPresenterServiceImpl extends RuntimePermissionPresenterService {
+ private static final String LOG_TAG = "PermissionPresenter";
+
+ @Override
+ public List<RuntimePermissionPresentationInfo> onGetAppPermissions(String packageName) {
+ final PackageInfo packageInfo;
+ try {
+ packageInfo = getPackageManager().getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Error getting package:" + packageName, e);
+ return null;
+ }
+
+ List<RuntimePermissionPresentationInfo> permissions = new ArrayList<>();
+
+ AppPermissions appPermissions = new AppPermissions(this, packageInfo, null, false, null);
+ for (AppPermissionGroup group : appPermissions.getPermissionGroups()) {
+ if (Utils.shouldShowPermission(group, packageName)) {
+ final boolean granted = group.areRuntimePermissionsGranted();
+ final boolean standard = Utils.OS_PKG.equals(group.getDeclaringPackage());
+ RuntimePermissionPresentationInfo permission =
+ new RuntimePermissionPresentationInfo(group.getLabel(),
+ granted, standard);
+ permissions.add(permission);
+ }
+ }
+
+ return permissions;
+ }
+
+ @Override
+ public List<ApplicationInfo> onGetAppsUsingPermissions(boolean system) {
+ final List<ApplicationInfo> appInfos = Utils.getAllInstalledApplications(this);
+ if (appInfos == null || appInfos.isEmpty()) {
+ return null;
+ }
+ List<ApplicationInfo> appsResult = new ArrayList<>();
+ ArraySet<String> launcherPackages = Utils.getLauncherPackages(this);
+ final int appInfosSize = appInfos.size();
+ for (int i = 0; i < appInfosSize; i++) {
+ ApplicationInfo appInfo = appInfos.get(i);
+ final String packageName = appInfo.packageName;
+ final PackageInfo packageInfo;
+ try {
+ packageInfo = getPackageManager().getPackageInfo(
+ packageName, PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Error getting package info for:" + packageName, e);
+ continue;
+
+ }
+ AppPermissions appPermissions = new AppPermissions(this,
+ packageInfo, null, false, null);
+ boolean shouldShow = false;
+
+
+ for (AppPermissionGroup group : appPermissions.getPermissionGroups()) {
+ if (Utils.shouldShowPermission(group, packageName)) {
+ shouldShow = true;
+ break;
+ }
+ }
+ if (shouldShow) {
+ if (Utils.isSystem(appPermissions, launcherPackages) == system) {
+ appsResult.add(appInfo);
+ }
+ }
+ }
+ return appsResult;
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/ConfirmActionDialogFragment.java b/src/com/android/packageinstaller/permission/ui/ConfirmActionDialogFragment.java
new file mode 100644
index 00000000..d6d79001
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/ConfirmActionDialogFragment.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+package com.android.packageinstaller.permission.ui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import com.android.packageinstaller.R;
+
+public final class ConfirmActionDialogFragment extends DialogFragment {
+ public static final String ARG_MESSAGE = "MESSAGE";
+ public static final String ARG_ACTION = "ACTION";
+
+ public static interface OnActionConfirmedListener {
+ public void onActionConfirmed(String action);
+ }
+
+ public static ConfirmActionDialogFragment newInstance(CharSequence message, String action) {
+ Bundle arguments = new Bundle();
+ arguments.putCharSequence(ARG_MESSAGE, message);
+ arguments.putString(ARG_ACTION, action);
+ ConfirmActionDialogFragment fragment = new ConfirmActionDialogFragment();
+ fragment.setArguments(arguments);
+ return fragment;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle bundle) {
+ return new AlertDialog.Builder(getContext())
+ .setMessage(getArguments().getString(ARG_MESSAGE))
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Activity activity = getActivity();
+ if (activity instanceof OnActionConfirmedListener) {
+ String groupName = getArguments().getString(ARG_ACTION);
+ ((OnActionConfirmedListener) activity)
+ .onActionConfirmed(groupName);
+ }
+ }
+ })
+ .create();
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
index 102fd6ef..4ee76a18 100644
--- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
+++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
@@ -25,13 +25,14 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.hardware.camera2.utils.ArrayUtils;
+import android.os.Build;
import android.os.Bundle;
-import android.text.SpannableString;
-import android.text.style.StyleSpan;
+import android.text.Html;
+import android.text.Spanned;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -44,8 +45,8 @@ import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.model.Permission;
+import com.android.packageinstaller.permission.ui.handheld.GrantPermissionsViewHandlerImpl;
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
-import com.android.packageinstaller.permission.utils.Utils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -99,17 +100,27 @@ public class GrantPermissionsActivity extends OverlayTouchActivity
PackageInfo callingPackageInfo = getCallingPackageInfo();
+ if (callingPackageInfo == null || callingPackageInfo.requestedPermissions == null
+ || callingPackageInfo.requestedPermissions.length <= 0) {
+ setResultAndFinish();
+ return;
+ }
+
+ // Don't allow legacy apps to request runtime permissions.
+ if (callingPackageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ // Returning empty arrays means a cancellation.
+ mRequestedPermissions = new String[0];
+ mGrantResults = new int[0];
+ setResultAndFinish();
+ return;
+ }
+
DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
final int permissionPolicy = devicePolicyManager.getPermissionPolicy(null);
// If calling package is null we default to deny all.
updateDefaultResults(callingPackageInfo, permissionPolicy);
- if (callingPackageInfo == null) {
- setResultAndFinish();
- return;
- }
-
mAppPermissions = new AppPermissions(this, callingPackageInfo, null, false,
new Runnable() {
@Override
@@ -118,15 +129,15 @@ public class GrantPermissionsActivity extends OverlayTouchActivity
}
});
- for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
- boolean groupHasRequestedPermission = false;
- for (String requestedPermission : mRequestedPermissions) {
- if (group.hasPermission(requestedPermission)) {
- groupHasRequestedPermission = true;
+ for (String requestedPermission : mRequestedPermissions) {
+ AppPermissionGroup group = null;
+ for (AppPermissionGroup nextGroup : mAppPermissions.getPermissionGroups()) {
+ if (nextGroup.hasPermission(requestedPermission)) {
+ group = nextGroup;
break;
}
}
- if (!groupHasRequestedPermission) {
+ if (group == null) {
continue;
}
// We allow the user to choose only non-fixed permissions. A permission
@@ -176,6 +187,21 @@ public class GrantPermissionsActivity extends OverlayTouchActivity
}
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ // We need to relayout the window as dialog width may be
+ // different in landscape vs portrait which affect the min
+ // window height needed to show all content. We have to
+ // re-add the window to force it to be resized if needed.
+ View decor = getWindow().getDecorView();
+ getWindowManager().removeViewImmediate(decor);
+ getWindowManager().addView(decor, decor.getLayoutParams());
+ if (mViewHandler instanceof GrantPermissionsViewHandlerImpl) {
+ ((GrantPermissionsViewHandlerImpl) mViewHandler).onConfigurationChanged();
+ }
+ }
+
+ @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
View rootView = getWindow().getDecorView();
if (rootView.getTop() != 0) {
@@ -204,16 +230,10 @@ public class GrantPermissionsActivity extends OverlayTouchActivity
for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
if (groupState.mState == GroupState.STATE_UNKNOWN) {
CharSequence appLabel = mAppPermissions.getAppLabel();
- SpannableString message = new SpannableString(getString(
- R.string.permission_warning_template, appLabel,
- groupState.mGroup.getDescription()));
+ Spanned message = Html.fromHtml(getString(R.string.permission_warning_template,
+ appLabel, groupState.mGroup.getDescription()), 0);
// Set the permission message as the title so it can be announced.
setTitle(message);
- // Color the app name.
- int appLabelStart = message.toString().indexOf(appLabel.toString(), 0);
- int appLabelLength = appLabel.length();
- message.setSpan(new StyleSpan(Typeface.BOLD), appLabelStart,
- appLabelStart + appLabelLength, 0);
// Set the new grant view
// TODO: Use a real message for the action. We need group action APIs
@@ -241,11 +261,6 @@ public class GrantPermissionsActivity extends OverlayTouchActivity
@Override
public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
- if (isObscuredTouch()) {
- showOverlayDialog();
- finish();
- return;
- }
GroupState groupState = mRequestGrantPermissionGroups.get(name);
if (groupState.mGroup != null) {
if (granted) {
diff --git a/src/com/android/packageinstaller/permission/ui/ManualLayoutFrame.java b/src/com/android/packageinstaller/permission/ui/ManualLayoutFrame.java
index c9ccf9c1..a20c9523 100644
--- a/src/com/android/packageinstaller/permission/ui/ManualLayoutFrame.java
+++ b/src/com/android/packageinstaller/permission/ui/ManualLayoutFrame.java
@@ -18,58 +18,56 @@ package com.android.packageinstaller.permission.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.FrameLayout;
+import android.view.ViewGroup;
-/**
- * Allows one standard layout pass, but afterwards holds getMeasuredHeight constant,
- * however still allows drawing larger at the size needed by its children. This allows
- * a dialog to tell the window the height is constant (with keeps its position constant)
- * but allows the view to grow downwards for animation.
- */
-public class ManualLayoutFrame extends FrameLayout {
-
- private int mDesiredHeight;
- private int mHeight;
+public class ManualLayoutFrame extends ViewGroup {
+ private int mContentBottom;
private int mWidth;
- private View mOffsetView;
-
public ManualLayoutFrame(Context context, AttributeSet attrs) {
super(context, attrs);
- setClipChildren(false);
- setClipToPadding(false);
}
- public int getLayoutHeight() {
- return mDesiredHeight;
+ public void onConfigurationChanged() {
+ mContentBottom = 0;
+ mWidth = 0;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mWidth != 0) {
- // Keep the width constant to avoid weirdness.
+ int newWidth = mWidth;
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ switch (widthMode) {
+ case MeasureSpec.AT_MOST: {
+ newWidth = Math.min(mWidth, MeasureSpec.getSize(widthMeasureSpec));
+ } break;
+ case MeasureSpec.EXACTLY: {
+ newWidth = MeasureSpec.getSize(widthMeasureSpec);
+ } break;
+ }
+ if (newWidth != mWidth) {
+ mWidth = newWidth;
+ }
widthMeasureSpec = MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mDesiredHeight = getMeasuredHeight();
- if (mHeight == 0 && mDesiredHeight != 0) {
- // Record the first non-zero width and height, this will be the height henceforth.
- mHeight = mDesiredHeight;
+ if (mWidth == 0) {
mWidth = getMeasuredWidth();
}
- if (mHeight != 0) {
- // Always report the same height
- setMeasuredDimension(getMeasuredWidth(), mHeight);
- }
+
+ measureChild(getChildAt(0), widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (mDesiredHeight != 0) {
- // Draw at height we expect to be.
- setBottom(getTop() + mDesiredHeight);
- bottom = top + mDesiredHeight;
+ View content = getChildAt(0);
+ if (mContentBottom == 0) {
+ mContentBottom = (getMeasuredHeight() + content.getMeasuredHeight()) / 2;
}
- super.onLayout(changed, left, top, right, bottom);
+ final int contentLeft = (getMeasuredWidth() - content.getMeasuredWidth()) / 2;
+ final int contentTop = mContentBottom - content.getMeasuredHeight();
+ final int contentRight = contentLeft + content.getMeasuredWidth();
+ content.layout(contentLeft, contentTop, contentRight, mContentBottom);
}
}
diff --git a/src/com/android/packageinstaller/permission/ui/OverlayTouchActivity.java b/src/com/android/packageinstaller/permission/ui/OverlayTouchActivity.java
index b3938b1e..a7800ca5 100644
--- a/src/com/android/packageinstaller/permission/ui/OverlayTouchActivity.java
+++ b/src/com/android/packageinstaller/permission/ui/OverlayTouchActivity.java
@@ -15,35 +15,33 @@
*/
package com.android.packageinstaller.permission.ui;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.MotionEvent;
+import android.app.AppOpsManager;
+import android.os.Binder;
+import android.os.IBinder;
public class OverlayTouchActivity extends Activity {
-
- private boolean mObscuredTouch;
-
- public boolean isObscuredTouch() {
- return mObscuredTouch;
- }
+ private final IBinder mToken = new Binder();
@Override
- protected void onCreate(Bundle savedInstanceState) {
- getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- super.onCreate(savedInstanceState);
+ protected void onResume() {
+ super.onResume();
+ setOverlayAllowed(false);
}
@Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- mObscuredTouch = (event.getFlags() & (MotionEvent.FLAG_WINDOW_IS_OBSCURED
- | MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED)) != 0;
- return super.dispatchTouchEvent(event);
+ protected void onPause() {
+ super.onPause();
+ setOverlayAllowed(true);
}
- public void showOverlayDialog() {
- startActivity(new Intent(this, OverlayWarningDialog.class));
+ private void setOverlayAllowed(boolean allowed) {
+ AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
+ if (appOpsManager != null) {
+ appOpsManager.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+ !allowed, mToken);
+ appOpsManager.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW,
+ !allowed, mToken);
+ }
}
}
diff --git a/src/com/android/packageinstaller/permission/ui/ReviewPermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/ReviewPermissionsActivity.java
new file mode 100644
index 00000000..6bc251ae
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/ReviewPermissionsActivity.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+package com.android.packageinstaller.permission.ui;
+
+import android.app.Activity;
+
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.preference.TwoStatePreference;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.model.AppPermissionGroup;
+import com.android.packageinstaller.permission.model.AppPermissions;
+import com.android.packageinstaller.permission.utils.Utils;
+import com.android.packageinstaller.permission.ui.ConfirmActionDialogFragment.OnActionConfirmedListener;
+
+import java.util.List;
+
+public final class ReviewPermissionsActivity extends Activity
+ implements OnActionConfirmedListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ PackageInfo packageInfo = getTargetPackageInfo();
+ if (packageInfo == null) {
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.review_permissions);
+ if (getFragmentManager().findFragmentById(R.id.preferences_frame) == null) {
+ getFragmentManager().beginTransaction().add(R.id.preferences_frame,
+ ReviewPermissionsFragment.newInstance(packageInfo)).commit();
+ }
+ }
+
+ @Override
+ public void onActionConfirmed(String action) {
+ Fragment fragment = getFragmentManager().findFragmentById(R.id.preferences_frame);
+ if (fragment instanceof OnActionConfirmedListener) {
+ ((OnActionConfirmedListener) fragment).onActionConfirmed(action);
+ }
+ }
+
+ private PackageInfo getTargetPackageInfo() {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ if (TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ try {
+ return getPackageManager().getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ public static final class ReviewPermissionsFragment extends PreferenceFragment
+ implements View.OnClickListener, Preference.OnPreferenceChangeListener,
+ ConfirmActionDialogFragment.OnActionConfirmedListener {
+ public static final String EXTRA_PACKAGE_INFO =
+ "com.android.packageinstaller.permission.ui.extra.PACKAGE_INFO";
+
+ private AppPermissions mAppPermissions;
+
+ private Button mContinueButton;
+ private Button mCancelButton;
+
+ private PreferenceCategory mNewPermissionsCategory;
+
+ private boolean mHasConfirmedRevoke;
+
+ public static ReviewPermissionsFragment newInstance(PackageInfo packageInfo) {
+ Bundle arguments = new Bundle();
+ arguments.putParcelable(ReviewPermissionsFragment.EXTRA_PACKAGE_INFO, packageInfo);
+ ReviewPermissionsFragment instance = new ReviewPermissionsFragment();
+ instance.setArguments(arguments);
+ instance.setRetainInstance(true);
+ return instance;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ PackageInfo packageInfo = getArguments().getParcelable(EXTRA_PACKAGE_INFO);
+ if (packageInfo == null) {
+ activity.finish();
+ return;
+ }
+
+ mAppPermissions = new AppPermissions(activity, packageInfo, null, false,
+ new Runnable() {
+ @Override
+ public void run() {
+ getActivity().finish();
+ }
+ });
+
+ if (mAppPermissions.getPermissionGroups().isEmpty()) {
+ activity.finish();
+ return;
+ }
+
+ boolean reviewRequired = false;
+ for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
+ if (group.isReviewRequired()) {
+ reviewRequired = true;
+ break;
+ }
+ }
+
+ if (!reviewRequired) {
+ activity.finish();
+ }
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ bindUi();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mAppPermissions.refresh();
+ loadPreferences();
+ }
+
+ @Override
+ public void onClick(View view) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ if (view == mContinueButton) {
+ confirmPermissionsReview();
+ executeCallback(true);
+ } else if (view == mCancelButton) {
+ executeCallback(false);
+ activity.setResult(Activity.RESULT_CANCELED);
+ }
+ activity.finish();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mHasConfirmedRevoke) {
+ return true;
+ }
+ if (preference instanceof SwitchPreference) {
+ SwitchPreference switchPreference = (SwitchPreference) preference;
+ if (switchPreference.isChecked()) {
+ showWarnRevokeDialog(switchPreference.getKey());
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onActionConfirmed(String action) {
+ Preference preference = getPreferenceManager().findPreference(action);
+ if (preference instanceof SwitchPreference) {
+ SwitchPreference switchPreference = (SwitchPreference) preference;
+ switchPreference.setChecked(false);
+ mHasConfirmedRevoke = true;
+ }
+ }
+
+ private void showWarnRevokeDialog(final String groupName) {
+ DialogFragment fragment = ConfirmActionDialogFragment.newInstance(
+ getString(R.string.old_sdk_deny_warning), groupName);
+ fragment.show(getFragmentManager(), fragment.getClass().getName());
+ }
+
+ private void confirmPermissionsReview() {
+ PreferenceGroup preferenceGroup = mNewPermissionsCategory != null
+ ? mNewPermissionsCategory : getPreferenceScreen();
+
+ final int preferenceCount = preferenceGroup.getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ Preference preference = preferenceGroup.getPreference(i);
+ if (preference instanceof TwoStatePreference) {
+ TwoStatePreference twoStatePreference = (TwoStatePreference) preference;
+ String groupName = preference.getKey();
+ AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
+ if (twoStatePreference.isChecked()) {
+ group.grantRuntimePermissions(false);
+ } else {
+ group.revokeRuntimePermissions(false);
+ }
+ group.resetReviewRequired();
+ }
+ }
+ }
+
+ private void bindUi() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ // Set icon
+ Drawable icon = mAppPermissions.getPackageInfo().applicationInfo.loadIcon(
+ activity.getPackageManager());
+ ImageView iconView = (ImageView) activity.findViewById(R.id.app_icon);
+ iconView.setImageDrawable(icon);
+
+ // Set message
+ String appLabel = mAppPermissions.getAppLabel().toString();
+ final int labelTemplateResId = isPackageUpdated()
+ ? R.string.permission_review_title_template_update
+ : R.string.permission_review_title_template_install;
+ SpannableString message = new SpannableString(getString(labelTemplateResId, appLabel));
+ // Set the permission message as the title so it can be announced.
+ activity.setTitle(message);
+
+ // Color the app name.
+ final int appLabelStart = message.toString().indexOf(appLabel, 0);
+ final int appLabelLength = appLabel.length();
+
+ TypedValue typedValue = new TypedValue();
+ activity.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+ final int color = activity.getColor(typedValue.resourceId);
+
+ message.setSpan(new ForegroundColorSpan(color), appLabelStart,
+ appLabelStart + appLabelLength, 0);
+ TextView permissionsMessageView = (TextView) activity.findViewById(
+ R.id.permissions_message);
+ permissionsMessageView.setText(message);
+
+
+ mContinueButton = (Button) getActivity().findViewById(R.id.continue_button);
+ mContinueButton.setOnClickListener(this);
+
+ mCancelButton = (Button) getActivity().findViewById(R.id.cancel_button);
+ mCancelButton.setOnClickListener(this);
+ }
+
+ private void loadPreferences() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ screen = getPreferenceManager().createPreferenceScreen(getActivity());
+ setPreferenceScreen(screen);
+ } else {
+ screen.removeAll();
+ }
+
+ PreferenceGroup currentPermissionsCategory = null;
+ PreferenceGroup oldNewPermissionsCategory = mNewPermissionsCategory;
+ mNewPermissionsCategory = null;
+
+ final boolean isPackageUpdated = isPackageUpdated();
+
+ for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
+ if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)
+ || !Utils.OS_PKG.equals(group.getDeclaringPackage())) {
+ continue;
+ }
+
+ // TODO: Sort permissions - platform first then third-party ones
+
+ final SwitchPreference preference;
+ Preference cachedPreference = oldNewPermissionsCategory != null
+ ? oldNewPermissionsCategory.findPreference(group.getName()) : null;
+ if (cachedPreference instanceof SwitchPreference) {
+ preference = (SwitchPreference) cachedPreference;
+ } else {
+ preference = new SwitchPreference(getActivity());
+
+ preference.setKey(group.getName());
+ Drawable icon = Utils.loadDrawable(activity.getPackageManager(),
+ group.getIconPkg(), group.getIconResId());
+ preference.setIcon(Utils.applyTint(getContext(), icon,
+ android.R.attr.colorControlNormal));
+ preference.setTitle(group.getLabel());
+ preference.setSummary(group.getDescription());
+ preference.setPersistent(false);
+
+ preference.setOnPreferenceChangeListener(this);
+ }
+
+ preference.setChecked(group.areRuntimePermissionsGranted());
+
+ // Mutable state
+ if (group.isPolicyFixed()) {
+ preference.setEnabled(false);
+ preference.setSummary(getString(
+ R.string.permission_summary_enforced_by_policy));
+ } else {
+ preference.setEnabled(true);
+ }
+
+ if (group.isReviewRequired()) {
+ if (!isPackageUpdated) {
+ screen.addPreference(preference);
+ } else {
+ if (mNewPermissionsCategory == null) {
+ mNewPermissionsCategory = new PreferenceCategory(activity);
+ mNewPermissionsCategory.setTitle(R.string.new_permissions_category);
+ mNewPermissionsCategory.setOrder(1);
+ screen.addPreference(mNewPermissionsCategory);
+ }
+ mNewPermissionsCategory.addPreference(preference);
+ }
+ } else {
+ if (currentPermissionsCategory == null) {
+ currentPermissionsCategory = new PreferenceCategory(activity);
+ currentPermissionsCategory.setTitle(R.string.current_permissions_category);
+ currentPermissionsCategory.setOrder(2);
+ screen.addPreference(currentPermissionsCategory);
+ }
+ currentPermissionsCategory.addPreference(preference);
+ }
+ }
+ }
+
+ private boolean isPackageUpdated() {
+ List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
+ final int groupCount = groups.size();
+ for (int i = 0; i < groupCount; i++) {
+ AppPermissionGroup group = groups.get(i);
+ if (!group.isReviewRequired()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void executeCallback(boolean success) {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ if (success) {
+ IntentSender intent = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+ if (intent != null) {
+ try {
+ int flagMask = 0;
+ int flagValues = 0;
+ if (activity.getIntent().getBooleanExtra(
+ Intent.EXTRA_RESULT_NEEDED, false)) {
+ flagMask = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
+ flagValues = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
+ }
+ activity.startIntentSenderForResult(intent, -1, null,
+ flagMask, flagValues, 0);
+ } catch (IntentSender.SendIntentException e) {
+ /* ignore */
+ }
+ return;
+ }
+ }
+ RemoteCallback callback = activity.getIntent().getParcelableExtra(
+ Intent.EXTRA_REMOTE_CALLBACK);
+ if (callback != null) {
+ Bundle result = new Bundle();
+ result.putBoolean(Intent.EXTRA_RETURN_RESULT, success);
+ callback.sendResult(result);
+ }
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/SecureButtonView.java b/src/com/android/packageinstaller/permission/ui/SecureButtonView.java
new file mode 100644
index 00000000..624744e5
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/SecureButtonView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+package com.android.packageinstaller.permission.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.Button;
+
+/**
+ * Extension of Button that uses the hidden MotionEvent flag for partially obscured windows to
+ * prevent tapjacking attacks.
+ */
+public class SecureButtonView extends Button {
+
+ public SecureButtonView(Context context) {
+ this(context, null);
+ }
+
+ public SecureButtonView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SecureButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SecureButtonView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+ if ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0
+ || (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0) {
+ // Window is obscured, drop this touch.
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java
index b3b0895c..0c249e55 100644
--- a/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java
@@ -123,7 +123,7 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader {
}
if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
- || (perm.flags & PermissionInfo.FLAG_HIDDEN) != 0) {
+ || (perm.flags & PermissionInfo.FLAG_REMOVED) != 0) {
continue;
}
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java
index f56cba70..422fb124 100644
--- a/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java
@@ -16,7 +16,6 @@
package com.android.packageinstaller.permission.ui.handheld;
-import android.annotation.Nullable;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
@@ -49,14 +48,17 @@ import android.widget.Toast;
import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
-import com.android.packageinstaller.permission.ui.OverlayTouchActivity;
import com.android.packageinstaller.permission.utils.LocationUtils;
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
import com.android.packageinstaller.permission.utils.Utils;
+import com.android.settingslib.HelpUtils;
+import com.android.settingslib.RestrictedLockUtils;
import java.util.ArrayList;
import java.util.List;
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
public final class AppPermissionsFragment extends SettingsWithHeader
implements OnPreferenceChangeListener {
@@ -115,6 +117,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
public void onResume() {
super.onResume();
mAppPermissions.refresh();
+ loadPreferences();
setPreferencesCheckedState();
}
@@ -140,7 +143,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mAppPermissions != null) {
bindUi(this, mAppPermissions.getPackageInfo());
@@ -151,6 +154,8 @@ public final class AppPermissionsFragment extends SettingsWithHeader
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
+ HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
+ getClass().getName());
}
private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
@@ -171,20 +176,6 @@ public final class AppPermissionsFragment extends SettingsWithHeader
if (ab != null) {
ab.setTitle(R.string.app_permissions);
}
-
- ViewGroup rootView = (ViewGroup) fragment.getView();
- ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
- if (iconView != null) {
- iconView.setImageDrawable(icon);
- }
- TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
- if (titleView != null) {
- titleView.setText(R.string.app_permissions);
- }
- TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
- if (breadcrumbView != null) {
- breadcrumbView.setText(label);
- }
}
private void loadPreferences() {
@@ -216,7 +207,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
- SwitchPreference preference = new SwitchPreference(context);
+ RestrictedSwitchPreference preference = new RestrictedSwitchPreference(context);
preference.setOnPreferenceChangeListener(this);
preference.setKey(group.getName());
Drawable icon = Utils.loadDrawable(context.getPackageManager(),
@@ -225,10 +216,17 @@ public final class AppPermissionsFragment extends SettingsWithHeader
android.R.attr.colorControlNormal));
preference.setTitle(group.getLabel());
if (group.isPolicyFixed()) {
- preference.setSummary(getString(R.string.permission_summary_enforced_by_policy));
+ EnforcedAdmin admin = RestrictedLockUtils.getProfileOrDeviceOwner(getContext(),
+ group.getUserId());
+ if (admin != null) {
+ preference.setDisabledByAdmin(admin);
+ preference.setSummary(R.string.disabled_by_admin_summary_text);
+ } else {
+ preference.setSummary(R.string.permission_summary_enforced_by_policy);
+ preference.setEnabled(false);
+ }
}
preference.setPersistent(false);
- preference.setEnabled(!group.isPolicyFixed());
preference.setChecked(group.areRuntimePermissionsGranted());
if (isPlatform) {
@@ -273,12 +271,6 @@ public final class AppPermissionsFragment extends SettingsWithHeader
return false;
}
- OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
- if (activity.isObscuredTouch()) {
- activity.showOverlayDialog();
- return false;
- }
-
addToggledGroup(group);
if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
@@ -294,7 +286,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
.setMessage(grantedByDefault ? R.string.system_warning
: R.string.old_sdk_deny_warning)
.setNegativeButton(R.string.cancel, null)
- .setPositiveButton(R.string.grant_dialog_button_deny,
+ .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -385,7 +377,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
bindUi(this, getPackageInfo(getActivity(), packageName));
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java b/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java
index 2d27f069..f2b0912d 100644
--- a/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java
+++ b/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java
@@ -16,23 +16,14 @@
package com.android.packageinstaller.permission.ui.handheld;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
-import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams;
-import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.Button;
@@ -45,8 +36,6 @@ import com.android.packageinstaller.permission.ui.ButtonBarLayout;
import com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler;
import com.android.packageinstaller.permission.ui.ManualLayoutFrame;
-import java.util.ArrayList;
-
public final class GrantPermissionsViewHandlerImpl
implements GrantPermissionsViewHandler, OnClickListener {
@@ -59,14 +48,8 @@ public final class GrantPermissionsViewHandlerImpl
public static final String ARG_GROUP_DO_NOT_ASK_CHECKED = "ARG_GROUP_DO_NOT_ASK_CHECKED";
// Animation parameters.
- private static final long SIZE_START_DELAY = 300;
- private static final long SIZE_START_LENGTH = 233;
- private static final long FADE_OUT_START_DELAY = 300;
- private static final long FADE_OUT_START_LENGTH = 217;
- private static final long TRANSLATE_START_DELAY = 367;
- private static final long TRANSLATE_LENGTH = 317;
- private static final long GROUP_UPDATE_DELAY = 400;
- private static final long DO_NOT_ASK_CHECK_DELAY = 450;
+ private static final long OUT_DURATION = 200;
+ private static final long IN_DURATION = 300;
private final Context mContext;
@@ -86,22 +69,13 @@ public final class GrantPermissionsViewHandlerImpl
private CheckBox mDoNotAskCheckbox;
private Button mAllowButton;
- private ArrayList<ViewHeightController> mHeightControllers;
private ManualLayoutFrame mRootView;
// Needed for animation
private ViewGroup mDescContainer;
private ViewGroup mCurrentDesc;
- private ViewGroup mNextDesc;
-
private ViewGroup mDialogContainer;
-
- private final Runnable mUpdateGroup = new Runnable() {
- @Override
- public void run() {
- updateGroup();
- }
- };
+ private ButtonBarLayout mButtonBar;
public GrantPermissionsViewHandlerImpl(Context context) {
mContext = context;
@@ -160,171 +134,148 @@ public final class GrantPermissionsViewHandlerImpl
}
}
- private void animateToPermission() {
- if (mHeightControllers == null) {
- // We need to manually control the height of any views heigher than the root that
- // we inflate. Find all the views up to the root and create ViewHeightControllers for
- // them.
- mHeightControllers = new ArrayList<>();
- ViewRootImpl viewRoot = mRootView.getViewRootImpl();
- ViewParent v = mRootView.getParent();
- addHeightController(mDialogContainer);
- addHeightController(mRootView);
- while (v != viewRoot) {
- addHeightController((View) v);
- v = v.getParent();
- }
- // On the heighest level view, we want to setTop rather than setBottom to control the
- // height, this way the dialog will grow up rather than down.
- ViewHeightController realRootView =
- mHeightControllers.get(mHeightControllers.size() - 1);
- realRootView.setControlTop(true);
- }
-
- // Grab the current height/y positions, then wait for the layout to change,
- // so we can get the end height/y positions.
- final SparseArray<Float> startPositions = getViewPositions();
- final int startHeight = mRootView.getLayoutHeight();
- mRootView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- mRootView.removeOnLayoutChangeListener(this);
- SparseArray<Float> endPositions = getViewPositions();
- int endHeight = mRootView.getLayoutHeight();
- if (startPositions.get(R.id.do_not_ask_checkbox) == 0
- && endPositions.get(R.id.do_not_ask_checkbox) != 0) {
- // If the checkbox didn't have a position before but has one now then set
- // the start position to the end position because it just became visible.
- startPositions.put(R.id.do_not_ask_checkbox,
- endPositions.get(R.id.do_not_ask_checkbox));
- }
- animateYPos(startPositions, endPositions, endHeight - startHeight);
- }
- });
+ public void onConfigurationChanged() {
+ mRootView.onConfigurationChanged();
+ }
+ private void animateOldContent(Runnable callback) {
// Fade out old description group and scale out the icon for it.
Interpolator interpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_linear_in);
+
+ // Icon scale to zero
mIconView.animate()
.scaleX(0)
.scaleY(0)
- .setStartDelay(FADE_OUT_START_DELAY)
- .setDuration(FADE_OUT_START_LENGTH)
+ .setDuration(OUT_DURATION)
.setInterpolator(interpolator)
.start();
+
+ // Description fade out
mCurrentDesc.animate()
.alpha(0)
- .setStartDelay(FADE_OUT_START_DELAY)
- .setDuration(FADE_OUT_START_LENGTH)
+ .setDuration(OUT_DURATION)
.setInterpolator(interpolator)
- .setListener(null)
+ .withEndAction(callback)
.start();
- // Update the index of the permission after the animations have started.
- mCurrentGroupView.getHandler().postDelayed(mUpdateGroup, GROUP_UPDATE_DELAY);
+ // Checkbox fade out if needed
+ if (!mShowDonNotAsk && mDoNotAskCheckbox.getVisibility() == View.VISIBLE) {
+ mDoNotAskCheckbox.animate()
+ .alpha(0)
+ .setDuration(OUT_DURATION)
+ .setInterpolator(interpolator)
+ .start();
+ }
+ }
- // Add the new description and translate it in.
- mNextDesc = (ViewGroup) LayoutInflater.from(mContext).inflate(
+ private void attachNewContent(final Runnable callback) {
+ mCurrentDesc = (ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.permission_description, mDescContainer, false);
+ mDescContainer.removeAllViews();
+ mDescContainer.addView(mCurrentDesc);
+
+ mDialogContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ mDialogContainer.removeOnLayoutChangeListener(this);
+
+ // Prepare new content to the right to be moved in
+ final int containerWidth = mDescContainer.getWidth();
+ mCurrentDesc.setTranslationX(containerWidth);
+
+ // How much scale for the dialog to appear the same?
+ final int oldDynamicHeight = oldBottom - oldTop - mButtonBar.getHeight();
+ final float scaleY = (float) oldDynamicHeight / mDescContainer.getHeight();
+
+ // How much to translate for the dialog to appear the same?
+ final int translationCompensatingScale = (int) (scaleY
+ * mDescContainer.getHeight() - mDescContainer.getHeight()) / 2;
+ final int translationY = (oldTop - top) + translationCompensatingScale;
+
+ // Animate to the current layout
+ mDescContainer.setScaleY(scaleY);
+ mDescContainer.setTranslationY(translationY);
+ mDescContainer.animate()
+ .translationY(0)
+ .scaleY(1.0f)
+ .setInterpolator(AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.linear_out_slow_in))
+ .setDuration(IN_DURATION)
+ .withEndAction(callback)
+ .start();
+ }
+ }
+ );
- mMessageView = (TextView) mNextDesc.findViewById(R.id.permission_message);
- mIconView = (ImageView) mNextDesc.findViewById(R.id.permission_icon);
- updateDescription();
-
- int width = mDescContainer.getRootView().getWidth();
- mDescContainer.addView(mNextDesc);
- mNextDesc.setTranslationX(width);
-
- final View oldDesc = mCurrentDesc;
- // Remove the old view from the description, so that we can shrink if necessary.
- mDescContainer.removeView(oldDesc);
- oldDesc.setPadding(mDescContainer.getLeft(), mDescContainer.getTop(),
- mRootView.getRight() - mDescContainer.getRight(), 0);
- mRootView.addView(oldDesc);
+ mMessageView = (TextView) mCurrentDesc.findViewById(R.id.permission_message);
+ mIconView = (ImageView) mCurrentDesc.findViewById(R.id.permission_icon);
- mCurrentDesc = mNextDesc;
- mNextDesc.animate()
- .translationX(0)
- .setStartDelay(TRANSLATE_START_DELAY)
- .setDuration(TRANSLATE_LENGTH)
- .setInterpolator(AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.linear_out_slow_in))
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // This is the longest animation, when it finishes, we are done.
- mRootView.removeView(oldDesc);
- }
- })
- .start();
+ final boolean doNotAskWasShown = mDoNotAskCheckbox.getVisibility() == View.VISIBLE;
- boolean visibleBefore = mDoNotAskCheckbox.getVisibility() == View.VISIBLE;
+ updateDescription();
+ updateGroup();
updateDoNotAskCheckBox();
- boolean visibleAfter = mDoNotAskCheckbox.getVisibility() == View.VISIBLE;
- if (visibleBefore != visibleAfter) {
- Animation anim = AnimationUtils.loadAnimation(mContext,
- visibleAfter ? android.R.anim.fade_in : android.R.anim.fade_out);
- anim.setStartOffset(visibleAfter ? DO_NOT_ASK_CHECK_DELAY : 0);
- mDoNotAskCheckbox.startAnimation(anim);
+
+ if (!doNotAskWasShown && mShowDonNotAsk) {
+ mDoNotAskCheckbox.setAlpha(0);
}
}
- private void addHeightController(View v) {
- ViewHeightController heightController = new ViewHeightController(v);
- heightController.setHeight(v.getHeight());
- mHeightControllers.add(heightController);
- }
+ private void animateNewContent() {
+ Interpolator interpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.linear_out_slow_in);
- private SparseArray<Float> getViewPositions() {
- SparseArray<Float> locMap = new SparseArray<>();
- final int N = mDialogContainer.getChildCount();
- for (int i = 0; i < N; i++) {
- View child = mDialogContainer.getChildAt(i);
- if (child.getId() <= 0) {
- // Only track views with ids.
- continue;
- }
- locMap.put(child.getId(), child.getY());
+ // Description slide in
+ mCurrentDesc.animate()
+ .translationX(0)
+ .setDuration(IN_DURATION)
+ .setInterpolator(interpolator)
+ .start();
+
+ // Checkbox fade in if needed
+ if (mShowDonNotAsk && mDoNotAskCheckbox.getVisibility() == View.VISIBLE
+ && mDoNotAskCheckbox.getAlpha() < 1.0f) {
+ mDoNotAskCheckbox.setAlpha(0);
+ mDoNotAskCheckbox.animate()
+ .alpha(1.0f)
+ .setDuration(IN_DURATION)
+ .setInterpolator(interpolator)
+ .start();
}
- return locMap;
}
- private void animateYPos(SparseArray<Float> startPositions, SparseArray<Float> endPositions,
- int heightDiff) {
- final int N = startPositions.size();
- for (int i = 0; i < N; i++) {
- int key = startPositions.keyAt(i);
- float start = startPositions.get(key);
- float end = endPositions.get(key);
- if (start != end) {
- final View child = mDialogContainer.findViewById(key);
- child.setTranslationY(start - end);
- child.animate()
- .setStartDelay(SIZE_START_DELAY)
- .setDuration(SIZE_START_LENGTH)
- .translationY(0)
- .start();
+ private void animateToPermission() {
+ // Remove the old content
+ animateOldContent(new Runnable() {
+ @Override
+ public void run() {
+ // Add the new content
+ attachNewContent(new Runnable() {
+ @Override
+ public void run() {
+ // Animate the new content
+ animateNewContent();
+ }
+ });
}
- }
- for (int i = 0; i < mHeightControllers.size(); i++) {
- mHeightControllers.get(i).animateAddHeight(heightDiff);
- }
+ });
}
@Override
public View createView() {
mRootView = (ManualLayoutFrame) LayoutInflater.from(mContext)
.inflate(R.layout.grant_permissions, null);
- ((ButtonBarLayout) mRootView.findViewById(R.id.button_group)).setAllowStacking(true);
-
- mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container);
+ mButtonBar = (ButtonBarLayout) mRootView.findViewById(R.id.button_group);
+ mButtonBar.setAllowStacking(true);
mMessageView = (TextView) mRootView.findViewById(R.id.permission_message);
mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon);
mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text);
mDoNotAskCheckbox = (CheckBox) mRootView.findViewById(R.id.do_not_ask_checkbox);
mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button);
+ mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container);
mDescContainer = (ViewGroup) mRootView.findViewById(R.id.desc_container);
mCurrentDesc = (ViewGroup) mRootView.findViewById(R.id.perm_desc_root);
@@ -402,61 +353,4 @@ public final class GrantPermissionsViewHandlerImpl
mResultListener.onPermissionGrantResult(mGroupName, false, doNotAskAgain);
}
}
-
- /**
- * Manually controls the height of a view through getBottom/setTop. Also listens
- * for layout changes and sets the height again to be sure it doesn't change.
- */
- private static final class ViewHeightController implements OnLayoutChangeListener {
- private final View mView;
- private int mHeight;
- private int mNextHeight;
- private boolean mControlTop;
- private ObjectAnimator mAnimator;
-
- public ViewHeightController(View view) {
- mView = view;
- mView.addOnLayoutChangeListener(this);
- }
-
- public void setControlTop(boolean controlTop) {
- mControlTop = controlTop;
- }
-
- public void animateAddHeight(int heightDiff) {
- if (heightDiff != 0) {
- if (mNextHeight == 0) {
- mNextHeight = mHeight;
- }
- mNextHeight += heightDiff;
- if (mAnimator != null) {
- mAnimator.cancel();
- }
- mAnimator = ObjectAnimator.ofInt(this, "height", mHeight, mNextHeight);
- mAnimator.setStartDelay(SIZE_START_DELAY);
- mAnimator.setDuration(SIZE_START_LENGTH);
- mAnimator.start();
- }
- }
-
- public void setHeight(int height) {
- mHeight = height;
- updateHeight();
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- // Ensure that the height never changes.
- updateHeight();
- }
-
- private void updateHeight() {
- if (mControlTop) {
- mView.setTop(mView.getBottom() - mHeight);
- } else {
- mView.setBottom(mView.getTop() + mHeight);
- }
- }
- }
}
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java
index c53da879..238af36d 100644
--- a/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java
@@ -15,7 +15,6 @@
*/
package com.android.packageinstaller.permission.ui.handheld;
-import android.annotation.Nullable;
import android.app.ActionBar;
import android.app.FragmentTransaction;
import android.content.ActivityNotFoundException;
@@ -115,32 +114,15 @@ public final class ManagePermissionsFragment extends PermissionsFrameFragment
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
bindPermissionUi(getActivity(), getView());
}
- private static void bindPermissionUi(@Nullable Context context, @Nullable View rootView) {
+ private static void bindPermissionUi(Context context, View rootView) {
if (context == null || rootView == null) {
return;
}
-
- ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
- if (iconView != null) {
- // Set the icon as the background instead of the image because ImageView
- // doesn't properly scale vector drawables beyond their intrinsic size
- Drawable icon = context.getDrawable(R.drawable.ic_lock);
- icon.setTint(context.getColor(R.color.off_white));
- iconView.setBackground(icon);
- }
- TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
- if (titleView != null) {
- titleView.setText(R.string.app_permissions);
- }
- TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
- if (breadcrumbView != null) {
- breadcrumbView.setText(R.string.app_permissions_breadcrumb);
- }
}
private void updatePermissionsUi() {
@@ -260,7 +242,7 @@ public final class ManagePermissionsFragment extends PermissionsFrameFragment
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
bindPermissionUi(getActivity(), getView());
}
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java
index eee2f716..df0bdd46 100644
--- a/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java
@@ -43,14 +43,17 @@ import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.PermissionApps;
import com.android.packageinstaller.permission.model.PermissionApps.Callback;
import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
-import com.android.packageinstaller.permission.ui.OverlayTouchActivity;
import com.android.packageinstaller.permission.utils.LocationUtils;
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
import com.android.packageinstaller.permission.utils.Utils;
+import com.android.settingslib.HelpUtils;
+import com.android.settingslib.RestrictedLockUtils;
import java.util.ArrayList;
import java.util.List;
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback,
Preference.OnPreferenceChangeListener {
@@ -111,6 +114,8 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
R.string.menu_show_system);
mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
R.string.menu_hide_system);
+ HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
+ getClass().getName());
updateMenu();
}
@@ -150,22 +155,6 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
if (ab != null) {
ab.setTitle(fragment.getString(R.string.permission_title, label));
}
-
- final ViewGroup rootView = (ViewGroup) fragment.getView();
- final ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
- if (iconView != null) {
- // Set the icon as the background instead of the image because ImageView
- // doesn't properly scale vector drawables beyond their intrinsic size
- iconView.setBackground(icon);
- }
- final TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
- if (titleView != null) {
- titleView.setText(label);
- }
- final TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
- if (breadcrumbView != null) {
- breadcrumbView.setText(R.string.app_permissions);
- }
}
private void setOnPermissionsLoadedListener(Callback callback) {
@@ -219,12 +208,20 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
if (existingPref != null) {
// If existing preference - only update its state.
- if (app.isPolicyFixed()) {
- existingPref.setSummary(getString(
- R.string.permission_summary_enforced_by_policy));
+ final boolean isPolicyFixed = app.isPolicyFixed();
+ EnforcedAdmin enforcedAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
+ getActivity(), app.getUserId());
+ if (!isTelevision && (existingPref instanceof RestrictedSwitchPreference)) {
+ ((RestrictedSwitchPreference) existingPref).setDisabledByAdmin(
+ isPolicyFixed ? enforcedAdmin : null);
+ existingPref.setSummary(isPolicyFixed ?
+ getString(R.string.disabled_by_admin_summary_text) : null);
+ } else {
+ existingPref.setEnabled(!isPolicyFixed);
+ existingPref.setSummary(isPolicyFixed ?
+ getString(R.string.permission_summary_enforced_by_policy) : null);
}
existingPref.setPersistent(false);
- existingPref.setEnabled(!app.isPolicyFixed());
if (existingPref instanceof SwitchPreference) {
((SwitchPreference) existingPref)
.setChecked(app.areRuntimePermissionsGranted());
@@ -232,16 +229,23 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
continue;
}
- SwitchPreference pref = new SwitchPreference(context);
+ RestrictedSwitchPreference pref = new RestrictedSwitchPreference(context);
pref.setOnPreferenceChangeListener(this);
pref.setKey(app.getKey());
pref.setIcon(app.getIcon());
pref.setTitle(app.getLabel());
+ EnforcedAdmin enforcedAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
+ getActivity(), app.getUserId());
if (app.isPolicyFixed()) {
- pref.setSummary(getString(R.string.permission_summary_enforced_by_policy));
+ if (!isTelevision && enforcedAdmin != null) {
+ pref.setDisabledByAdmin(enforcedAdmin);
+ pref.setSummary(R.string.disabled_by_admin_summary_text);
+ } else {
+ pref.setEnabled(false);
+ pref.setSummary(R.string.permission_summary_enforced_by_policy);
+ }
}
pref.setPersistent(false);
- pref.setEnabled(!app.isPolicyFixed());
pref.setChecked(app.areRuntimePermissionsGranted());
if (isSystemApp && isTelevision) {
@@ -318,12 +322,6 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
return false;
}
- OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
- if (activity.isObscuredTouch()) {
- activity.showOverlayDialog();
- return false;
- }
-
addToggledGroup(app.getPackageName(), app.getPermissionGroup());
if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
@@ -340,7 +338,7 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
.setMessage(grantedByDefault ? R.string.system_warning
: R.string.old_sdk_deny_warning)
.setNegativeButton(R.string.cancel, null)
- .setPositiveButton(R.string.grant_dialog_button_deny,
+ .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/RestrictedSwitchPreference.java b/src/com/android/packageinstaller/permission/ui/handheld/RestrictedSwitchPreference.java
new file mode 100644
index 00000000..44a7f471
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/handheld/RestrictedSwitchPreference.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+package com.android.packageinstaller.permission.ui.handheld;
+
+import android.content.Context;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.packageinstaller.R;
+import com.android.settingslib.RestrictedLockUtils;
+
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+public class RestrictedSwitchPreference extends SwitchPreference {
+ private final Context mContext;
+ private boolean mDisabledByAdmin;
+ private EnforcedAdmin mEnforcedAdmin;
+ private final int mSwitchWidgetResId;
+
+ public RestrictedSwitchPreference(Context context) {
+ super(context);
+ mSwitchWidgetResId = getWidgetLayoutResource();
+ mContext = context;
+ }
+
+ @Override
+ public void onBindView(View view) {
+ super.onBindView(view);
+ if (mDisabledByAdmin) {
+ view.setEnabled(true);
+ }
+ if (mDisabledByAdmin) {
+ final TextView summaryView = (TextView) view.findViewById(android.R.id.summary);
+ if (summaryView != null) {
+ summaryView.setText(
+ isChecked() ? R.string.enabled_by_admin : R.string.disabled_by_admin);
+ summaryView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (enabled && mDisabledByAdmin) {
+ setDisabledByAdmin(null);
+ } else {
+ super.setEnabled(enabled);
+ }
+ }
+
+ public void setDisabledByAdmin(EnforcedAdmin admin) {
+ final boolean disabled = (admin != null ? true : false);
+ mEnforcedAdmin = admin;
+ if (mDisabledByAdmin != disabled) {
+ mDisabledByAdmin = disabled;
+ setWidgetLayoutResource(disabled ? R.layout.restricted_icon : mSwitchWidgetResId);
+ setEnabled(!disabled);
+ }
+ }
+
+ @Override
+ public void performClick(PreferenceScreen preferenceScreen) {
+ if (mDisabledByAdmin) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
+ } else {
+ super.performClick(preferenceScreen);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java b/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java
index c15a4287..d5775796 100644
--- a/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java
+++ b/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java
@@ -28,7 +28,6 @@ import android.widget.TextView;
import com.android.packageinstaller.DeviceUtils;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.permission.utils.Utils;
public abstract class SettingsWithHeader extends PermissionsFrameFragment
implements OnClickListener {
diff --git a/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java
index d4910128..0f8cb5b1 100644
--- a/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java
@@ -16,8 +16,10 @@
package com.android.packageinstaller.permission.ui.television;
+import android.Manifest;
import android.app.ActionBar;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -28,9 +30,12 @@ import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceGroup;
@@ -38,6 +43,8 @@ import android.util.Log;
import android.view.MenuItem;
import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.model.AppPermissionGroup;
+import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.utils.Utils;
import java.util.ArrayList;
@@ -50,6 +57,10 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader {
private static final String KEY_OTHER = "other_perms";
+ private PackageInfo mPackageInfo;
+
+ private AppPermissions mAppPermissions;
+
public static AllAppPermissionsFragment newInstance(String packageName) {
AllAppPermissionsFragment instance = new AllAppPermissionsFragment();
Bundle arguments = new Bundle();
@@ -67,6 +78,22 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader {
ab.setTitle(R.string.all_permissions);
ab.setDisplayHomeAsUpEnabled(true);
}
+
+ String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
+ try {
+ mPackageInfo = getActivity().getPackageManager().getPackageInfo(pkg,
+ PackageManager.GET_PERMISSIONS);
+ } catch (NameNotFoundException e) {
+ getActivity().finish();
+ }
+
+ mAppPermissions = new AppPermissions(getActivity(), mPackageInfo, null, false,
+ new Runnable() {
+ @Override
+ public void run() {
+ getActivity().finish();
+ }
+ });
}
@Override
@@ -86,62 +113,64 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader {
return super.onOptionsItemSelected(item);
}
- private void updateUi() {
- if (getPreferenceScreen() != null) {
- getPreferenceScreen().removeAll();
- }
- addPreferencesFromResource(R.xml.all_permissions);
+ private PreferenceGroup getOtherGroup() {
PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER);
+ if (otherGroup == null) {
+ otherGroup = new PreferenceCategory(getPreferenceManager().getContext());
+ otherGroup.setKey(KEY_OTHER);
+ otherGroup.setTitle(getString(R.string.other_permissions));
+ getPreferenceScreen().addPreference(otherGroup);
+ }
+ return otherGroup;
+ }
+
+ private void updateUi() {
+ getPreferenceScreen().removeAll();
+
ArrayList<Preference> prefs = new ArrayList<>(); // Used for sorting.
- prefs.add(otherGroup);
- String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
- otherGroup.removeAll();
- PackageManager pm = getContext().getPackageManager();
+ PackageManager pm = getActivity().getPackageManager();
- try {
- PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
-
- ApplicationInfo appInfo = info.applicationInfo;
- final Drawable icon = appInfo.loadIcon(pm);
- final CharSequence label = appInfo.loadLabel(pm);
- Intent infoIntent = null;
- if (!getActivity().getIntent().getBooleanExtra(
- AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) {
- infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
- .setData(Uri.fromParts("package", pkg, null));
- }
- setHeader(icon, label, infoIntent);
-
- if (info.requestedPermissions != null) {
- for (int i = 0; i < info.requestedPermissions.length; i++) {
- PermissionInfo perm;
- try {
- perm = pm.getPermissionInfo(info.requestedPermissions[i], 0);
- } catch (NameNotFoundException e) {
- Log.e(LOG_TAG,
- "Can't get permission info for " + info.requestedPermissions[i], e);
- continue;
- }
+ ApplicationInfo appInfo = mPackageInfo.applicationInfo;
+ final Drawable icon = appInfo.loadIcon(pm);
+ final CharSequence label = appInfo.loadLabel(pm);
+ Intent infoIntent = null;
+ if (!getActivity().getIntent().getBooleanExtra(
+ AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) {
+ infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(Uri.fromParts("package", mPackageInfo.packageName, null));
+ }
+ setHeader(icon, label, infoIntent, null);
- if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
- || (perm.flags & PermissionInfo.FLAG_HIDDEN) != 0) {
- continue;
- }
+ if (mPackageInfo.requestedPermissions != null) {
+ for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) {
+ PermissionInfo perm;
+ try {
+ perm = pm.getPermissionInfo(mPackageInfo.requestedPermissions[i], 0);
+ } catch (NameNotFoundException e) {
+ Log.e(LOG_TAG, "Can't get permission info for "
+ + mPackageInfo.requestedPermissions[i], e);
+ continue;
+ }
+
+ if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
+ || (perm.flags & PermissionInfo.FLAG_REMOVED) != 0) {
+ continue;
+ }
- if (perm.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) {
- PermissionGroupInfo group = getGroup(perm.group, pm);
- PreferenceGroup pref =
- findOrCreate(group != null ? group : perm, pm, prefs);
- pref.addPreference(getPreference(perm, group, pm));
- } else if (perm.protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
- PermissionGroupInfo group = getGroup(perm.group, pm);
- otherGroup.addPreference(getPreference(perm, group, pm));
+ PermissionGroupInfo group = getGroup(perm.group, pm);
+ if (perm.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) {
+ PreferenceGroup pref = findOrCreate(group != null ? group : perm, pm, prefs);
+ pref.addPreference(getPreference(perm, group));
+ } else if (perm.protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
+ PreferenceGroup otherGroup = getOtherGroup();
+ if (prefs.indexOf(otherGroup) < 0) {
+ prefs.add(otherGroup);
}
+ getOtherGroup().addPreference(getPreference(perm, group));
}
}
- } catch (NameNotFoundException e) {
- Log.e(LOG_TAG, "Problem getting package info for " + pkg, e);
}
+
// Sort an ArrayList of the groups and then set the order from the sorting.
Collections.sort(prefs, new Comparator<Preference>() {
@Override
@@ -176,7 +205,7 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader {
ArrayList<Preference> prefs) {
PreferenceGroup pref = (PreferenceGroup) findPreference(group.name);
if (pref == null) {
- pref = new PreferenceCategory(getContext());
+ pref = new PreferenceCategory(getActivity());
pref.setKey(group.name);
pref.setLayoutResource(R.layout.preference_category_material);
pref.setTitle(group.loadLabel(pm));
@@ -186,26 +215,57 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader {
return pref;
}
- private Preference getPreference(PermissionInfo perm, PermissionGroupInfo group,
- PackageManager pm) {
- Preference pref = new Preference(getContext());
- pref.setLayoutResource(R.layout.preference_permissions);
- Drawable icon = null;
- if (perm.icon != 0) {
- icon = perm.loadIcon(pm);
- } else if (group != null && group.icon != 0) {
- icon = group.loadIcon(pm);
+ private Preference getPreference(final PermissionInfo perm, final PermissionGroupInfo group) {
+ if (isMutableGranularPermission(perm.name)) {
+ return getMutablePreference(perm, group);
} else {
- icon = getContext().getDrawable(R.drawable.ic_perm_device_info);
+ return getImmutablePreference(perm, group);
}
- pref.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal));
+ }
+
+ private Preference getMutablePreference(final PermissionInfo perm, PermissionGroupInfo group) {
+ final AppPermissionGroup permGroup = mAppPermissions.getPermissionGroup(group.name);
+ final String[] filterPermissions = new String[]{perm.name};
+
+ // TODO: No hardcoded layouts
+ SwitchPreference pref = new SwitchPreference(getPreferenceManager().getContext());
+ pref.setLayoutResource(R.layout.preference_permissions);
+ pref.setChecked(permGroup.areRuntimePermissionsGranted(filterPermissions));
+ pref.setIcon(getTintedPermissionIcon(getActivity(), perm, group));
+ pref.setTitle(perm.loadLabel(getActivity().getPackageManager()));
+ pref.setPersistent(false);
+
+ pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ if (value == Boolean.TRUE) {
+ permGroup.grantRuntimePermissions(false, filterPermissions);
+ } else {
+ permGroup.revokeRuntimePermissions(false, filterPermissions);
+ }
+ return true;
+ }
+ });
+
+ return pref;
+ }
+
+ private Preference getImmutablePreference(final PermissionInfo perm,
+ PermissionGroupInfo group) {
+ final PackageManager pm = getActivity().getPackageManager();
+
+ // TODO: No hardcoded layouts
+ Preference pref = new Preference(getActivity());
+ pref.setLayoutResource(R.layout.preference_permissions);
+ pref.setIcon(getTintedPermissionIcon(getActivity(), perm, group));
pref.setTitle(perm.loadLabel(pm));
- final CharSequence desc = perm.loadDescription(pm);
+ pref.setPersistent(false);
+
pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
- new AlertDialog.Builder(getContext())
- .setMessage(desc)
+ new AlertDialog.Builder(getActivity())
+ .setMessage(perm.loadDescription(pm))
.setPositiveButton(android.R.string.ok, null)
.show();
return true;
@@ -214,4 +274,33 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader {
return pref;
}
+
+ private static Drawable getTintedPermissionIcon(Context context, PermissionInfo perm,
+ PermissionGroupInfo group) {
+ final Drawable icon;
+ if (perm.icon != 0) {
+ icon = perm.loadIcon(context.getPackageManager());
+ } else if (group != null && group.icon != 0) {
+ icon = group.loadIcon(context.getPackageManager());
+ } else {
+ icon = context.getDrawable(R.drawable.ic_perm_device_info);
+ }
+ return Utils.applyTint(context, icon, android.R.attr.colorControlNormal);
+ }
+
+ private static boolean isMutableGranularPermission(String name) {
+ if (!Build.PERMISSIONS_REVIEW_REQUIRED) {
+ return false;
+ }
+ switch (name) {
+ case Manifest.permission.READ_CONTACTS:
+ case Manifest.permission.WRITE_CONTACTS:
+ case Manifest.permission.READ_SMS:
+ case Manifest.permission.READ_CALL_LOG:
+ case Manifest.permission.CALL_PHONE: {
+ return true;
+ }
+ }
+ return false;
+ }
} \ No newline at end of file
diff --git a/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java
index 42a2661c..26467de9 100644
--- a/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java
@@ -16,7 +16,6 @@
package com.android.packageinstaller.permission.ui.television;
-import android.annotation.Nullable;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
@@ -28,6 +27,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -37,20 +37,22 @@ import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.packageinstaller.R;
+
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
-import com.android.packageinstaller.permission.ui.OverlayTouchActivity;
+import com.android.packageinstaller.permission.ui.ReviewPermissionsActivity;
import com.android.packageinstaller.permission.utils.LocationUtils;
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
import com.android.packageinstaller.permission.utils.Utils;
@@ -103,12 +105,22 @@ public final class AppPermissionsFragment extends SettingsWithHeader
return;
}
+
mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() {
@Override
public void run() {
getActivity().finish();
}
});
+
+ if (mAppPermissions.isReviewRequired()) {
+ Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ startActivity(intent);
+ getActivity().finish();
+ return;
+ }
+
loadPreferences();
}
@@ -116,6 +128,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
public void onResume() {
super.onResume();
mAppPermissions.refresh();
+ loadPreferences();
setPreferencesCheckedState();
}
@@ -141,7 +154,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mAppPermissions != null) {
bindUi(this, mAppPermissions.getPackageInfo());
@@ -166,26 +179,8 @@ public final class AppPermissionsFragment extends SettingsWithHeader
Drawable icon = appInfo.loadIcon(pm);
CharSequence label = appInfo.loadLabel(pm);
- fragment.setHeader(icon, label, infoIntent);
-
- ActionBar ab = activity.getActionBar();
- if (ab != null) {
- ab.setTitle(R.string.app_permissions);
- }
-
- ViewGroup rootView = (ViewGroup) fragment.getView();
- ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
- if (iconView != null) {
- iconView.setImageDrawable(icon);
- }
- TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
- if (titleView != null) {
- titleView.setText(R.string.app_permissions);
- }
- TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
- if (breadcrumbView != null) {
- breadcrumbView.setText(label);
- }
+ fragment.setHeader(icon, label, infoIntent, fragment.getString(
+ R.string.app_permissions_decor_title));
}
private void loadPreferences() {
@@ -196,9 +191,11 @@ public final class AppPermissionsFragment extends SettingsWithHeader
PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
+ screen.addPreference(createHeaderLineTwoPreference(context));
if (mExtraScreen != null) {
mExtraScreen.removeAll();
+ mExtraScreen = null;
}
final Preference extraPerms = new Preference(context);
@@ -232,6 +229,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
} else {
if (mExtraScreen == null) {
mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
+ mExtraScreen.addPreference(createHeaderLineTwoPreference(context));
}
mExtraScreen.addPreference(preference);
}
@@ -260,6 +258,30 @@ public final class AppPermissionsFragment extends SettingsWithHeader
setLoading(false /* loading */, true /* animate */);
}
+ /**
+ * Creates a heading below decor_title and above the rest of the preferences. This heading
+ * displays the app name and banner icon. It's used in both system and additional permissions
+ * fragments for each app. The styling used is the same as a leanback preference with a
+ * customized background color
+ * @param context The context the preferences created on
+ * @return The preference header to be inserted as the first preference in the list.
+ */
+ private Preference createHeaderLineTwoPreference(Context context) {
+ Preference headerLineTwo = new Preference(context) {
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ holder.itemView.setBackgroundColor(
+ getResources().getColor(R.color.lb_header_banner_color));
+ }
+ };
+ headerLineTwo.setKey(HEADER_PREFERENCE_KEY);
+ headerLineTwo.setSelectable(false);
+ headerLineTwo.setTitle(mLabel);
+ headerLineTwo.setIcon(mIcon);
+ return headerLineTwo;
+ }
+
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
String groupName = preference.getKey();
@@ -269,12 +291,6 @@ public final class AppPermissionsFragment extends SettingsWithHeader
return false;
}
- OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
- if (activity.isObscuredTouch()) {
- activity.showOverlayDialog();
- return false;
- }
-
addToggledGroup(group);
if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
@@ -290,7 +306,7 @@ public final class AppPermissionsFragment extends SettingsWithHeader
.setMessage(grantedByDefault ? R.string.system_warning
: R.string.old_sdk_deny_warning)
.setNegativeButton(R.string.cancel, null)
- .setPositiveButton(R.string.grant_dialog_button_deny,
+ .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -375,7 +391,6 @@ public final class AppPermissionsFragment extends SettingsWithHeader
public void onCreate(Bundle savedInstanceState) {
mOuterFragment = (AppPermissionsFragment) getTargetFragment();
super.onCreate(savedInstanceState);
- setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, mOuterFragment.mInfoIntent);
setHasOptionsMenu(true);
}
@@ -385,12 +400,28 @@ public final class AppPermissionsFragment extends SettingsWithHeader
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
bindUi(this, getPackageInfo(getActivity(), packageName));
}
+ private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
+ Activity activity = fragment.getActivity();
+ PackageManager pm = activity.getPackageManager();
+ ApplicationInfo appInfo = packageInfo.applicationInfo;
+ Intent infoIntent = null;
+ if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
+ infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(Uri.fromParts("package", packageInfo.packageName, null));
+ }
+
+ Drawable icon = appInfo.loadIcon(pm);
+ CharSequence label = appInfo.loadLabel(pm);
+ fragment.setHeader(icon, label, infoIntent, fragment.getString(
+ R.string.additional_permissions_decor_title));
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
diff --git a/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java
index 47301f48..35f866de 100644
--- a/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java
@@ -21,7 +21,6 @@ import android.app.FragmentTransaction;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
@@ -30,8 +29,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.model.PermissionApps;
@@ -42,7 +39,7 @@ import com.android.packageinstaller.permission.utils.Utils;
import java.util.List;
-public final class ManagePermissionsFragment extends PermissionsFrameFragment
+public final class ManagePermissionsFragment extends SettingsWithHeader
implements PermissionGroups.PermissionsGroupsChangeCallback, OnPreferenceClickListener {
private static final String LOG_TAG = "ManagePermissionsFragment";
@@ -115,32 +112,17 @@ public final class ManagePermissionsFragment extends PermissionsFrameFragment
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- bindPermissionUi(getActivity(), getView());
+ bindPermissionUi(this, getView());
}
- private static void bindPermissionUi(@Nullable Context context, @Nullable View rootView) {
- if (context == null || rootView == null) {
+ private static void bindPermissionUi(SettingsWithHeader fragment, @Nullable View rootView) {
+ if (fragment == null || rootView == null) {
return;
}
-
- ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
- if (iconView != null) {
- // Set the icon as the background instead of the image because ImageView
- // doesn't properly scale vector drawables beyond their intrinsic size
- Drawable icon = context.getDrawable(R.drawable.ic_lock);
- icon.setTint(context.getColor(R.color.off_white));
- iconView.setBackground(icon);
- }
- TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
- if (titleView != null) {
- titleView.setText(R.string.app_permissions);
- }
- TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
- if (breadcrumbView != null) {
- breadcrumbView.setText(R.string.app_permissions_breadcrumb);
- }
+ fragment.setHeader(null, null, null, fragment.getString(
+ R.string.manage_permissions_decor_title));
}
private void updatePermissionsUi() {
@@ -227,7 +209,7 @@ public final class ManagePermissionsFragment extends PermissionsFrameFragment
}
}
- public static class AdditionalPermissionsFragment extends PermissionsFrameFragment {
+ public static class AdditionalPermissionsFragment extends SettingsWithHeader {
@Override
public void onCreate(Bundle icicle) {
setLoading(true /* loading */, false /* animate */);
@@ -253,9 +235,17 @@ public final class ManagePermissionsFragment extends PermissionsFrameFragment
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- bindPermissionUi(getActivity(), getView());
+ bindPermissionUi(this, getView());
+ }
+
+ private static void bindPermissionUi(SettingsWithHeader fragment, @Nullable View rootView) {
+ if (fragment == null || rootView == null) {
+ return;
+ }
+ fragment.setHeader(null, null, null,
+ fragment.getString(R.string.additional_permissions_decor_title));
}
@Override
diff --git a/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java
index 0f240bef..29839c14 100644
--- a/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java
@@ -15,6 +15,7 @@
*/
package com.android.packageinstaller.permission.ui.television;
+import android.annotation.Nullable;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.Fragment;
@@ -45,7 +46,7 @@ import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.PermissionApps;
import com.android.packageinstaller.permission.model.PermissionApps.Callback;
import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
-import com.android.packageinstaller.permission.ui.OverlayTouchActivity;
+import com.android.packageinstaller.permission.ui.ReviewPermissionsActivity;
import com.android.packageinstaller.permission.utils.LocationUtils;
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
import com.android.packageinstaller.permission.utils.Utils;
@@ -53,7 +54,7 @@ import com.android.packageinstaller.permission.utils.Utils;
import java.util.ArrayList;
import java.util.List;
-public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback,
+public final class PermissionAppsFragment extends SettingsWithHeader implements Callback,
OnPreferenceChangeListener {
private static final int MENU_SHOW_SYSTEM = Menu.FIRST;
@@ -150,29 +151,12 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
bindUi(this, mPermissionApps);
}
- private static void bindUi(Fragment fragment, PermissionApps permissionApps) {
+ private static void bindUi(SettingsWithHeader fragment, PermissionApps permissionApps) {
final Drawable icon = permissionApps.getIcon();
final CharSequence label = permissionApps.getLabel();
- final ActionBar ab = fragment.getActivity().getActionBar();
- if (ab != null) {
- ab.setTitle(fragment.getString(R.string.permission_title, label));
- }
- final ViewGroup rootView = (ViewGroup) fragment.getView();
- final ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
- if (iconView != null) {
- // Set the icon as the background instead of the image because ImageView
- // doesn't properly scale vector drawables beyond their intrinsic size
- iconView.setBackground(icon);
- }
- final TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
- if (titleView != null) {
- titleView.setText(label);
- }
- final TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
- if (breadcrumbView != null) {
- breadcrumbView.setText(R.string.app_permissions);
- }
+ fragment.setHeader(null, null, null,
+ fragment.getString(R.string.permission_apps_decor_title, label));
}
private void setOnPermissionsLoadedListener(Callback callback) {
@@ -271,7 +255,8 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
@Override
public boolean onPreferenceClick(Preference preference) {
SystemAppsFragment frag = new SystemAppsFragment();
- setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME));
+ setPermissionName(frag, getArguments().getString(
+ Intent.EXTRA_PERMISSION_NAME));
frag.setTargetFragment(PermissionAppsFragment.this, 0);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, frag)
@@ -321,19 +306,21 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
return false;
}
- OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
- if (activity.isObscuredTouch()) {
- activity.showOverlayDialog();
+ if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
+ app.getPackageName())) {
+ LocationUtils.showLocationDialog(getContext(), app.getLabel());
return false;
}
addToggledGroup(app.getPackageName(), app.getPermissionGroup());
- if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
- app.getPackageName())) {
- LocationUtils.showLocationDialog(getContext(), app.getLabel());
+ if (app.isReviewRequired()) {
+ Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, app.getPackageName());
+ startActivity(intent);
return false;
}
+
if (newValue == Boolean.TRUE) {
app.grantRuntimePermissions();
} else {
@@ -343,7 +330,7 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
.setMessage(grantedByDefault ? R.string.system_warning
: R.string.old_sdk_deny_warning)
.setNegativeButton(R.string.cancel, null)
- .setPositiveButton(R.string.grant_dialog_button_deny,
+ .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -394,7 +381,7 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
}
}
- public static class SystemAppsFragment extends PermissionsFrameFragment implements Callback {
+ public static class SystemAppsFragment extends SettingsWithHeader implements Callback {
PermissionAppsFragment mOuterFragment;
@Override
@@ -421,6 +408,14 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
bindUi(this, permissionApps);
}
+
+ private static void bindUi(SettingsWithHeader fragment, PermissionApps permissionApps) {
+ final CharSequence label = permissionApps.getLabel();
+ fragment.setHeader(null, null, null,
+ fragment.getString(R.string.system_apps_decor_title, label));
+ }
+
+
@Override
public void onPermissionsLoaded(PermissionApps permissionApps) {
setPreferenceScreen();
diff --git a/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java b/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java
index e81aee86..6119f98c 100644
--- a/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java
@@ -22,7 +22,6 @@ import android.support.v14.preference.PreferenceFragment;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.preference.PreferenceScreen;
import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.AdapterDataObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,19 +29,19 @@ import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
-
import com.android.packageinstaller.DeviceUtils;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.permission.utils.Utils;
public abstract class PermissionsFrameFragment extends PreferenceFragment {
- private static final float WINDOW_ALIGNMENT_OFFSET_PERCENT = 50;
+ // Key identifying the preference used on TV as the extra header in a permission fragment.
+ // This is to distinguish it from the rest of the preferences
+ protected static final String HEADER_PREFERENCE_KEY = "HeaderPreferenceKey";
private ViewGroup mPreferencesContainer;
- // TV-specific instance variables
- @Nullable private VerticalGridView mGridView;
+ // TV-specific instance variable
+ @Nullable private RecyclerView mGridView;
private View mLoadingView;
private ViewGroup mPrefsView;
@@ -74,7 +73,7 @@ public abstract class PermissionsFrameFragment extends PreferenceFragment {
}
@Override
- public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
PreferenceScreen preferences = getPreferenceScreen();
if (preferences == null) {
preferences = getPreferenceManager().createPreferenceScreen(getActivity());
@@ -133,18 +132,13 @@ public abstract class PermissionsFrameFragment extends PreferenceFragment {
@Override
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
- Bundle savedInstanceState) {
- if (DeviceUtils.isTelevision(getContext())) {
- mGridView = (VerticalGridView) inflater.inflate(
- R.layout.leanback_preferences_list, parent, false);
- mGridView.setWindowAlignmentOffset(0);
- mGridView.setWindowAlignmentOffsetPercent(WINDOW_ALIGNMENT_OFFSET_PERCENT);
- mGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
- mGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED);
- return mGridView;
- } else {
- return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
- }
+ Bundle savedInstanceState) {
+ VerticalGridView verticalGridView = (VerticalGridView) inflater.inflate(
+ R.layout.leanback_preferences_list, parent, false);
+ verticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
+ verticalGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED);
+ mGridView = verticalGridView;
+ return mGridView;
}
@Override
@@ -155,7 +149,7 @@ public abstract class PermissionsFrameFragment extends PreferenceFragment {
final TextView emptyView = (TextView) getView().findViewById(R.id.no_permissions);
onSetEmptyText(emptyView);
final RecyclerView recyclerView = getListView();
- adapter.registerAdapterDataObserver(new AdapterDataObserver() {
+ adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
checkEmpty();
@@ -172,18 +166,20 @@ public abstract class PermissionsFrameFragment extends PreferenceFragment {
}
private void checkEmpty() {
- boolean isEmpty = adapter.getItemCount() == 0;
+ boolean isEmpty = isPreferenceListEmpty();
emptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
- recyclerView.setVisibility(isEmpty ? View.GONE : View.VISIBLE);
+ recyclerView.setVisibility(isEmpty && adapter.getItemCount() == 0 ?
+ View.GONE : View.VISIBLE);
if (!isEmpty && mGridView != null) {
mGridView.requestFocus();
}
}
});
- boolean isEmpty = adapter.getItemCount() == 0;
+ boolean isEmpty = isPreferenceListEmpty();
emptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
- recyclerView.setVisibility(isEmpty ? View.GONE : View.VISIBLE);
+ recyclerView.setVisibility(isEmpty && adapter.getItemCount() == 0 ?
+ View.GONE : View.VISIBLE);
if (!isEmpty && mGridView != null) {
mGridView.requestFocus();
}
@@ -192,6 +188,13 @@ public abstract class PermissionsFrameFragment extends PreferenceFragment {
return adapter;
}
+ private boolean isPreferenceListEmpty() {
+ PreferenceScreen screen = getPreferenceScreen();
+ return screen.getPreferenceCount() == 0 || (
+ screen.getPreferenceCount() == 1 &&
+ (screen.findPreference(HEADER_PREFERENCE_KEY) != null));
+ }
+
/**
* Hook for subclasses to change the default text of the empty view.
* Base implementation leaves the default empty view text.
@@ -201,4 +204,3 @@ public abstract class PermissionsFrameFragment extends PreferenceFragment {
protected void onSetEmptyText(TextView textView) {
}
}
-
diff --git a/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java b/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java
index 4dae629c..06f7c142 100644
--- a/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java
+++ b/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java
@@ -25,10 +25,8 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
-
import com.android.packageinstaller.DeviceUtils;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.permission.utils.Utils;
public abstract class SettingsWithHeader extends PermissionsFrameFragment
implements OnClickListener {
@@ -37,50 +35,40 @@ public abstract class SettingsWithHeader extends PermissionsFrameFragment
protected Intent mInfoIntent;
protected Drawable mIcon;
protected CharSequence mLabel;
+ protected CharSequence mDecorTitle;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
- if (!DeviceUtils.isTelevision(getContext())) {
- mHeader = inflater.inflate(R.layout.header, root, false);
- getPreferencesContainer().addView(mHeader, 0);
- updateHeader();
- }
+ mHeader = inflater.inflate(R.layout.header, root, false);
+ getPreferencesContainer().addView(mHeader, 0);
+ updateHeader();
return root;
}
- public void setHeader(Drawable icon, CharSequence label, Intent infoIntent) {
+ public void setHeader(Drawable icon, CharSequence label, Intent infoIntent,
+ CharSequence decorTitle) {
mIcon = icon;
mLabel = label;
mInfoIntent = infoIntent;
+ mDecorTitle = decorTitle;
updateHeader();
}
- private void updateHeader() {
- if (mHeader != null) {
- final ImageView appIcon = (ImageView) mHeader.findViewById(R.id.icon);
- appIcon.setImageDrawable(mIcon);
-
- final TextView appName = (TextView) mHeader.findViewById(R.id.name);
- appName.setText(mLabel);
+ public View getHeader() {
+ return mHeader;
+ }
- final View info = mHeader.findViewById(R.id.info);
- if (mInfoIntent == null) {
- info.setVisibility(View.GONE);
- } else {
- info.setVisibility(View.VISIBLE);
- info.setClickable(true);
- info.setOnClickListener(this);
- }
- }
+ protected void updateHeader() {
+ final TextView decorTitle = (TextView) mHeader.findViewById(R.id.decor_title);
+ decorTitle.setText(mDecorTitle);
}
@Override
public void onClick(View v) {
getActivity().startActivity(mInfoIntent);
}
-
}
diff --git a/src/com/android/packageinstaller/permission/ui/wear/AppPermissionsFragmentWear.java b/src/com/android/packageinstaller/permission/ui/wear/AppPermissionsFragmentWear.java
index aba97fc8..db1c94d8 100644
--- a/src/com/android/packageinstaller/permission/ui/wear/AppPermissionsFragmentWear.java
+++ b/src/com/android/packageinstaller/permission/ui/wear/AppPermissionsFragmentWear.java
@@ -17,11 +17,9 @@
package com.android.packageinstaller.permission.ui.wear;
import android.Manifest;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
@@ -35,7 +33,6 @@ import android.widget.Toast;
import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
-import com.android.packageinstaller.permission.ui.OverlayTouchActivity;
import com.android.packageinstaller.permission.ui.wear.settings.PermissionsSettingsAdapter;
import com.android.packageinstaller.permission.ui.wear.settings.SettingsAdapter;
import com.android.packageinstaller.permission.utils.LocationUtils;
@@ -120,22 +117,22 @@ public final class AppPermissionsFragmentWear extends TitledSettingsFragment {
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mAppPermissions != null) {
initializeLayout(mAdapter);
- bindHeader(mAppPermissions.getPackageInfo());
+ mHeader.setText(R.string.app_permissions);
+ mDetails.setText(R.string.no_permissions);
+ if (mAdapter.getItemCount() == 0) {
+ mDetails.setVisibility(View.VISIBLE);
+ mWheel.setVisibility(View.GONE);
+ } else {
+ mDetails.setVisibility(View.GONE);
+ mWheel.setVisibility(View.VISIBLE);
+ }
}
}
- private void bindHeader(PackageInfo packageInfo) {
- Activity activity = getActivity();
- PackageManager pm = activity.getPackageManager();
- ApplicationInfo appInfo = packageInfo.applicationInfo;
- CharSequence label = appInfo.loadLabel(pm);
- mHeader.setText(label);
- }
-
private void initializePermissionGroupList() {
final String packageName = mAppPermissions.getPackageInfo().packageName;
List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
@@ -197,12 +194,6 @@ public final class AppPermissionsFragmentWear extends TitledSettingsFragment {
return;
}
- OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
- if (activity.isObscuredTouch()) {
- activity.showOverlayDialog();
- return;
- }
-
addToggledGroup(group);
if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
diff --git a/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java b/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java
index ef7efb28..b673a498 100644
--- a/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java
@@ -28,7 +28,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.packageinstaller.permission.ui.wear.settings.ViewUtils;
@@ -46,6 +46,7 @@ public abstract class TitledSettingsFragment extends Fragment implements
private int mInitialHeaderHeight;
protected TextView mHeader;
+ protected TextView mDetails;
protected WearableListView mWheel;
private int mCharLimitShortTitle;
@@ -114,6 +115,9 @@ public abstract class TitledSettingsFragment extends Fragment implements
mHeader.addOnLayoutChangeListener(this);
mHeader.addTextChangedListener(mHeaderTextWatcher);
+ mDetails = (TextView) v.findViewById(R.id.details);
+ mDetails.addOnLayoutChangeListener(this);
+
mWheel.setAdapter(adapter);
mWheel.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
@@ -134,13 +138,14 @@ public abstract class TitledSettingsFragment extends Fragment implements
adjustHeaderSize();
- positionOnCircular(getContext(), mHeader, mWheel);
+ positionOnCircular(getContext(), mHeader, mDetails, mWheel);
}
- public void positionOnCircular(Context context, View header, final ViewGroup wheel) {
+ public void positionOnCircular(Context context, View header, View details,
+ final ViewGroup wheel) {
if (ViewUtils.getIsCircular(context)) {
- FrameLayout.LayoutParams params =
- (FrameLayout.LayoutParams) header.getLayoutParams();
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) header.getLayoutParams();
params.topMargin = (int) context.getResources().getDimension(
R.dimen.settings_header_top_margin_circular);
// Note that the margins are made symmetrical here. Since they're symmetrical we choose
@@ -152,10 +157,14 @@ public abstract class TitledSettingsFragment extends Fragment implements
params.rightMargin = margin;
params.gravity = Gravity.CENTER_HORIZONTAL;
header.setLayoutParams(params);
+ details.setLayoutParams(params);
if (header instanceof TextView) {
((TextView) header).setGravity(Gravity.CENTER);
}
+ if (details instanceof TextView) {
+ ((TextView) details).setGravity(Gravity.CENTER);
+ }
final int leftPadding = (int) context.getResources().getDimension(
R.dimen.round_content_padding_left);
@@ -209,7 +218,7 @@ public abstract class TitledSettingsFragment extends Fragment implements
}
mHeader.setMinHeight((int) height);
- FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mHeader.getLayoutParams();
+ LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mHeader.getLayoutParams();
final Context context = getContext();
if (!singleLine) {
// Make the top margin a little bit smaller so there is more space for the title.
diff --git a/src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java b/src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java
index 03713419..0800c14c 100644
--- a/src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java
+++ b/src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java
@@ -66,7 +66,7 @@ public final class WarningConfirmationActivity extends Activity {
@Override
public CharSequence getVerticalButton2Text() {
- return getString(R.string.grant_dialog_button_deny);
+ return getString(R.string.grant_dialog_button_deny_anyway);
}
@Override
diff --git a/src/com/android/packageinstaller/permission/utils/ArrayUtils.java b/src/com/android/packageinstaller/permission/utils/ArrayUtils.java
new file mode 100644
index 00000000..4b7a3947
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/utils/ArrayUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+package com.android.packageinstaller.permission.utils;
+
+import java.util.Objects;
+
+public final class ArrayUtils {
+ private ArrayUtils() { /* cannot be instantiated */ }
+
+ /**
+ * Checks that value is present as at least one of the elements of the array.
+ * @param array the array to check in
+ * @param value the value to check for
+ * @return true if the value is present in the array
+ */
+ public static <T> boolean contains(T[] array, T value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ public static <T> int indexOf(T[] array, T value) {
+ if (array == null) return -1;
+ for (int i = 0; i < array.length; i++) {
+ if (Objects.equals(array[i], value)) return i;
+ }
+ return -1;
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/utils/IoUtils.java b/src/com/android/packageinstaller/permission/utils/IoUtils.java
new file mode 100644
index 00000000..ff7d1831
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/utils/IoUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+package com.android.packageinstaller.permission.utils;
+
+public final class IoUtils {
+ private IoUtils() {
+ }
+
+ /**
+ * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
+ */
+ public static void closeQuietly(AutoCloseable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/utils/Utils.java b/src/com/android/packageinstaller/permission/utils/Utils.java
index 21830378..22663e13 100644
--- a/src/com/android/packageinstaller/permission/utils/Utils.java
+++ b/src/com/android/packageinstaller/permission/utils/Utils.java
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
@@ -36,7 +35,7 @@ import com.android.packageinstaller.permission.model.PermissionApps.PermissionAp
import java.util.List;
-public class Utils {
+public final class Utils {
private static final String LOG_TAG = "Utils";
diff --git a/src/com/android/packageinstaller/wear/WearPackageArgs.java b/src/com/android/packageinstaller/wear/WearPackageArgs.java
index 67051da0..da49192a 100644
--- a/src/com/android/packageinstaller/wear/WearPackageArgs.java
+++ b/src/com/android/packageinstaller/wear/WearPackageArgs.java
@@ -25,6 +25,8 @@ import android.os.Bundle;
* installing/uninstalling.
*/
public class WearPackageArgs {
+ private static final String KEY_PACKAGE_NAME =
+ "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
private static final String KEY_ASSET_URI =
"com.google.android.clockwork.EXTRA_ASSET_URI";
private static final String KEY_START_ID =
@@ -45,16 +47,16 @@ public class WearPackageArgs {
"com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
public static String getPackageName(Bundle b) {
- return b.getString(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ return b.getString(KEY_PACKAGE_NAME);
}
- public static Uri getAssetUri(Bundle b) {
- return b.getParcelable(KEY_ASSET_URI);
+ public static Bundle setPackageName(Bundle b, String packageName) {
+ b.putString(KEY_PACKAGE_NAME, packageName);
+ return b;
}
- public static Bundle setAssetUri(Bundle b, Uri assetUri) {
- b.putParcelable(KEY_ASSET_URI, assetUri);
- return b;
+ public static Uri getAssetUri(Bundle b) {
+ return b.getParcelable(KEY_ASSET_URI);
}
public static Uri getPermUri(Bundle b) {
diff --git a/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
index 3874c0a4..7387ed2a 100644
--- a/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
+++ b/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -66,13 +66,18 @@ import java.util.Set;
*
* Install Action example:
* adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
- * -t vnd.android.cursor.item/wearable_apk \
- * -d content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
+ * -d package://com.google.android.gms \
+ * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
* --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
* --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
* --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
* com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
*
+ * Uninstall Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
* Retry GMS:
* adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
* com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
@@ -141,29 +146,49 @@ public class WearPackageInstallerService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!DeviceUtils.isWear(this)) {
- Log.w(TAG, "Not running on wearable");
+ Log.w(TAG, "Not running on wearable.");
+ return START_NOT_STICKY;
+ }
+
+ if (intent == null) {
+ Log.w(TAG, "Got null intent.");
return START_NOT_STICKY;
}
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Got install/uninstall request " + intent);
+ }
+
+ Uri packageUri = intent.getData();
+ if (packageUri == null) {
+ Log.e(TAG, "No package URI in intent");
+ return START_NOT_STICKY;
+ }
+ final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
+ if (packageName == null) {
+ Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
+ return START_NOT_STICKY;
+ }
+
PowerManager.WakeLock lock = getLock(this.getApplicationContext());
if (!lock.isHeld()) {
lock.acquire();
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Got install/uninstall request " + intent);
+
+ Bundle intentBundle = intent.getExtras();
+ if (intentBundle == null) {
+ intentBundle = new Bundle();
}
- if (intent != null) {
- Bundle intentBundle = intent.getExtras();
- WearPackageArgs.setStartId(intentBundle, startId);
- if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
- final Message msg = mServiceHandler.obtainMessage(START_INSTALL);
- WearPackageArgs.setAssetUri(intentBundle, intent.getData());
- msg.setData(intentBundle);
- mServiceHandler.sendMessage(msg);
- } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
- Message msg = mServiceHandler.obtainMessage(START_UNINSTALL);
- msg.setData(intentBundle);
- mServiceHandler.sendMessage(msg);
- }
+ WearPackageArgs.setStartId(intentBundle, startId);
+ WearPackageArgs.setPackageName(intentBundle, packageName);
+ if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
+ Message msg = mServiceHandler.obtainMessage(START_INSTALL);
+ msg.setData(intentBundle);
+ mServiceHandler.sendMessage(msg);
+ } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
+ Message msg = mServiceHandler.obtainMessage(START_UNINSTALL);
+ msg.setData(intentBundle);
+ mServiceHandler.sendMessage(msg);
}
return START_NOT_STICKY;
}
diff --git a/src/com/android/packageinstaller/wear/WearPackageUtil.java b/src/com/android/packageinstaller/wear/WearPackageUtil.java
index 688d6167..e340d627 100644
--- a/src/com/android/packageinstaller/wear/WearPackageUtil.java
+++ b/src/com/android/packageinstaller/wear/WearPackageUtil.java
@@ -16,11 +16,11 @@
package com.android.packageinstaller.wear;
-import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageParser;
+import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
@@ -86,7 +86,7 @@ public class WearPackageUtil {
* decompress it here
*/
public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
- String packageName, @Nullable String compressionAlg) {
+ String packageName, String compressionAlg) {
File newFile = getTemporaryFile(context, packageName);
if (fd == null || fd.getFileDescriptor() == null) {
return null;
@@ -164,4 +164,16 @@ public class WearPackageUtil {
+ " for " + wearablePackageName);
context.startService(newIntent);
}
+
+ /**
+ * @return com.google.com from expected formats like
+ * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
+ */
+ public static String getSanitizedPackageName(Uri packageUri) {
+ String packageName = packageUri.getEncodedSchemeSpecificPart();
+ if (packageName != null) {
+ return packageName.replaceAll("^/+", "");
+ }
+ return packageName;
+ }
}