diff options
Diffstat (limited to 'src/com/android')
7 files changed, 269 insertions, 106 deletions
diff --git a/src/com/android/packageinstaller/InstallAppProgress.java b/src/com/android/packageinstaller/InstallAppProgress.java index d51cab1d..f0362eb2 100755 --- a/src/com/android/packageinstaller/InstallAppProgress.java +++ b/src/com/android/packageinstaller/InstallAppProgress.java @@ -16,16 +16,21 @@ */ 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.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; @@ -34,6 +39,7 @@ 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; @@ -41,7 +47,13 @@ import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; +import libcore.io.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,11 +66,14 @@ 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; @@ -72,6 +87,8 @@ 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) { @@ -81,7 +98,7 @@ public class InstallAppProgress extends Activity implements View.OnClickListener 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(); @@ -94,7 +111,7 @@ public class InstallAppProgress extends Activity implements View.OnClickListener 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); centerTextLabel = R.string.install_done; @@ -114,7 +131,7 @@ 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 { @@ -146,18 +163,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; } @@ -179,9 +209,19 @@ public class InstallAppProgress extends Activity implements View.OnClickListener 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(); } + @SuppressWarnings("deprecation") @Override public Dialog onCreateDialog(int id, Bundle bundle) { switch (id) { @@ -210,36 +250,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)); @@ -255,40 +344,41 @@ public class InstallAppProgress extends Activity implements View.OnClickListener 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); + params.setInstallFlagsForcePermissionPrompt(); + + 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) { diff --git a/src/com/android/packageinstaller/InstallFlowAnalytics.java b/src/com/android/packageinstaller/InstallFlowAnalytics.java index 4591f31c..6477ceb6 100644 --- a/src/com/android/packageinstaller/InstallFlowAnalytics.java +++ b/src/com/android/packageinstaller/InstallFlowAnalytics.java @@ -16,6 +16,8 @@ */ package com.android.packageinstaller; +import com.android.internal.util.HexDump; + import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; @@ -469,7 +471,7 @@ public class InstallFlowAnalytics implements Parcelable { Log.w(TAG, "Failed to hash APK contents", e); } finally { String digestHex = (digest != null) - ? IntegralToString.bytesToHexString(digest, false) + ? HexDump.toHexString(digest, false) : ""; EventLogTags.writeInstallPackageAttempt( resultAndFlags, diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java index 3693470b..621da358 100644 --- a/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -447,7 +447,13 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen mInstallFlowAnalytics.setSystemApp( (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)); - startInstallConfirm(); + // If we have a session id, we're invoked to verify the permissions for the given + // package. Otherwise, we start the install process. + if (mSessionId != -1) { + startInstallConfirm(); + } else { + startInstall(); + } } void setPmResult(int pmResult) { @@ -697,7 +703,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen } else { mScrollView.pageScroll(View.FOCUS_DOWN); } - } else if(v == mCancel) { + } else if (v == mCancel) { // Cancel and finish setResult(RESULT_CANCELED); if (mSessionId != -1) { diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java index bb4dde7e..ffa8bf35 100644 --- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java +++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java @@ -107,7 +107,7 @@ public class GrantPermissionsActivity extends OverlayTouchActivity return; } - mAppPermissions = new AppPermissions(this, callingPackageInfo, mRequestedPermissions, false, + mAppPermissions = new AppPermissions(this, callingPackageInfo, null, false, new Runnable() { @Override public void run() { @@ -116,6 +116,16 @@ public class GrantPermissionsActivity extends OverlayTouchActivity }); for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { + boolean groupHasRequestedPermission = false; + for (String requestedPermission : mRequestedPermissions) { + if (group.hasPermission(requestedPermission)) { + groupHasRequestedPermission = true; + break; + } + } + if (!groupHasRequestedPermission) { + continue; + } // We allow the user to choose only non-fixed permissions. A permission // is fixed either by device policy or the user denying with prejudice. if (!group.isUserFixed() && !group.isPolicyFixed()) { @@ -135,7 +145,13 @@ public class GrantPermissionsActivity extends OverlayTouchActivity } break; default: { - mRequestGrantPermissionGroups.put(group.getName(), new GroupState(group)); + if (!group.areRuntimePermissionsGranted()) { + mRequestGrantPermissionGroups.put(group.getName(), + new GroupState(group)); + } else { + group.grantRuntimePermissions(false); + updateGrantResults(group); + } } break; } } else { diff --git a/src/com/android/packageinstaller/permission/ui/wear/ConfirmationViewHandler.java b/src/com/android/packageinstaller/permission/ui/wear/ConfirmationViewHandler.java index db3340f6..954d7e96 100644 --- a/src/com/android/packageinstaller/permission/ui/wear/ConfirmationViewHandler.java +++ b/src/com/android/packageinstaller/permission/ui/wear/ConfirmationViewHandler.java @@ -6,6 +6,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.text.TextUtils; import android.util.Log; @@ -106,7 +107,7 @@ public abstract class ConfirmationViewHandler implements android.R.interpolator.fast_out_slow_in); mButtonBarFloatingHeight = mContext.getResources().getDimension( R.dimen.conf_diag_floating_height); - mHideHandler = new Handler(this); + mHideHandler = new Handler(Looper.getMainLooper(), this); mScrollingContainer.getViewTreeObserver().addOnScrollChangedListener(this); mRoot.getViewTreeObserver().addOnGlobalLayoutListener(this); @@ -189,7 +190,8 @@ public abstract class ConfirmationViewHandler implements // In order to fake the buttons peeking at the bottom, need to do set the // padding properly. if (mContent.getPaddingBottom() != mButtonBarContainer.getHeight()) { - mContent.setPadding(0, 0, 0, mButtonBarContainer.getHeight()); + mContent.setPadding(mContent.getPaddingLeft(), mContent.getPaddingTop(), + mContent.getPaddingRight(), mButtonBarContainer.getHeight()); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, " set mContent.PaddingBottom: " + mButtonBarContainer.getHeight()); } @@ -236,6 +238,9 @@ public abstract class ConfirmationViewHandler implements @Override public void onScrollChanged () { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onScrollChanged"); + } mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR); hideButtonBar(); } @@ -257,12 +262,13 @@ public abstract class ConfirmationViewHandler implements // Evaluate the max height the button bar can go final int screenHeight = mRoot.getHeight(); + final int halfScreenHeight = screenHeight / 2; final int buttonBarHeight = mButtonBarContainer.getHeight(); + final int contentHeight = mContent.getHeight() - buttonBarHeight; final int buttonBarMaxHeight = - Math.min(buttonBarHeight, screenHeight / 2); + Math.min(buttonBarHeight, halfScreenHeight); if (Log.isLoggable(TAG, Log.DEBUG)) { - final int contentHeight = mContent.getHeight() - buttonBarHeight; Log.d(TAG, " screenHeight: " + screenHeight); Log.d(TAG, " contentHeight: " + contentHeight); Log.d(TAG, " buttonBarHeight: " + buttonBarHeight); @@ -270,7 +276,13 @@ public abstract class ConfirmationViewHandler implements } mButtonBarContainer.setTranslationZ(mButtonBarFloatingHeight); - mHideHandler.sendEmptyMessageDelayed(MSG_HIDE_BUTTON_BAR, 3000); + + // Only hide the button bar if it is occluding the content or the button bar is bigger than + // half the screen + if (contentHeight > halfScreenHeight + || buttonBarHeight > halfScreenHeight) { + mHideHandler.sendEmptyMessageDelayed(MSG_HIDE_BUTTON_BAR, 3000); + } generateButtonBarAnimator(buttonBarHeight, buttonBarHeight - buttonBarMaxHeight, 0, mButtonBarFloatingHeight, 1000); @@ -294,7 +306,10 @@ public abstract class ConfirmationViewHandler implements mButtonBarContainer.getHeight() - offset : mButtonBarContainer.getHeight()); if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, " topMargin: " + topMargin); Log.d(TAG, " contentHeight: " + contentHeight); + Log.d(TAG, " screenHeight: " + screenHeight); + Log.d(TAG, " offset: " + offset); Log.d(TAG, " buttonBarHeight: " + buttonBarHeight); Log.d(TAG, " mContent.getPaddingBottom(): " + mContent.getPaddingBottom()); Log.d(TAG, " mScrollingContainer.getScrollY(): " + mScrollingContainer.getScrollY()); @@ -302,6 +317,14 @@ public abstract class ConfirmationViewHandler implements } if (!mHiddenBefore || mButtonBarAnimator == null) { + // Remove previous call to MSG_SHOW_BUTTON_BAR if the user scrolled or something before + // the animation got a chance to play + mHideHandler.removeMessages(MSG_SHOW_BUTTON_BAR); + + if(mButtonBarAnimator != null) { + mButtonBarAnimator.cancel(); // stop current animation if there is one playing + } + // hasn't hidden the bar yet, just hide now to the right height generateButtonBarAnimator( mButtonBarContainer.getTranslationY(), translationY, @@ -316,6 +339,7 @@ public abstract class ConfirmationViewHandler implements (float) HIDE_ANIM_DURATION * (translationY - mButtonBarContainer.getTranslationY()) / mButtonBarContainer.getHeight()), 0); + generateButtonBarAnimator( mButtonBarContainer.getTranslationY(), translationY, mButtonBarFloatingHeight, 0, duration); @@ -335,6 +359,15 @@ public abstract class ConfirmationViewHandler implements private void generateButtonBarAnimator( float startY, float endY, float startZ, float endZ, long duration) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "generateButtonBarAnimator"); + Log.d(TAG, " startY: " + startY); + Log.d(TAG, " endY: " + endY); + Log.d(TAG, " startZ: " + startZ); + Log.d(TAG, " endZ: " + endZ); + Log.d(TAG, " duration: " + duration); + } + mButtonBarAnimator = ObjectAnimator.ofPropertyValuesHolder( mButtonBarContainer, diff --git a/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java b/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java index 64f42d0d..ef7efb28 100644 --- a/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java @@ -207,9 +207,9 @@ public abstract class TitledSettingsFragment extends Fragment implements if (!singleLine) { height += getResources().getDimension(R.dimen.setting_header_extra_line_height); } + mHeader.setMinHeight((int) height); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mHeader.getLayoutParams(); - params.height = (int) height; 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/wear/WearPackageInstallerService.java b/src/com/android/packageinstaller/wear/WearPackageInstallerService.java index 5ce0b9a1..ba83ea28 100644 --- a/src/com/android/packageinstaller/wear/WearPackageInstallerService.java +++ b/src/com/android/packageinstaller/wear/WearPackageInstallerService.java @@ -362,18 +362,76 @@ public class WearPackageInstallerService extends Service { private boolean checkPermissions(PackageParser.Package pkg, int companionSdkVersion, int companionDeviceVersion, Uri permUri, List<String> wearablePermissions, File apkFile) { + // If the Wear App is targeted for M-release, since the permission model has been changed, + // permissions may not be granted on the phone yet. We need a different flow for user to + // accept these permissions. + // + // Assumption: Code is running on E-release, so Wear is always running M. + // - Case 1: If the Wear App(WA) is targeting 23, always choose the M model (4 cases) + // - Case 2: Else if the Phone App(PA) is targeting 23 and Phone App(P) is running on M, + // show a Dialog so that the user can accept all perms (1 case) + // - Also show a warning to the developer if the watch is targeting M + // - Case 3: If Case 2 is false, then the behavior on the phone is pre-M. Stick to pre-M + // behavior on watch (as long as we don't hit case 1). + // - 3a: WA(22) PA(22) P(22) -> watch app is not targeting 23 + // - 3b: WA(22) PA(22) P(23) -> watch app is not targeting 23 + // - 3c: WA(22) PA(23) P(22) -> watch app is not targeting 23 + // - Case 4: We did not get Companion App's/Device's version, always show dialog to user to + // accept permissions. (This happens if the AndroidWear Companion App is really old). + boolean isWearTargetingM = + pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; + if (isWearTargetingM) { // Case 1 + // Install the app if Wear App is ready for the new perms model. + return true; + } + + List<String> unavailableWearablePerms = getWearPermsNotGrantedOnPhone(pkg.packageName, + permUri, wearablePermissions); + if (unavailableWearablePerms == null) { + return false; + } + + if (unavailableWearablePerms.size() == 0) { + // All permissions requested by the watch are already granted on the phone, no need + // to do anything. + return true; + } + + // Cases 2 and 4. + boolean isCompanionTargetingM = companionSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; + boolean isCompanionRunningM = companionDeviceVersion > Build.VERSION_CODES.LOLLIPOP_MR1; + if (isCompanionTargetingM) { // Case 2 Warning + Log.w(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if " + + "phone app is targeting at least 23, will continue."); + } + if ((isCompanionTargetingM && isCompanionRunningM) || // Case 2 + companionSdkVersion == 0 || companionDeviceVersion == 0) { // Case 4 + startPermsServiceForInstall(pkg, apkFile, unavailableWearablePerms); + } + + // Case 3a-3c. + return false; + } + + /** + * Given a {@string packageName} corresponding to a phone app, query the provider for all the + * perms that are granted. + * @return null if there is an error retrieving this info + * else, a list of all the wearable perms that are not in the list of granted perms of + * the phone. + */ + private List<String> getWearPermsNotGrantedOnPhone(String packageName, Uri permUri, + List<String> wearablePermissions) { if (permUri == null) { Log.e(TAG, "Permission URI is null"); - return false; + return null; } Cursor permCursor = getContentResolver().query(permUri, null, null, null, null); if (permCursor == null) { Log.e(TAG, "Could not get the cursor for the permissions"); - return false; + return null; } - final String packageName = pkg.packageName; - Set<String> grantedPerms = new HashSet<>(); Set<String> ungrantedPerms = new HashSet<>(); while(permCursor.moveToNext()) { @@ -408,49 +466,7 @@ public class WearPackageInstallerService extends Service { } } } - - - // If the Wear App is targeted for M-release, since the permission model has been changed, - // permissions may not be granted on the phone yet. We need a different flow for user to - // accept these permissions. - // - // Case 1: Companion App >= 23 (and running on M), Wear App targeting >= 23 - // - If Wear is running L (ie DMR1), show a dialog so that the user can accept all perms - // - If Wear is running M (ie E-release), use new permission model. - // Case 2: Companion App <= 22, Wear App targeting <= 22 - // - Default to old behavior. - // Case 3: Companion App <= 22, Wear App targeting >= 23 - // - If Wear is running L (ie DMR1), install the app as before. In effect, pretend - // like wear app is targeting 22. - // - If Wear is running M (ie E-release), use new permission model. - // Case 4: Companion App >= 23 (and running on M), Wear App targeting <= 22 - // - Show a warning below to the developer. - // - Show a dialog as in Case 1 with DMR1. This behavior will happen in E and DMR1. - // Case 5: We did not get Companion App's/Device's version (we have to guess here) - // - Show dialog if Wear App targeting >= 23 and Wear is not running M - if (unavailableWearablePerms.size() > 0) { - boolean isCompanionTargetingM = companionSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; - boolean isCompanionRunningM = companionDeviceVersion > Build.VERSION_CODES.LOLLIPOP_MR1; - boolean isWearTargetingM = - pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; - boolean isWearRunningM = Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1; - - if (companionSdkVersion == 0 || companionDeviceVersion == 0) { // Case 5 - if (isWearTargetingM && !isWearRunningM) { - startPermsServiceForInstall(pkg, apkFile, unavailableWearablePerms); - } - } else if (isCompanionTargetingM && isCompanionRunningM) { - if (!isWearTargetingM) { // Case 4 - Log.w(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if phone " + - "app is targeting at least 23."); - startPermsServiceForInstall(pkg, apkFile, unavailableWearablePerms); - } else if (!isWearRunningM) { // Case 1, part 1 - startPermsServiceForInstall(pkg, apkFile, unavailableWearablePerms); - } - } // Else, nothing to do. See explanation above. - } - - return unavailableWearablePerms.size() == 0; + return unavailableWearablePerms; } private void finishService(PowerManager.WakeLock lock, int startId) { @@ -472,7 +488,7 @@ public class WearPackageInstallerService extends Service { } private void startPermsServiceForInstall(final PackageParser.Package pkg, final File apkFile, - ArrayList<String> unavailableWearablePerms) { + List<String> unavailableWearablePerms) { final String packageName = pkg.packageName; Intent showPermsIntent = new Intent() |