summaryrefslogtreecommitdiffstats
path: root/src/com/android/packageinstaller
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/packageinstaller')
-rw-r--r--src/com/android/packageinstaller/DeviceUtils.java32
-rw-r--r--src/com/android/packageinstaller/InstallFlowAnalytics.java7
-rw-r--r--src/com/android/packageinstaller/PackageInstallerActivity.java22
-rw-r--r--src/com/android/packageinstaller/permission/model/AppPermissions.java9
-rw-r--r--src/com/android/packageinstaller/permission/model/PermissionApps.java3
-rw-r--r--src/com/android/packageinstaller/permission/model/PermissionGroups.java5
-rw-r--r--src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java107
-rw-r--r--src/com/android/packageinstaller/permission/ui/ButtonBarLayout.java117
-rw-r--r--src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java17
-rw-r--r--src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java2
-rw-r--r--src/com/android/packageinstaller/permission/ui/GrantPermissionsWatchViewHandler.java176
-rw-r--r--src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java29
-rw-r--r--src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java1
-rw-r--r--src/com/android/packageinstaller/permission/ui/PreferenceImageView.java69
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java214
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java404
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java (renamed from src/com/android/packageinstaller/permission/ui/GrantPermissionsDefaultViewHandler.java)16
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java268
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java428
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java121
-rw-r--r--src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java (renamed from src/com/android/packageinstaller/permission/ui/SettingsWithHeader.java)6
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java (renamed from src/com/android/packageinstaller/permission/ui/AllAppPermissionsFragment.java)2
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java (renamed from src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java)3
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/GrantPermissionsViewHandlerImpl.java (renamed from src/com/android/packageinstaller/permission/ui/GrantPermissionsTvViewHandler.java)9
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java (renamed from src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java)4
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java (renamed from src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java)6
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java (renamed from src/com/android/packageinstaller/permission/ui/PermissionsFrameFragment.java)21
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java86
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/AppPermissionsFragmentWear.java335
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/ConfirmationViewHandler.java381
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java234
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java118
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/settings/ExtendedOnCenterProximityListener.java30
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/settings/ExtendedViewHolder.java84
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/settings/PermissionsSettingsAdapter.java101
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/settings/SettingsAdapter.java276
-rw-r--r--src/com/android/packageinstaller/permission/ui/wear/settings/ViewUtils.java48
-rw-r--r--src/com/android/packageinstaller/permission/utils/LocationUtils.java17
-rw-r--r--src/com/android/packageinstaller/permission/utils/Utils.java21
-rw-r--r--src/com/android/packageinstaller/wear/WearPackageArgs.java92
-rw-r--r--src/com/android/packageinstaller/wear/WearPackageIconProvider.java202
-rw-r--r--src/com/android/packageinstaller/wear/WearPackageInstallerService.java602
-rw-r--r--src/com/android/packageinstaller/wear/WearPackageUtil.java167
43 files changed, 4809 insertions, 83 deletions
diff --git a/src/com/android/packageinstaller/DeviceUtils.java b/src/com/android/packageinstaller/DeviceUtils.java
new file mode 100644
index 00000000..8e2d57ea
--- /dev/null
+++ b/src/com/android/packageinstaller/DeviceUtils.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+
+public class DeviceUtils {
+ public static boolean isTelevision(Context context) {
+ int uiMode = context.getResources().getConfiguration().uiMode;
+ return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
+ }
+
+ public static boolean isWear(final Context context) {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+}
diff --git a/src/com/android/packageinstaller/InstallFlowAnalytics.java b/src/com/android/packageinstaller/InstallFlowAnalytics.java
index 2fc6db37..4591f31c 100644
--- a/src/com/android/packageinstaller/InstallFlowAnalytics.java
+++ b/src/com/android/packageinstaller/InstallFlowAnalytics.java
@@ -85,6 +85,11 @@ public class InstallFlowAnalytics implements Parcelable {
*/
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;
@@ -600,4 +605,4 @@ public class InstallFlowAnalytics implements Parcelable {
}
return digest.digest();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java
index 6bcd80e4..868872a9 100644
--- a/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -110,6 +110,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
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);
@@ -293,7 +294,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
Log.i(TAG, "Canceling installation");
finish();
}
- })
+ })
.setOnCancelListener(this)
.create();
case DLG_INSTALL_ERROR :
@@ -333,6 +334,18 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
})
.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();
+ }
+ })
+ .setOnCancelListener(this)
+ .create();
}
return null;
}
@@ -478,6 +491,13 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
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);
diff --git a/src/com/android/packageinstaller/permission/model/AppPermissions.java b/src/com/android/packageinstaller/permission/model/AppPermissions.java
index d465ee09..a0f23d64 100644
--- a/src/com/android/packageinstaller/permission/model/AppPermissions.java
+++ b/src/com/android/packageinstaller/permission/model/AppPermissions.java
@@ -23,6 +23,8 @@ import android.text.BidiFormatter;
import android.text.TextPaint;
import android.text.TextUtils;
+import com.android.packageinstaller.DeviceUtils;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -165,9 +167,12 @@ public final class AppPermissions {
private static CharSequence loadEllipsizedAppLabel(Context context, PackageInfo packageInfo) {
String label = packageInfo.applicationInfo.loadLabel(
context.getPackageManager()).toString();
- String noNewLineLabel = label.replace("\n", " ");
- String ellipsizedLabel = TextUtils.ellipsize(noNewLineLabel, sAppLabelEllipsizePaint,
+ 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/PermissionApps.java b/src/com/android/packageinstaller/permission/model/PermissionApps.java
index 9365bf13..e5d96d55 100644
--- a/src/com/android/packageinstaller/permission/model/PermissionApps.java
+++ b/src/com/android/packageinstaller/permission/model/PermissionApps.java
@@ -31,6 +31,7 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
+import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.utils.Utils;
import java.util.ArrayList;
@@ -275,7 +276,7 @@ public class PermissionApps {
if (info.icon != 0) {
mIcon = info.loadUnbadgedIcon(mPm);
} else {
- mIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_perm_device_info);
+ mIcon = mContext.getDrawable(R.drawable.ic_perm_device_info);
}
mIcon = Utils.applyTint(mContext, mIcon, android.R.attr.colorControlNormal);
}
diff --git a/src/com/android/packageinstaller/permission/model/PermissionGroups.java b/src/com/android/packageinstaller/permission/model/PermissionGroups.java
index 59eba856..c496e898 100644
--- a/src/com/android/packageinstaller/permission/model/PermissionGroups.java
+++ b/src/com/android/packageinstaller/permission/model/PermissionGroups.java
@@ -212,11 +212,12 @@ public final class PermissionGroups implements LoaderCallbacks<List<PermissionGr
}
private Drawable loadItemInfoIcon(PackageItemInfo itemInfo) {
- final Drawable icon;
+ Drawable icon = null;
if (itemInfo.icon > 0) {
icon = Utils.loadDrawable(getContext().getPackageManager(),
itemInfo.packageName, itemInfo.icon);
- } else {
+ }
+ if (icon == null) {
icon = getContext().getDrawable(R.drawable.ic_perm_device_info);
}
return icon;
diff --git a/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java b/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java
index 2a46f1a6..810ae8ec 100644
--- a/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java
+++ b/src/com/android/packageinstaller/permission/model/PermissionStatusReceiver.java
@@ -18,6 +18,7 @@ package com.android.packageinstaller.permission.model;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -30,37 +31,55 @@ import com.android.packageinstaller.permission.utils.Utils;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
public class PermissionStatusReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- int[] counts = new int[3];
- ArrayList<CharSequence> grantedGroups = new ArrayList<>();
- boolean succeeded = false;
+ if (Intent.ACTION_GET_PERMISSIONS_COUNT.equals(intent.getAction())) {
+ Intent responseIntent = new Intent(intent.getStringExtra(
+ Intent.EXTRA_GET_PERMISSIONS_RESPONSE_INTENT));
+ responseIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- boolean isForPackage = intent.hasExtra(Intent.EXTRA_PACKAGE_NAME);
+ int[] counts = new int[3];
+ ArrayList<CharSequence> grantedGroups = new ArrayList<>();
+ boolean succeeded = false;
- Intent responseIntent = new Intent(intent.getStringExtra(
- Intent.EXTRA_GET_PERMISSIONS_RESPONSE_INTENT));
- responseIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-
-
- if (isForPackage) {
- String pkg = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- succeeded = getPermissionsCount(context, pkg, counts, grantedGroups);
- } else {
- succeeded = getAppsWithPermissionsCount(context, counts);
- }
- if (succeeded) {
- responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_COUNT_RESULT, counts);
+ boolean isForPackage = intent.hasExtra(Intent.EXTRA_PACKAGE_NAME);
if (isForPackage) {
- responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_GROUP_LIST_RESULT,
- grantedGroups.toArray(new CharSequence[grantedGroups.size()]));
+ String pkg = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ succeeded = getPermissionsCount(context, pkg, counts, grantedGroups);
+ } else {
+ succeeded = getAppsWithPermissionsCount(context, counts);
}
- }
+ if (succeeded) {
+ responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_COUNT_RESULT, counts);
- context.sendBroadcast(responseIntent);
+ if (isForPackage) {
+ responseIntent.putExtra(Intent.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())) {
+ Intent responseIntent = new Intent(intent.getStringExtra(
+ 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,
+ appsList.toArray(new String[appsList.size()]));
+ responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT,
+ appLabelsList.toArray(new String[appLabelsList.size()]));
+ responseIntent.putExtra(Intent.EXTRA_GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT,
+ toPrimitiveBoolArray(isSystemAppList));
+ }
+ context.sendBroadcast(responseIntent);
+ }
}
public boolean getPermissionsCount(Context context, String pkg, int[] counts,
@@ -105,6 +124,42 @@ public class PermissionStatusReceiver extends BroadcastReceiver {
}
}
+ public boolean getAppsWithRuntimePermissions(Context context, List<String> appsList,
+ List<CharSequence> appLabelsList, List<Boolean> isSystemAppList) {
+ final List<ApplicationInfo> appInfos = Utils.getAllInstalledApplications(context);
+ if (appInfos == null) {
+ return false;
+ }
+ final int appInfosSize = appInfos.size();
+ try {
+ ArraySet<String> launcherPackages = Utils.getLauncherPackages(context);
+ for (int i = 0; i < appInfosSize; ++i) {
+ final String packageName = appInfos.get(i).packageName;
+ PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
+ packageName, PackageManager.GET_PERMISSIONS);
+ AppPermissions appPermissions =
+ new AppPermissions(context, packageInfo, null, false, null);
+
+ boolean shouldShow = false;
+ for (AppPermissionGroup group : appPermissions.getPermissionGroups()) {
+ if (Utils.shouldShowPermission(group, packageName)) {
+ shouldShow = true;
+ break;
+ }
+ }
+ if (shouldShow) {
+ appsList.add(packageName);
+ appLabelsList.add(appPermissions.getAppLabel());
+ isSystemAppList.add(Utils.isSystem(appPermissions, launcherPackages));
+ }
+ }
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+
+ return true;
+ }
+
public boolean getAppsWithPermissionsCount(Context context, int[] counts) {
ArraySet<String> launcherPkgs = Utils.getLauncherPackages(context);
// Indexed by uid.
@@ -130,4 +185,14 @@ public class PermissionStatusReceiver extends BroadcastReceiver {
counts[1] = allApps.size();
return true;
}
+
+ private boolean[] toPrimitiveBoolArray(final List<Boolean> list) {
+ final int count = list.size();
+ final boolean[] result = new boolean[count];
+ for (int i = 0; i < count; ++i) {
+ result[i] = list.get(i);
+ }
+
+ return result;
+ }
}
diff --git a/src/com/android/packageinstaller/permission/ui/ButtonBarLayout.java b/src/com/android/packageinstaller/permission/ui/ButtonBarLayout.java
new file mode 100644
index 00000000..59e54707
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/ButtonBarLayout.java
@@ -0,0 +1,117 @@
+/*
+ * 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.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import com.android.packageinstaller.R;
+
+/**
+ * An extension of LinearLayout that automatically switches to vertical
+ * orientation when it can't fit its child views horizontally.
+ */
+public class ButtonBarLayout extends LinearLayout {
+ /** Whether the current configuration allows stacking. */
+ private boolean mAllowStacking;
+
+ private int mLastWidthSize = -1;
+
+ public ButtonBarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAllowStacking = true;
+ }
+
+ public void setAllowStacking(boolean allowStacking) {
+ if (mAllowStacking != allowStacking) {
+ mAllowStacking = allowStacking;
+ if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) {
+ setStacked(false);
+ }
+ requestLayout();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ if (mAllowStacking) {
+ if (widthSize > mLastWidthSize && isStacked()) {
+ // We're being measured wider this time, try un-stacking.
+ setStacked(false);
+ }
+
+ mLastWidthSize = widthSize;
+ }
+
+ boolean needsRemeasure = false;
+
+ // If we're not stacked, make sure the measure spec is AT_MOST rather
+ // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
+ // know to stack the buttons.
+ final int initialWidthMeasureSpec;
+ if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
+ initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+
+ // We'll need to remeasure again to fill excess space.
+ needsRemeasure = true;
+ } else {
+ initialWidthMeasureSpec = widthMeasureSpec;
+ }
+
+ super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
+
+ if (mAllowStacking && !isStacked()) {
+ final int measuredWidth = getMeasuredWidthAndState();
+ final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK;
+ if (measuredWidthState == MEASURED_STATE_TOO_SMALL) {
+ setStacked(true);
+
+ // Measure again in the new orientation.
+ needsRemeasure = true;
+ }
+ }
+
+ if (needsRemeasure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private void setStacked(boolean stacked) {
+ setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
+ setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
+
+ final View spacer = findViewById(R.id.spacer);
+ if (spacer != null) {
+ spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
+ }
+
+ // Reverse the child order. This is specific to the Material button
+ // bar's layout XML and will probably not generalize.
+ final int childCount = getChildCount();
+ for (int i = childCount - 2; i >= 0; i--) {
+ bringChildToFront(getChildAt(i));
+ }
+ }
+
+ private boolean isStacked() {
+ return getOrientation() == LinearLayout.VERTICAL;
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
index 56b3f466..102fd6ef 100644
--- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
+++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
@@ -26,11 +26,12 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;
import android.content.res.Resources;
+import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.hardware.camera2.utils.ArrayUtils;
import android.os.Bundle;
import android.text.SpannableString;
-import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -38,6 +39,7 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import com.android.packageinstaller.DeviceUtils;
import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
@@ -71,10 +73,14 @@ public class GrantPermissionsActivity extends OverlayTouchActivity
setTitle(R.string.permission_request_title);
- if (Utils.isTelevision(this)) {
- mViewHandler = new GrantPermissionsTvViewHandler(this).setResultListener(this);
+ if (DeviceUtils.isTelevision(this)) {
+ mViewHandler = new com.android.packageinstaller.permission.ui.television
+ .GrantPermissionsViewHandlerImpl(this).setResultListener(this);
+ } else if (DeviceUtils.isWear(this)) {
+ mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this);
} else {
- mViewHandler = new GrantPermissionsDefaultViewHandler(this).setResultListener(this);
+ mViewHandler = new com.android.packageinstaller.permission.ui.handheld
+ .GrantPermissionsViewHandlerImpl(this).setResultListener(this);
}
mRequestedPermissions = getIntent().getStringArrayExtra(
@@ -206,8 +212,7 @@ public class GrantPermissionsActivity extends OverlayTouchActivity
// Color the app name.
int appLabelStart = message.toString().indexOf(appLabel.toString(), 0);
int appLabelLength = appLabel.length();
- int color = getColor(R.color.grant_permissions_app_color);
- message.setSpan(new ForegroundColorSpan(color), appLabelStart,
+ message.setSpan(new StyleSpan(Typeface.BOLD), appLabelStart,
appLabelStart + appLabelLength, 0);
// Set the new grant view
diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java
index 4032abb2..5e2259af 100644
--- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java
+++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsViewHandler.java
@@ -25,7 +25,7 @@ import android.view.WindowManager;
* Class for managing the presentation and user interaction of the "grant
* permissions" user interface.
*/
-interface GrantPermissionsViewHandler {
+public interface GrantPermissionsViewHandler {
/**
* Listener interface for getting notified when the user responds to a
diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsWatchViewHandler.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsWatchViewHandler.java
new file mode 100644
index 00000000..21042f00
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsWatchViewHandler.java
@@ -0,0 +1,176 @@
+package com.android.packageinstaller.permission.ui;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.ui.wear.ConfirmationViewHandler;
+
+/**
+ * Watch-specific view handler for the grant permissions activity.
+ */
+final class GrantPermissionsWatchViewHandler extends ConfirmationViewHandler
+ implements GrantPermissionsViewHandler {
+ private static final String TAG = "GrantPermsWatchViewH";
+
+ private static final String ARG_GROUP_NAME = "ARG_GROUP_NAME";
+
+ private final Context mContext;
+
+ private ResultListener mResultListener;
+
+ private String mGroupName;
+ private boolean mShowDoNotAsk;
+
+ private CharSequence mMessage;
+ private String mCurrentPageText;
+ private Icon mIcon;
+
+ GrantPermissionsWatchViewHandler(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public GrantPermissionsWatchViewHandler setResultListener(ResultListener listener) {
+ mResultListener = listener;
+ return this;
+ }
+
+ @Override
+ public View createView() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "createView()");
+ }
+
+ mShowDoNotAsk = false;
+
+ return super.createView();
+ }
+
+ @Override
+ public void updateWindowAttributes(WindowManager.LayoutParams outLayoutParams) {
+ outLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
+ outLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
+ outLayoutParams.format = PixelFormat.OPAQUE;
+ outLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+ outLayoutParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+ }
+
+ @Override
+ public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
+ CharSequence message, boolean showDoNotAsk) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "updateUi() - groupName: " + groupName
+ + ", groupCount: " + groupCount
+ + ", groupIndex: " + groupIndex
+ + ", icon: " + icon
+ + ", message: " + message
+ + ", showDoNotAsk: " + showDoNotAsk);
+ }
+
+ mGroupName = groupName;
+ mShowDoNotAsk = showDoNotAsk;
+ mMessage = message;
+ mIcon = icon;
+ mCurrentPageText = (groupCount > 1 ?
+ mContext.getString(R.string.current_permission_template, groupIndex + 1, groupCount)
+ : null);
+
+ invalidate();
+ }
+
+ @Override
+ public void saveInstanceState(Bundle outState) {
+ outState.putString(ARG_GROUP_NAME, mGroupName);
+ }
+
+ @Override
+ public void loadInstanceState(Bundle savedInstanceState) {
+ mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mResultListener != null) {
+ mResultListener.onPermissionGrantResult(mGroupName, false, false);
+ }
+ }
+
+ @Override // ConfirmationViewHandler
+ public void onButton1() {
+ onClick(true /* granted */, false /* doNotAskAgain */);
+ }
+
+ @Override // ConfirmationViewHandler
+ public void onButton2() {
+ onClick(false /* granted */, false /* doNotAskAgain */);
+ }
+
+ @Override // ConfirmationViewHandler
+ public void onButton3() {
+ onClick(false /* granted */, true /* doNotAskAgain */);
+ }
+
+ @Override // ConfirmationViewHandler
+ public CharSequence getCurrentPageText() {
+ return mCurrentPageText;
+ }
+
+ @Override // ConfirmationViewHandler
+ public Icon getPermissionIcon() {
+ return mIcon;
+ }
+
+ @Override // ConfirmationViewHandler
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+
+ @Override // ConfirmationViewHandler
+ public int getButtonBarMode() {
+ return mShowDoNotAsk ? MODE_VERTICAL_BUTTONS : MODE_HORIZONTAL_BUTTONS;
+ }
+
+ @Override // ConfirmationViewHandler
+ public CharSequence getVerticalButton1Text() {
+ return mContext.getString(R.string.grant_dialog_button_allow);
+ }
+
+ @Override // ConfirmationViewHandler
+ public CharSequence getVerticalButton2Text() {
+ return mContext.getString(R.string.grant_dialog_button_deny);
+ }
+
+ @Override // ConfirmationViewHandler
+ public CharSequence getVerticalButton3Text() {
+ return mContext.getString(R.string.grant_dialog_button_deny_dont_ask_again);
+ }
+
+ @Override // ConfirmationViewHandler
+ public Drawable getVerticalButton1Icon(){
+ return mContext.getDrawable(R.drawable.confirm_button);
+ }
+
+ @Override // ConfirmationViewHandler
+ public Drawable getVerticalButton2Icon(){
+ return mContext.getDrawable(R.drawable.cancel_button);
+ }
+
+ @Override // ConfirmationViewHandler
+ public Drawable getVerticalButton3Icon(){
+ return mContext.getDrawable(R.drawable.deny_button);
+ }
+
+ private void onClick(boolean granted, boolean doNotAskAgain) {
+ if (mResultListener != null) {
+ mResultListener.onPermissionGrantResult(mGroupName, granted, doNotAskAgain);
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java
index 8ba6b127..38dbf8f5 100644
--- a/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java
+++ b/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java
@@ -21,6 +21,9 @@ import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
+import com.android.packageinstaller.permission.ui.wear.AppPermissionsFragmentWear;
+import com.android.packageinstaller.DeviceUtils;
+
public final class ManagePermissionsActivity extends OverlayTouchActivity {
private static final String LOG_TAG = "ManagePermissionsActivity";
@@ -37,7 +40,13 @@ public final class ManagePermissionsActivity extends OverlayTouchActivity {
switch (action) {
case Intent.ACTION_MANAGE_PERMISSIONS: {
- fragment = ManagePermissionsFragment.newInstance();
+ if (DeviceUtils.isTelevision(this)) {
+ fragment = com.android.packageinstaller.permission.ui.television
+ .ManagePermissionsFragment.newInstance();
+ } else {
+ fragment = com.android.packageinstaller.permission.ui.handheld
+ .ManagePermissionsFragment.newInstance();
+ }
} break;
case Intent.ACTION_MANAGE_APP_PERMISSIONS: {
@@ -47,7 +56,15 @@ public final class ManagePermissionsActivity extends OverlayTouchActivity {
finish();
return;
}
- fragment = AppPermissionsFragment.newInstance(packageName);
+ if (DeviceUtils.isWear(this)) {
+ fragment = AppPermissionsFragmentWear.newInstance(packageName);
+ } else if (DeviceUtils.isTelevision(this)) {
+ fragment = com.android.packageinstaller.permission.ui.television
+ .AppPermissionsFragment.newInstance(packageName);
+ } else {
+ fragment = com.android.packageinstaller.permission.ui.handheld
+ .AppPermissionsFragment.newInstance(packageName);
+ }
} break;
case Intent.ACTION_MANAGE_PERMISSION_APPS: {
@@ -57,7 +74,13 @@ public final class ManagePermissionsActivity extends OverlayTouchActivity {
finish();
return;
}
- fragment = PermissionAppsFragment.newInstance(permissionName);
+ if (DeviceUtils.isTelevision(this)) {
+ fragment = com.android.packageinstaller.permission.ui.television
+ .PermissionAppsFragment.newInstance(permissionName);
+ } else {
+ fragment = com.android.packageinstaller.permission.ui.handheld
+ .PermissionAppsFragment.newInstance(permissionName);
+ }
} break;
default: {
diff --git a/src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java b/src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java
index a7c1e2a1..61734b47 100644
--- a/src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java
+++ b/src/com/android/packageinstaller/permission/ui/OverlayWarningDialog.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.packageinstaller.permission.ui;
import android.app.Activity;
diff --git a/src/com/android/packageinstaller/permission/ui/PreferenceImageView.java b/src/com/android/packageinstaller/permission/ui/PreferenceImageView.java
new file mode 100644
index 00000000..c3f51674
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/PreferenceImageView.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 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.widget.ImageView;
+
+/**
+ * Extension of ImageView that correctly applies maxWidth and maxHeight.
+ */
+public class PreferenceImageView extends ImageView {
+
+ public PreferenceImageView(Context context) {
+ this(context, null);
+ }
+
+ public PreferenceImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PreferenceImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public PreferenceImageView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int maxWidth = getMaxWidth();
+ if (maxWidth != Integer.MAX_VALUE
+ && (maxWidth < widthSize || widthMode == MeasureSpec.UNSPECIFIED)) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST);
+ }
+ }
+
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ final int maxHeight = getMaxHeight();
+ if (maxHeight != Integer.MAX_VALUE
+ && (maxHeight < heightSize || heightMode == MeasureSpec.UNSPECIFIED)) {
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java
new file mode 100644
index 00000000..b3b0895c
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/handheld/AllAppPermissionsFragment.java
@@ -0,0 +1,214 @@
+/*
+* 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.handheld;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceGroup;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.MenuItem;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+public final class AllAppPermissionsFragment extends SettingsWithHeader {
+
+ private static final String LOG_TAG = "AllAppPermissionsFragment";
+
+ private static final String KEY_OTHER = "other_perms";
+
+ public static AllAppPermissionsFragment newInstance(String packageName) {
+ AllAppPermissionsFragment instance = new AllAppPermissionsFragment();
+ Bundle arguments = new Bundle();
+ arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+ instance.setArguments(arguments);
+ return instance;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ final ActionBar ab = getActivity().getActionBar();
+ if (ab != null) {
+ ab.setTitle(R.string.all_permissions);
+ ab.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateUi();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home: {
+ getFragmentManager().popBackStack();
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void updateUi() {
+ if (getPreferenceScreen() != null) {
+ getPreferenceScreen().removeAll();
+ }
+ addPreferencesFromResource(R.xml.all_permissions);
+ PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER);
+ 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();
+
+ 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;
+ }
+
+ if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
+ || (perm.flags & PermissionInfo.FLAG_HIDDEN) != 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));
+ }
+ }
+ }
+ } 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
+ public int compare(Preference lhs, Preference rhs) {
+ String lKey = lhs.getKey();
+ String rKey = rhs.getKey();
+ if (lKey.equals(KEY_OTHER)) {
+ return 1;
+ } else if (rKey.equals(KEY_OTHER)) {
+ return -1;
+ } else if (Utils.isModernPermissionGroup(lKey)
+ != Utils.isModernPermissionGroup(rKey)) {
+ return Utils.isModernPermissionGroup(lKey) ? -1 : 1;
+ }
+ return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
+ }
+ });
+ for (int i = 0; i < prefs.size(); i++) {
+ prefs.get(i).setOrder(i);
+ }
+ }
+
+ private PermissionGroupInfo getGroup(String group, PackageManager pm) {
+ try {
+ return pm.getPermissionGroupInfo(group, 0);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm,
+ ArrayList<Preference> prefs) {
+ PreferenceGroup pref = (PreferenceGroup) findPreference(group.name);
+ if (pref == null) {
+ pref = new PreferenceCategory(getContext());
+ pref.setKey(group.name);
+ pref.setTitle(group.loadLabel(pm));
+ prefs.add(pref);
+ getPreferenceScreen().addPreference(pref);
+ }
+ return pref;
+ }
+
+ private Preference getPreference(PermissionInfo perm, PermissionGroupInfo group,
+ PackageManager pm) {
+ Preference pref = new Preference(getContext());
+ Drawable icon = null;
+ if (perm.icon != 0) {
+ icon = perm.loadIcon(pm);
+ } else if (group != null && group.icon != 0) {
+ icon = group.loadIcon(pm);
+ } else {
+ icon = getContext().getDrawable(R.drawable.ic_perm_device_info);
+ }
+ pref.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal));
+ pref.setTitle(perm.loadLabel(pm));
+ final CharSequence desc = perm.loadDescription(pm);
+ pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ new AlertDialog.Builder(getContext())
+ .setMessage(desc)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ return true;
+ }
+ });
+
+ return pref;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java
new file mode 100644
index 00000000..f56cba70
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/handheld/AppPermissionsFragment.java
@@ -0,0 +1,404 @@
+/*
+* 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.handheld;
+
+import android.annotation.Nullable;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.provider.Settings;
+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.utils.LocationUtils;
+import com.android.packageinstaller.permission.utils.SafetyNetLogger;
+import com.android.packageinstaller.permission.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class AppPermissionsFragment extends SettingsWithHeader
+ implements OnPreferenceChangeListener {
+
+ private static final String LOG_TAG = "ManagePermsFragment";
+
+ static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
+
+ private static final int MENU_ALL_PERMS = 0;
+
+ private List<AppPermissionGroup> mToggledGroups;
+ private AppPermissions mAppPermissions;
+ private PreferenceScreen mExtraScreen;
+
+ private boolean mHasConfirmedRevoke;
+
+ public static AppPermissionsFragment newInstance(String packageName) {
+ return setPackageName(new AppPermissionsFragment(), packageName);
+ }
+
+ private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
+ Bundle arguments = new Bundle();
+ arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+ fragment.setArguments(arguments);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setLoading(true /* loading */, false /* animate */);
+ setHasOptionsMenu(true);
+ final ActionBar ab = getActivity().getActionBar();
+ if (ab != null) {
+ ab.setDisplayHomeAsUpEnabled(true);
+ }
+
+ String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
+ Activity activity = getActivity();
+ PackageInfo packageInfo = getPackageInfo(activity, packageName);
+ if (packageInfo == null) {
+ Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
+ activity.finish();
+ return;
+ }
+
+ mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() {
+ @Override
+ public void run() {
+ getActivity().finish();
+ }
+ });
+ loadPreferences();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mAppPermissions.refresh();
+ setPreferencesCheckedState();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home: {
+ getActivity().finish();
+ return true;
+ }
+
+ case MENU_ALL_PERMS: {
+ Fragment frag = AllAppPermissionsFragment.newInstance(
+ getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, frag)
+ .addToBackStack("AllPerms")
+ .commit();
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (mAppPermissions != null) {
+ bindUi(this, mAppPermissions.getPackageInfo());
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
+ }
+
+ 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);
+
+ 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);
+ }
+ }
+
+ private void loadPreferences() {
+ Context context = getActivity();
+ if (context == null) {
+ return;
+ }
+
+ PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ screen = getPreferenceManager().createPreferenceScreen(getActivity());
+ setPreferenceScreen(screen);
+ }
+
+ screen.removeAll();
+
+ if (mExtraScreen != null) {
+ mExtraScreen.removeAll();
+ }
+
+ final Preference extraPerms = new Preference(context);
+ extraPerms.setIcon(R.drawable.ic_toc);
+ extraPerms.setTitle(R.string.additional_permissions);
+
+ for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
+ if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) {
+ continue;
+ }
+
+ boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
+
+ SwitchPreference preference = new SwitchPreference(context);
+ preference.setOnPreferenceChangeListener(this);
+ preference.setKey(group.getName());
+ Drawable icon = Utils.loadDrawable(context.getPackageManager(),
+ group.getIconPkg(), group.getIconResId());
+ preference.setIcon(Utils.applyTint(getContext(), icon,
+ android.R.attr.colorControlNormal));
+ preference.setTitle(group.getLabel());
+ if (group.isPolicyFixed()) {
+ preference.setSummary(getString(R.string.permission_summary_enforced_by_policy));
+ }
+ preference.setPersistent(false);
+ preference.setEnabled(!group.isPolicyFixed());
+ preference.setChecked(group.areRuntimePermissionsGranted());
+
+ if (isPlatform) {
+ screen.addPreference(preference);
+ } else {
+ if (mExtraScreen == null) {
+ mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
+ }
+ mExtraScreen.addPreference(preference);
+ }
+ }
+
+ if (mExtraScreen != null) {
+ extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
+ setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
+ frag.setTargetFragment(AppPermissionsFragment.this, 0);
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, frag)
+ .addToBackStack(null)
+ .commit();
+ return true;
+ }
+ });
+ int count = mExtraScreen.getPreferenceCount();
+ extraPerms.setSummary(getResources().getQuantityString(
+ R.plurals.additional_permissions_more, count, count));
+ screen.addPreference(extraPerms);
+ }
+
+ setLoading(false /* loading */, true /* animate */);
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference preference, Object newValue) {
+ String groupName = preference.getKey();
+ final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
+
+ if (group == null) {
+ return false;
+ }
+
+ OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
+ if (activity.isObscuredTouch()) {
+ activity.showOverlayDialog();
+ return false;
+ }
+
+ addToggledGroup(group);
+
+ if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
+ LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
+ return false;
+ }
+ if (newValue == Boolean.TRUE) {
+ group.grantRuntimePermissions(false);
+ } else {
+ final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
+ if (grantedByDefault || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) {
+ new AlertDialog.Builder(getContext())
+ .setMessage(grantedByDefault ? R.string.system_warning
+ : R.string.old_sdk_deny_warning)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.grant_dialog_button_deny,
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ((SwitchPreference) preference).setChecked(false);
+ group.revokeRuntimePermissions(false);
+ if (!grantedByDefault) {
+ mHasConfirmedRevoke = true;
+ }
+ }
+ })
+ .show();
+ return false;
+ } else {
+ group.revokeRuntimePermissions(false);
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ logToggledGroups();
+ }
+
+ private void addToggledGroup(AppPermissionGroup group) {
+ if (mToggledGroups == null) {
+ mToggledGroups = new ArrayList<>();
+ }
+ // Double toggle is back to initial state.
+ if (mToggledGroups.contains(group)) {
+ mToggledGroups.remove(group);
+ } else {
+ mToggledGroups.add(group);
+ }
+ }
+
+ private void logToggledGroups() {
+ if (mToggledGroups != null) {
+ String packageName = mAppPermissions.getPackageInfo().packageName;
+ SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups);
+ mToggledGroups = null;
+ }
+ }
+
+ private void setPreferencesCheckedState() {
+ setPreferencesCheckedState(getPreferenceScreen());
+ if (mExtraScreen != null) {
+ setPreferencesCheckedState(mExtraScreen);
+ }
+ }
+
+ private void setPreferencesCheckedState(PreferenceScreen screen) {
+ int preferenceCount = screen.getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ Preference preference = screen.getPreference(i);
+ if (preference instanceof SwitchPreference) {
+ SwitchPreference switchPref = (SwitchPreference) preference;
+ AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey());
+ if (group != null) {
+ switchPref.setChecked(group.areRuntimePermissionsGranted());
+ }
+ }
+ }
+ }
+
+ private static PackageInfo getPackageInfo(Activity activity, String packageName) {
+ try {
+ return activity.getPackageManager().getPackageInfo(
+ packageName, PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
+ return null;
+ }
+ }
+
+ public static class AdditionalPermissionsFragment extends SettingsWithHeader {
+ AppPermissionsFragment mOuterFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mOuterFragment = (AppPermissionsFragment) getTargetFragment();
+ super.onCreate(savedInstanceState);
+ setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, mOuterFragment.mInfoIntent);
+ setHasOptionsMenu(true);
+ setPreferenceScreen(mOuterFragment.mExtraScreen);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
+ bindUi(this, getPackageInfo(getActivity(), packageName));
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ getFragmentManager().popBackStack();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsDefaultViewHandler.java b/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java
index c5d78784..2d27f069 100644
--- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsDefaultViewHandler.java
+++ b/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.packageinstaller.permission.ui;
+package com.android.packageinstaller.permission.ui.handheld;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -40,12 +40,14 @@ import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
-import com.android.internal.widget.ButtonBarLayout;
import com.android.packageinstaller.R;
+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;
-final class GrantPermissionsDefaultViewHandler
+public final class GrantPermissionsViewHandlerImpl
implements GrantPermissionsViewHandler, OnClickListener {
public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME";
@@ -101,12 +103,12 @@ final class GrantPermissionsDefaultViewHandler
}
};
- GrantPermissionsDefaultViewHandler(Context context) {
+ public GrantPermissionsViewHandlerImpl(Context context) {
mContext = context;
}
@Override
- public GrantPermissionsDefaultViewHandler setResultListener(ResultListener listener) {
+ public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) {
mResultListener = listener;
return this;
}
@@ -314,9 +316,7 @@ final class GrantPermissionsDefaultViewHandler
public View createView() {
mRootView = (ManualLayoutFrame) LayoutInflater.from(mContext)
.inflate(R.layout.grant_permissions, null);
- ((ButtonBarLayout) mRootView.findViewById(R.id.button_group)).setAllowStacking(
- Resources.getSystem().getBoolean(
- com.android.internal.R.bool.allow_stacked_button_bar));
+ ((ButtonBarLayout) mRootView.findViewById(R.id.button_group)).setAllowStacking(true);
mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container);
mMessageView = (TextView) mRootView.findViewById(R.id.permission_message);
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java
new file mode 100644
index 00000000..c53da879
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/handheld/ManagePermissionsFragment.java
@@ -0,0 +1,268 @@
+/*
+ * 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.handheld;
+
+import android.annotation.Nullable;
+import android.app.ActionBar;
+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.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
+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;
+import com.android.packageinstaller.permission.model.PermissionApps.PmCache;
+import com.android.packageinstaller.permission.model.PermissionGroup;
+import com.android.packageinstaller.permission.model.PermissionGroups;
+import com.android.packageinstaller.permission.utils.Utils;
+
+import java.util.List;
+
+public final class ManagePermissionsFragment extends PermissionsFrameFragment
+ implements PermissionGroups.PermissionsGroupsChangeCallback,
+ Preference.OnPreferenceClickListener {
+ private static final String LOG_TAG = "ManagePermissionsFragment";
+
+ private static final String OS_PKG = "android";
+
+ private static final String EXTRA_PREFS_KEY = "extra_prefs_key";
+
+ private ArraySet<String> mLauncherPkgs;
+
+ private PermissionGroups mPermissions;
+
+ private PreferenceScreen mExtraScreen;
+
+ public static ManagePermissionsFragment newInstance() {
+ return new ManagePermissionsFragment();
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setLoading(true /* loading */, false /* animate */);
+ setHasOptionsMenu(true);
+ final ActionBar ab = getActivity().getActionBar();
+ if (ab != null) {
+ ab.setDisplayHomeAsUpEnabled(true);
+ }
+ mLauncherPkgs = Utils.getLauncherPackages(getContext());
+ mPermissions = new PermissionGroups(getActivity(), getLoaderManager(), this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mPermissions.refresh();
+ updatePermissionsUi();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ getActivity().finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ String key = preference.getKey();
+
+ PermissionGroup group = mPermissions.getGroup(key);
+ if (group == null) {
+ return false;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS)
+ .putExtra(Intent.EXTRA_PERMISSION_NAME, key);
+ try {
+ getActivity().startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(LOG_TAG, "No app to handle " + intent);
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onPermissionGroupsChanged() {
+ updatePermissionsUi();
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ bindPermissionUi(getActivity(), getView());
+ }
+
+ private static void bindPermissionUi(@Nullable Context context, @Nullable 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() {
+ Context context = getActivity();
+ if (context == null) {
+ return;
+ }
+
+ List<PermissionGroup> groups = mPermissions.getGroups();
+ PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ screen = getPreferenceManager().createPreferenceScreen(getActivity());
+ setPreferenceScreen(screen);
+ }
+
+ // Use this to speed up getting the info for all of the PermissionApps below.
+ // Create a new one for each refresh to make sure it has fresh data.
+ PmCache cache = new PmCache(getContext().getPackageManager());
+ for (PermissionGroup group : groups) {
+ boolean isSystemPermission = group.getDeclaringPackage().equals(OS_PKG);
+
+ Preference preference = findPreference(group.getName());
+ if (preference == null && mExtraScreen != null) {
+ preference = mExtraScreen.findPreference(group.getName());
+ }
+ if (preference == null) {
+ preference = new Preference(context);
+ preference.setOnPreferenceClickListener(this);
+ preference.setKey(group.getName());
+ preference.setIcon(Utils.applyTint(context, group.getIcon(),
+ android.R.attr.colorControlNormal));
+ preference.setTitle(group.getLabel());
+ // Set blank summary so that no resizing/jumping happens when the summary is loaded.
+ preference.setSummary(" ");
+ preference.setPersistent(false);
+ if (isSystemPermission) {
+ screen.addPreference(preference);
+ } else {
+ if (mExtraScreen == null) {
+ mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
+ }
+ mExtraScreen.addPreference(preference);
+ }
+ }
+ final Preference finalPref = preference;
+
+ new PermissionApps(getContext(), group.getName(), new PermissionApps.Callback() {
+ @Override
+ public void onPermissionsLoaded(PermissionApps permissionApps) {
+ if (getActivity() == null) {
+ return;
+ }
+ int granted = permissionApps.getGrantedCount(mLauncherPkgs);
+ int total = permissionApps.getTotalCount(mLauncherPkgs);
+ finalPref.setSummary(getString(R.string.app_permissions_group_summary,
+ granted, total));
+ }
+ }, cache).refresh(false);
+ }
+
+ if (mExtraScreen != null && mExtraScreen.getPreferenceCount() > 0
+ && screen.findPreference(EXTRA_PREFS_KEY) == null) {
+ Preference extraScreenPreference = new Preference(context);
+ extraScreenPreference.setKey(EXTRA_PREFS_KEY);
+ extraScreenPreference.setIcon(Utils.applyTint(context,
+ R.drawable.ic_more_items,
+ android.R.attr.colorControlNormal));
+ extraScreenPreference.setTitle(R.string.additional_permissions);
+ extraScreenPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
+ frag.setTargetFragment(ManagePermissionsFragment.this, 0);
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(android.R.id.content, frag);
+ ft.addToBackStack(null);
+ ft.commit();
+ return true;
+ }
+ });
+ int count = mExtraScreen.getPreferenceCount();
+ extraScreenPreference.setSummary(getResources().getQuantityString(
+ R.plurals.additional_permissions_more, count, count));
+ screen.addPreference(extraScreenPreference);
+ }
+ if (screen.getPreferenceCount() != 0) {
+ setLoading(false /* loading */, true /* animate */);
+ }
+ }
+
+ public static class AdditionalPermissionsFragment extends PermissionsFrameFragment {
+ @Override
+ public void onCreate(Bundle icicle) {
+ setLoading(true /* loading */, false /* animate */);
+ super.onCreate(icicle);
+ getActivity().setTitle(R.string.additional_permissions);
+ setHasOptionsMenu(true);
+
+ setPreferenceScreen(((ManagePermissionsFragment) getTargetFragment()).mExtraScreen);
+ setLoading(false /* loading */, true /* animate */);
+ }
+
+ @Override
+ public void onDestroy() {
+ getActivity().setTitle(R.string.app_permissions);
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ getFragmentManager().popBackStack();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable 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
new file mode 100644
index 00000000..eee2f716
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java
@@ -0,0 +1,428 @@
+/*
+ * 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.handheld;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+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 com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.R;
+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 java.util.ArrayList;
+import java.util.List;
+
+public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback,
+ Preference.OnPreferenceChangeListener {
+
+ private static final int MENU_SHOW_SYSTEM = Menu.FIRST;
+ private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 1;
+ private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem";
+
+ public static PermissionAppsFragment newInstance(String permissionName) {
+ return setPermissionName(new PermissionAppsFragment(), permissionName);
+ }
+
+ private static <T extends Fragment> T setPermissionName(T fragment, String permissionName) {
+ Bundle arguments = new Bundle();
+ arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName);
+ fragment.setArguments(arguments);
+ return fragment;
+ }
+
+ private PermissionApps mPermissionApps;
+
+ private PreferenceScreen mExtraScreen;
+
+ private ArrayMap<String, AppPermissionGroup> mToggledGroups;
+ private ArraySet<String> mLauncherPkgs;
+ private boolean mHasConfirmedRevoke;
+
+ private boolean mShowSystem;
+ private MenuItem mShowSystemMenu;
+ private MenuItem mHideSystemMenu;
+
+ private Callback mOnPermissionsLoadedListener;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setLoading(true /* loading */, false /* animate */);
+ setHasOptionsMenu(true);
+ final ActionBar ab = getActivity().getActionBar();
+ if (ab != null) {
+ ab.setDisplayHomeAsUpEnabled(true);
+ }
+ mLauncherPkgs = Utils.getLauncherPackages(getContext());
+
+ String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
+ mPermissionApps = new PermissionApps(getActivity(), groupName, this);
+ mPermissionApps.refresh(true);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mPermissionApps.refresh(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
+ R.string.menu_show_system);
+ mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
+ R.string.menu_hide_system);
+ updateMenu();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ getActivity().finish();
+ return true;
+ case MENU_SHOW_SYSTEM:
+ case MENU_HIDE_SYSTEM:
+ mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM;
+ if (mPermissionApps.getApps() != null) {
+ onPermissionsLoaded(mPermissionApps);
+ }
+ updateMenu();
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void updateMenu() {
+ mShowSystemMenu.setVisible(!mShowSystem);
+ mHideSystemMenu.setVisible(mShowSystem);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ bindUi(this, mPermissionApps);
+ }
+
+ private static void bindUi(Fragment 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);
+ }
+ }
+
+ private void setOnPermissionsLoadedListener(Callback callback) {
+ mOnPermissionsLoadedListener = callback;
+ }
+
+ @Override
+ public void onPermissionsLoaded(PermissionApps permissionApps) {
+ Context context = getActivity();
+
+ if (context == null) {
+ return;
+ }
+
+ boolean isTelevision = DeviceUtils.isTelevision(context);
+ PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ screen = getPreferenceManager().createPreferenceScreen(getActivity());
+ setPreferenceScreen(screen);
+ }
+
+ ArraySet<String> preferencesToRemove = new ArraySet<>();
+ for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
+ preferencesToRemove.add(screen.getPreference(i).getKey());
+ }
+ if (mExtraScreen != null) {
+ for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
+ preferencesToRemove.add(mExtraScreen.getPreference(i).getKey());
+ }
+ }
+
+ for (PermissionApp app : permissionApps.getApps()) {
+ if (!Utils.shouldShowPermission(app)) {
+ continue;
+ }
+
+ String key = app.getKey();
+ preferencesToRemove.remove(key);
+ Preference existingPref = screen.findPreference(key);
+ if (existingPref == null && mExtraScreen != null) {
+ existingPref = mExtraScreen.findPreference(key);
+ }
+
+ boolean isSystemApp = Utils.isSystem(app, mLauncherPkgs);
+ if (isSystemApp && !isTelevision && !mShowSystem) {
+ if (existingPref != null) {
+ screen.removePreference(existingPref);
+ }
+ continue;
+ }
+
+ if (existingPref != null) {
+ // If existing preference - only update its state.
+ if (app.isPolicyFixed()) {
+ existingPref.setSummary(getString(
+ R.string.permission_summary_enforced_by_policy));
+ }
+ existingPref.setPersistent(false);
+ existingPref.setEnabled(!app.isPolicyFixed());
+ if (existingPref instanceof SwitchPreference) {
+ ((SwitchPreference) existingPref)
+ .setChecked(app.areRuntimePermissionsGranted());
+ }
+ continue;
+ }
+
+ SwitchPreference pref = new SwitchPreference(context);
+ pref.setOnPreferenceChangeListener(this);
+ pref.setKey(app.getKey());
+ pref.setIcon(app.getIcon());
+ pref.setTitle(app.getLabel());
+ if (app.isPolicyFixed()) {
+ pref.setSummary(getString(R.string.permission_summary_enforced_by_policy));
+ }
+ pref.setPersistent(false);
+ pref.setEnabled(!app.isPolicyFixed());
+ pref.setChecked(app.areRuntimePermissionsGranted());
+
+ if (isSystemApp && isTelevision) {
+ if (mExtraScreen == null) {
+ mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
+ }
+ mExtraScreen.addPreference(pref);
+ } else {
+ screen.addPreference(pref);
+ }
+ }
+
+ if (mExtraScreen != null) {
+ preferencesToRemove.remove(KEY_SHOW_SYSTEM_PREFS);
+ Preference pref = screen.findPreference(KEY_SHOW_SYSTEM_PREFS);
+
+ if (pref == null) {
+ pref = new Preference(context);
+ pref.setKey(KEY_SHOW_SYSTEM_PREFS);
+ pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc,
+ android.R.attr.colorControlNormal));
+ pref.setTitle(R.string.preference_show_system_apps);
+ pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ SystemAppsFragment frag = new SystemAppsFragment();
+ setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME));
+ frag.setTargetFragment(PermissionAppsFragment.this, 0);
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, frag)
+ .addToBackStack("SystemApps")
+ .commit();
+ return true;
+ }
+ });
+ screen.addPreference(pref);
+ }
+
+ int grantedCount = 0;
+ for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) {
+ if (((SwitchPreference) mExtraScreen.getPreference(i)).isChecked()) {
+ grantedCount++;
+ }
+ }
+ pref.setSummary(getString(R.string.app_permissions_group_summary,
+ grantedCount, mExtraScreen.getPreferenceCount()));
+ }
+
+ for (String key : preferencesToRemove) {
+ Preference pref = screen.findPreference(key);
+ if (pref != null) {
+ screen.removePreference(pref);
+ } else if (mExtraScreen != null) {
+ pref = mExtraScreen.findPreference(key);
+ if (pref != null) {
+ mExtraScreen.removePreference(pref);
+ }
+ }
+ }
+
+ setLoading(false /* loading */, true /* animate */);
+
+ if (mOnPermissionsLoadedListener != null) {
+ mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference preference, Object newValue) {
+ String pkg = preference.getKey();
+ final PermissionApp app = mPermissionApps.getApp(pkg);
+
+ if (app == null) {
+ return false;
+ }
+
+ OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
+ if (activity.isObscuredTouch()) {
+ activity.showOverlayDialog();
+ return false;
+ }
+
+ addToggledGroup(app.getPackageName(), app.getPermissionGroup());
+
+ if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
+ app.getPackageName())) {
+ LocationUtils.showLocationDialog(getContext(), app.getLabel());
+ return false;
+ }
+ if (newValue == Boolean.TRUE) {
+ app.grantRuntimePermissions();
+ } else {
+ final boolean grantedByDefault = app.hasGrantedByDefaultPermissions();
+ if (grantedByDefault || (!app.hasRuntimePermissions() && !mHasConfirmedRevoke)) {
+ new AlertDialog.Builder(getContext())
+ .setMessage(grantedByDefault ? R.string.system_warning
+ : R.string.old_sdk_deny_warning)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.grant_dialog_button_deny,
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ((SwitchPreference) preference).setChecked(false);
+ app.revokeRuntimePermissions();
+ if (!grantedByDefault) {
+ mHasConfirmedRevoke = true;
+ }
+ }
+ })
+ .show();
+ return false;
+ } else {
+ app.revokeRuntimePermissions();
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ logToggledGroups();
+ }
+
+ private void addToggledGroup(String packageName, AppPermissionGroup group) {
+ if (mToggledGroups == null) {
+ mToggledGroups = new ArrayMap<>();
+ }
+ // Double toggle is back to initial state.
+ if (mToggledGroups.containsKey(packageName)) {
+ mToggledGroups.remove(packageName);
+ } else {
+ mToggledGroups.put(packageName, group);
+ }
+ }
+
+ private void logToggledGroups() {
+ if (mToggledGroups != null) {
+ final int groupCount = mToggledGroups.size();
+ for (int i = 0; i < groupCount; i++) {
+ String packageName = mToggledGroups.keyAt(i);
+ List<AppPermissionGroup> groups = new ArrayList<>();
+ groups.add(mToggledGroups.valueAt(i));
+ SafetyNetLogger.logPermissionsToggled(packageName, groups);
+ }
+ mToggledGroups = null;
+ }
+ }
+
+ public static class SystemAppsFragment extends PermissionsFrameFragment implements Callback {
+ PermissionAppsFragment mOuterFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mOuterFragment = (PermissionAppsFragment) getTargetFragment();
+ setLoading(true /* loading */, false /* animate */);
+ super.onCreate(savedInstanceState);
+ if (mOuterFragment.mExtraScreen != null) {
+ setPreferenceScreen();
+ } else {
+ mOuterFragment.setOnPermissionsLoadedListener(this);
+ }
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
+ PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, null);
+ bindUi(this, permissionApps);
+ }
+
+ @Override
+ public void onPermissionsLoaded(PermissionApps permissionApps) {
+ setPreferenceScreen();
+ mOuterFragment.setOnPermissionsLoadedListener(null);
+ }
+
+ private void setPreferenceScreen() {
+ setPreferenceScreen(mOuterFragment.mExtraScreen);
+ setLoading(false /* loading */, true /* animate */);
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java b/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java
new file mode 100644
index 00000000..e7f63b23
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java
@@ -0,0 +1,121 @@
+/*
+ * 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.handheld;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.widget.ListView;
+import android.widget.TextView;
+import com.android.packageinstaller.R;
+
+public abstract class PermissionsFrameFragment extends PreferenceFragment {
+ private ViewGroup mPreferencesContainer;
+
+ private View mLoadingView;
+ private ViewGroup mPrefsView;
+ private boolean mIsLoading;
+
+ /**
+ * Returns the view group that holds the preferences objects. This will
+ * only be set after {@link #onCreateView} has been called.
+ */
+ protected final ViewGroup getPreferencesContainer() {
+ return mPreferencesContainer;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.permissions_frame, container,
+ false);
+ mPrefsView = (ViewGroup) rootView.findViewById(R.id.prefs_container);
+ if (mPrefsView == null) {
+ mPrefsView = rootView;
+ }
+ mLoadingView = rootView.findViewById(R.id.loading_container);
+ mPreferencesContainer = (ViewGroup) super.onCreateView(
+ inflater, mPrefsView, savedInstanceState);
+ setLoading(mIsLoading, false, true /* force */);
+ mPrefsView.addView(mPreferencesContainer);
+ return rootView;
+ }
+
+ protected void setLoading(boolean loading, boolean animate) {
+ setLoading(loading, animate, false);
+ }
+
+ private void setLoading(boolean loading, boolean animate, boolean force) {
+ if (mIsLoading != loading || force) {
+ mIsLoading = loading;
+ if (getView() == null) {
+ // If there is no created view, there is no reason to animate.
+ animate = false;
+ }
+ if (mPrefsView != null) {
+ setViewShown(mPrefsView, !loading, animate);
+ }
+ if (mLoadingView != null) {
+ setViewShown(mLoadingView, loading, animate);
+ }
+ }
+ }
+
+ @Override
+ public ListView getListView() {
+ ListView listView = super.getListView();
+ if (listView.getEmptyView() == null) {
+ TextView emptyView = (TextView) getView().findViewById(R.id.no_permissions);
+ listView.setEmptyView(emptyView);
+ }
+ return listView;
+ }
+
+ private void setViewShown(final View view, boolean shown, boolean animate) {
+ if (animate) {
+ Animation animation = AnimationUtils.loadAnimation(getContext(),
+ shown ? android.R.anim.fade_in : android.R.anim.fade_out);
+ if (shown) {
+ view.setVisibility(View.VISIBLE);
+ } else {
+ animation.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+ view.startAnimation(animation);
+ } else {
+ view.clearAnimation();
+ view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/SettingsWithHeader.java b/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java
index 7b58fed1..c15a4287 100644
--- a/src/com/android/packageinstaller/permission/ui/SettingsWithHeader.java
+++ b/src/com/android/packageinstaller/permission/ui/handheld/SettingsWithHeader.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.packageinstaller.permission.ui;
+package com.android.packageinstaller.permission.ui.handheld;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -26,6 +26,7 @@ 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;
@@ -42,7 +43,7 @@ public abstract class SettingsWithHeader extends PermissionsFrameFragment
Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
- if (!Utils.isTelevision(getContext())) {
+ if (!DeviceUtils.isTelevision(getContext())) {
mHeader = inflater.inflate(R.layout.header, root, false);
getPreferencesContainer().addView(mHeader, 0);
updateHeader();
@@ -81,5 +82,4 @@ public abstract class SettingsWithHeader extends PermissionsFrameFragment
public void onClick(View v) {
getActivity().startActivity(mInfoIntent);
}
-
}
diff --git a/src/com/android/packageinstaller/permission/ui/AllAppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java
index 2fb9a510..d4910128 100644
--- a/src/com/android/packageinstaller/permission/ui/AllAppPermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.packageinstaller.permission.ui;
+package com.android.packageinstaller.permission.ui.television;
import android.app.ActionBar;
import android.app.AlertDialog;
diff --git a/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java
index 6396c61e..42a2661c 100644
--- a/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.packageinstaller.permission.ui;
+package com.android.packageinstaller.permission.ui.television;
import android.annotation.Nullable;
import android.app.ActionBar;
@@ -50,6 +50,7 @@ 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;
diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsTvViewHandler.java b/src/com/android/packageinstaller/permission/ui/television/GrantPermissionsViewHandlerImpl.java
index 0e979ab6..a2538821 100644
--- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsTvViewHandler.java
+++ b/src/com/android/packageinstaller/permission/ui/television/GrantPermissionsViewHandlerImpl.java
@@ -1,4 +1,4 @@
-package com.android.packageinstaller.permission.ui;
+package com.android.packageinstaller.permission.ui.television;
import android.content.Context;
import android.graphics.PixelFormat;
@@ -15,11 +15,12 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler;
/**
* TV-specific view handler for the grant permissions activity.
*/
-final class GrantPermissionsTvViewHandler implements GrantPermissionsViewHandler, OnClickListener {
+public final class GrantPermissionsViewHandlerImpl implements GrantPermissionsViewHandler, OnClickListener {
private static final String ARG_GROUP_NAME = "ARG_GROUP_NAME";
@@ -37,12 +38,12 @@ final class GrantPermissionsTvViewHandler implements GrantPermissionsViewHandler
private Button mSoftDenyButton;
private Button mHardDenyButton;
- GrantPermissionsTvViewHandler(Context context) {
+ public GrantPermissionsViewHandlerImpl(Context context) {
mContext = context;
}
@Override
- public GrantPermissionsTvViewHandler setResultListener(ResultListener listener) {
+ public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) {
mResultListener = listener;
return this;
}
diff --git a/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java
index e5e06e09..47301f48 100644
--- a/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/ManagePermissionsFragment.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.packageinstaller.permission.ui;
+package com.android.packageinstaller.permission.ui.television;
import android.annotation.Nullable;
import android.app.ActionBar;
@@ -202,7 +202,7 @@ public final class ManagePermissionsFragment extends PermissionsFrameFragment
Preference extraScreenPreference = new Preference(context);
extraScreenPreference.setKey(EXTRA_PREFS_KEY);
extraScreenPreference.setIcon(Utils.applyTint(context,
- com.android.internal.R.drawable.ic_more_items,
+ R.drawable.ic_more_items,
android.R.attr.colorControlNormal));
extraScreenPreference.setTitle(R.string.additional_permissions);
extraScreenPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
diff --git a/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java
index 8dacd037..0f240bef 100644
--- a/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.packageinstaller.permission.ui;
+package com.android.packageinstaller.permission.ui.television;
import android.app.ActionBar;
import android.app.AlertDialog;
@@ -39,11 +39,13 @@ 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.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;
@@ -185,7 +187,7 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple
return;
}
- boolean isTelevision = Utils.isTelevision(context);
+ boolean isTelevision = DeviceUtils.isTelevision(context);
PreferenceScreen screen = getPreferenceScreen();
ArraySet<String> preferencesToRemove = new ArraySet<>();
diff --git a/src/com/android/packageinstaller/permission/ui/PermissionsFrameFragment.java b/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java
index 40058f6d..e81aee86 100644
--- a/src/com/android/packageinstaller/permission/ui/PermissionsFrameFragment.java
+++ b/src/com/android/packageinstaller/permission/ui/television/PermissionsFrameFragment.java
@@ -1,4 +1,20 @@
-package com.android.packageinstaller.permission.ui;
+/*
+ * 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.television;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -15,6 +31,7 @@ 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;
@@ -117,7 +134,7 @@ public abstract class PermissionsFrameFragment extends PreferenceFragment {
@Override
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
- if (Utils.isTelevision(getContext())) {
+ if (DeviceUtils.isTelevision(getContext())) {
mGridView = (VerticalGridView) inflater.inflate(
R.layout.leanback_preferences_list, parent, false);
mGridView.setWindowAlignmentOffset(0);
diff --git a/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java b/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java
new file mode 100644
index 00000000..4dae629c
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/television/SettingsWithHeader.java
@@ -0,0 +1,86 @@
+/*
+ * 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.television;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+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 {
+
+ private View mHeader;
+ protected Intent mInfoIntent;
+ protected Drawable mIcon;
+ protected CharSequence mLabel;
+
+ @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();
+ }
+
+ return root;
+ }
+
+ public void setHeader(Drawable icon, CharSequence label, Intent infoIntent) {
+ mIcon = icon;
+ mLabel = label;
+ mInfoIntent = infoIntent;
+ 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);
+
+ 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);
+ }
+ }
+ }
+
+ @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
new file mode 100644
index 00000000..aba97fc8
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/AppPermissionsFragmentWear.java
@@ -0,0 +1,335 @@
+/*
+* 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.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;
+import android.support.wearable.view.WearableListView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+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;
+import com.android.packageinstaller.permission.utils.SafetyNetLogger;
+import com.android.packageinstaller.permission.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class AppPermissionsFragmentWear extends TitledSettingsFragment {
+
+ private static final String LOG_TAG = "ManagePermsFragment";
+
+ private static final int WARNING_CONFIRMATION_REQUEST = 252;
+ private List<AppPermissionGroup> mToggledGroups;
+ private AppPermissions mAppPermissions;
+ private PermissionsSettingsAdapter mAdapter;
+
+ private boolean mHasConfirmedRevoke;
+
+ public static AppPermissionsFragmentWear newInstance(String packageName) {
+ return setPackageName(new AppPermissionsFragmentWear(), packageName);
+ }
+
+ private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
+ Bundle arguments = new Bundle();
+ arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+ fragment.setArguments(arguments);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
+ Activity activity = getActivity();
+ PackageManager pm = activity.getPackageManager();
+ PackageInfo packageInfo;
+
+ try {
+ packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
+ packageInfo = null;
+ }
+
+ if (packageInfo == null) {
+ Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
+ activity.finish();
+ return;
+ }
+
+ mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() {
+ @Override
+ public void run() {
+ getActivity().finish();
+ }
+ });
+
+ mAdapter = new PermissionsSettingsAdapter(getContext());
+
+ initializePermissionGroupList();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.settings, container, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mAppPermissions.refresh();
+
+ // Also refresh the UI
+ final int count = mAdapter.getItemCount();
+ for (int i = 0; i < count; ++i) {
+ updatePermissionGroupSetting(i);
+ }
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (mAppPermissions != null) {
+ initializeLayout(mAdapter);
+ bindHeader(mAppPermissions.getPackageInfo());
+ }
+ }
+
+ 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();
+ List<SettingsAdapter.Setting<AppPermissionGroup>> nonSystemGroups = new ArrayList<>();
+
+ final int count = groups.size();
+ for (int i = 0; i < count; ++i) {
+ final AppPermissionGroup group = groups.get(i);
+ if (!Utils.shouldShowPermission(group, packageName)) {
+ continue;
+ }
+
+ boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
+
+ SettingsAdapter.Setting<AppPermissionGroup> setting =
+ new SettingsAdapter.Setting<AppPermissionGroup>(
+ group.getLabel(),
+ getPermissionGroupIcon(group),
+ i);
+ setting.data = group;
+
+ // The UI shows System settings first, then non-system settings
+ if (isPlatform) {
+ mAdapter.addSetting(setting);
+ } else {
+ nonSystemGroups.add(setting);
+ }
+ }
+
+ // Now add the non-system settings to the end of the list
+ final int nonSystemCount = nonSystemGroups.size();
+ for (int i = 0; i < nonSystemCount; ++i) {
+ final SettingsAdapter.Setting<AppPermissionGroup> setting = nonSystemGroups.get(i);
+ mAdapter.addSetting(setting);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ logAndClearToggledGroups();
+ }
+
+ @Override
+ public void onClick(WearableListView.ViewHolder view) {
+ final int index = view.getPosition();
+ SettingsAdapter.Setting<AppPermissionGroup> setting = mAdapter.get(index);
+ final AppPermissionGroup group = setting.data;
+
+ if (group == null) {
+ Log.e(LOG_TAG, "Error: AppPermissionGroup is null");
+ return;
+ }
+
+ // The way WearableListView is designed, there is no way to avoid this click handler
+ // Since the policy is fixed, ignore the click as the user is not able to change the state
+ // of this permission group
+ if (group.isPolicyFixed()) {
+ return;
+ }
+
+ OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
+ if (activity.isObscuredTouch()) {
+ activity.showOverlayDialog();
+ return;
+ }
+
+ addToggledGroup(group);
+
+ if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
+ LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
+ return;
+ }
+
+ if (!group.areRuntimePermissionsGranted()) {
+ group.grantRuntimePermissions(false);
+ } else {
+ final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
+ if (grantedByDefault || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) {
+ Intent intent = new Intent(getActivity(), WarningConfirmationActivity.class);
+ intent.putExtra(WarningConfirmationActivity.EXTRA_WARNING_MESSAGE,
+ getString(grantedByDefault ?
+ R.string.system_warning : R.string.old_sdk_deny_warning));
+ intent.putExtra(WarningConfirmationActivity.EXTRA_INDEX, index);
+ startActivityForResult(intent, WARNING_CONFIRMATION_REQUEST);
+ } else {
+ group.revokeRuntimePermissions(false);
+ }
+ }
+
+ updatePermissionGroupSetting(index);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == WARNING_CONFIRMATION_REQUEST) {
+ if (resultCode == Activity.RESULT_OK) {
+ int index = data.getIntExtra(WarningConfirmationActivity.EXTRA_INDEX, -1);
+ if (index == -1) {
+ Log.e(LOG_TAG, "Warning confirmation request came back with no index.");
+ return;
+ }
+
+ SettingsAdapter.Setting<AppPermissionGroup> setting = mAdapter.get(index);
+ final AppPermissionGroup group = setting.data;
+ group.revokeRuntimePermissions(false);
+ if (!group.hasGrantedByDefaultPermission()) {
+ mHasConfirmedRevoke = true;
+ }
+
+ updatePermissionGroupSetting(index);
+ }
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ private void updatePermissionGroupSetting(int index) {
+ SettingsAdapter.Setting<AppPermissionGroup> setting = mAdapter.get(index);
+ AppPermissionGroup group = setting.data;
+ mAdapter.updateSetting(
+ index,
+ group.getLabel(),
+ getPermissionGroupIcon(group),
+ group);
+ }
+
+ private void addToggledGroup(AppPermissionGroup group) {
+ if (mToggledGroups == null) {
+ mToggledGroups = new ArrayList<>();
+ }
+ // Double toggle is back to initial state.
+ if (mToggledGroups.contains(group)) {
+ mToggledGroups.remove(group);
+ } else {
+ mToggledGroups.add(group);
+ }
+ }
+
+ private void logAndClearToggledGroups() {
+ if (mToggledGroups != null) {
+ String packageName = mAppPermissions.getPackageInfo().packageName;
+ SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups);
+ mToggledGroups = null;
+ }
+ }
+
+ private int getPermissionGroupIcon(AppPermissionGroup group) {
+ String groupName = group.getName();
+ boolean isEnabled = group.areRuntimePermissionsGranted();
+ int resId;
+
+ switch (groupName) {
+ case Manifest.permission_group.CALENDAR:
+ resId = isEnabled ? R.drawable.ic_permission_calendar
+ : R.drawable.ic_permission_calendardisable;
+ break;
+ case Manifest.permission_group.CAMERA:
+ resId = isEnabled ? R.drawable.ic_permission_camera
+ : R.drawable.ic_permission_cameradisable;
+ break;
+ case Manifest.permission_group.CONTACTS:
+ resId = isEnabled ? R.drawable.ic_permission_contact
+ : R.drawable.ic_permission_contactdisable;
+ break;
+ case Manifest.permission_group.LOCATION:
+ resId = isEnabled ? R.drawable.ic_permission_location
+ : R.drawable.ic_permission_locationdisable;
+ break;
+ case Manifest.permission_group.MICROPHONE:
+ resId = isEnabled ? R.drawable.ic_permission_mic
+ : R.drawable.ic_permission_micdisable;
+ break;
+ case Manifest.permission_group.PHONE:
+ resId = isEnabled ? R.drawable.ic_permission_call
+ : R.drawable.ic_permission_calldisable;
+ break;
+ case Manifest.permission_group.SENSORS:
+ resId = isEnabled ? R.drawable.ic_permission_sensor
+ : R.drawable.ic_permission_sensordisable;
+ break;
+ case Manifest.permission_group.SMS:
+ resId = isEnabled ? R.drawable.ic_permission_sms
+ : R.drawable.ic_permission_smsdisable;
+ break;
+ case Manifest.permission_group.STORAGE:
+ resId = isEnabled ? R.drawable.ic_permission_storage
+ : R.drawable.ic_permission_storagedisable;
+ break;
+ default:
+ resId = isEnabled ? R.drawable.ic_permission_shield
+ : R.drawable.ic_permission_shielddisable;
+ }
+
+ return resId;
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/wear/ConfirmationViewHandler.java b/src/com/android/packageinstaller/permission/ui/wear/ConfirmationViewHandler.java
new file mode 100644
index 00000000..1c55e1bd
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/ConfirmationViewHandler.java
@@ -0,0 +1,381 @@
+package com.android.packageinstaller.permission.ui.wear;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+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;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.android.packageinstaller.R;
+
+public abstract class ConfirmationViewHandler implements
+ Handler.Callback,
+ View.OnClickListener,
+ ViewTreeObserver.OnScrollChangedListener,
+ ViewTreeObserver.OnGlobalLayoutListener {
+ private static final String TAG = "ConfirmationViewHandler";
+
+ public static final int MODE_HORIZONTAL_BUTTONS = 0;
+ public static final int MODE_VERTICAL_BUTTONS = 1;
+
+ private static final int MSG_SHOW_BUTTON_BAR = 1001;
+ private static final int MSG_HIDE_BUTTON_BAR = 1002;
+ private static final long HIDE_ANIM_DURATION = 500;
+
+ private View mRoot;
+ private TextView mCurrentPageText;
+ private ImageView mIcon;
+ private TextView mMessage;
+ private ScrollView mScrollingContainer;
+ private ViewGroup mContent;
+ private ViewGroup mHorizontalButtonBar;
+ private ViewGroup mVerticalButtonBar;
+ private Button mVerticalButton1;
+ private Button mVerticalButton2;
+ private Button mVerticalButton3;
+ private View mButtonBarContainer;
+
+ private Context mContext;
+
+ private Handler mHideHandler;
+ private Interpolator mInterpolator;
+ private float mButtonBarFloatingHeight;
+ private ObjectAnimator mButtonBarAnimator;
+ private float mCurrentTranslation;
+ private boolean mHiddenBefore;
+
+ // TODO: Move these into a builder
+ /** In the 2 button layout, this is allow button */
+ public abstract void onButton1();
+ /** In the 2 button layout, this is deny button */
+ public abstract void onButton2();
+ public abstract void onButton3();
+ public abstract CharSequence getVerticalButton1Text();
+ public abstract CharSequence getVerticalButton2Text();
+ public abstract CharSequence getVerticalButton3Text();
+ public abstract Drawable getVerticalButton1Icon();
+ public abstract Drawable getVerticalButton2Icon();
+ public abstract Drawable getVerticalButton3Icon();
+ public abstract CharSequence getCurrentPageText();
+ public abstract Icon getPermissionIcon();
+ public abstract CharSequence getMessage();
+
+ public ConfirmationViewHandler(Context context) {
+ mContext = context;
+ }
+
+ public View createView() {
+ mRoot = LayoutInflater.from(mContext).inflate(R.layout.confirmation_dialog, null);
+
+ mMessage = (TextView) mRoot.findViewById(R.id.message);
+ mCurrentPageText = (TextView) mRoot.findViewById(R.id.current_page_text);
+ mIcon = (ImageView) mRoot.findViewById(R.id.icon);
+ mButtonBarContainer = mRoot.findViewById(R.id.button_bar_container);
+ mContent = (ViewGroup) mRoot.findViewById(R.id.content);
+ mScrollingContainer = (ScrollView) mRoot.findViewById(R.id.scrolling_container);
+ mHorizontalButtonBar = (ViewGroup) mRoot.findViewById(R.id.horizontal_button_bar);
+ mVerticalButtonBar = (ViewGroup) mRoot.findViewById(R.id.vertical_button_bar);
+
+ Button horizontalAllow = (Button) mRoot.findViewById(R.id.permission_allow_button);
+ Button horizontalDeny = (Button) mRoot.findViewById(R.id.permission_deny_button);
+ horizontalAllow.setOnClickListener(this);
+ horizontalDeny.setOnClickListener(this);
+
+ mVerticalButton1 = (Button) mRoot.findViewById(R.id.vertical_button1);
+ mVerticalButton2 = (Button) mRoot.findViewById(R.id.vertical_button2);
+ mVerticalButton3 = (Button) mRoot.findViewById(R.id.vertical_button3);
+ mVerticalButton1.setOnClickListener(this);
+ mVerticalButton2.setOnClickListener(this);
+ mVerticalButton3.setOnClickListener(this);
+
+ mInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_slow_in);
+ mButtonBarFloatingHeight = mContext.getResources().getDimension(
+ R.dimen.conf_diag_floating_height);
+ mHideHandler = new Handler(Looper.getMainLooper(), this);
+
+ mScrollingContainer.getViewTreeObserver().addOnScrollChangedListener(this);
+ mRoot.getViewTreeObserver().addOnGlobalLayoutListener(this);
+
+ return mRoot;
+ }
+
+ /**
+ * Child class should override this for other modes. Call invalidate() to update the UI to the
+ * new button mode.
+ * @return The current mode the layout should use for the buttons
+ */
+ public int getButtonBarMode() {
+ return MODE_HORIZONTAL_BUTTONS;
+ }
+
+ public void invalidate() {
+ CharSequence currentPageText = getCurrentPageText();
+ if (!TextUtils.isEmpty(currentPageText)) {
+ mCurrentPageText.setText(currentPageText);
+ mCurrentPageText.setVisibility(View.VISIBLE);
+ } else {
+ mCurrentPageText.setVisibility(View.GONE);
+ }
+
+ Icon icon = getPermissionIcon();
+ if (icon != null) {
+ mIcon.setImageIcon(icon);
+ mIcon.setVisibility(View.VISIBLE);
+ } else {
+ mIcon.setVisibility(View.GONE);
+ }
+ mMessage.setText(getMessage());
+
+ switch (getButtonBarMode()) {
+ case MODE_HORIZONTAL_BUTTONS:
+ mHorizontalButtonBar.setVisibility(View.VISIBLE);
+ mVerticalButtonBar.setVisibility(View.GONE);
+ break;
+ case MODE_VERTICAL_BUTTONS:
+ mHorizontalButtonBar.setVisibility(View.GONE);
+ mVerticalButtonBar.setVisibility(View.VISIBLE);
+
+ mVerticalButton1.setText(getVerticalButton1Text());
+ mVerticalButton2.setText(getVerticalButton2Text());
+
+ mVerticalButton1.setCompoundDrawablesWithIntrinsicBounds(
+ getVerticalButton1Icon(), null, null, null);
+ mVerticalButton2.setCompoundDrawablesWithIntrinsicBounds(
+ getVerticalButton2Icon(), null, null, null);
+
+ CharSequence verticalButton3Text = getVerticalButton3Text();
+ if (TextUtils.isEmpty(verticalButton3Text)) {
+ mVerticalButton3.setVisibility(View.GONE);
+ } else {
+ mVerticalButton3.setText(getVerticalButton3Text());
+ mVerticalButton3.setCompoundDrawablesWithIntrinsicBounds(
+ getVerticalButton3Icon(), null, null, null);
+ }
+ break;
+ }
+
+ mScrollingContainer.scrollTo(0, 0);
+
+ mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR);
+ mHideHandler.removeMessages(MSG_SHOW_BUTTON_BAR);
+ }
+
+ @Override
+ public void onGlobalLayout() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onGlobalLayout");
+ Log.d(TAG, " contentHeight: " + mContent.getHeight());
+ }
+
+ if (mButtonBarAnimator != null) {
+ mButtonBarAnimator.cancel();
+ }
+
+ // In order to fake the buttons peeking at the bottom, need to do set the
+ // padding properly.
+ if (mContent.getPaddingBottom() != 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());
+ }
+ }
+
+ mButtonBarContainer.setTranslationY(mButtonBarContainer.getHeight());
+
+ // Give everything a chance to render
+ mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR);
+ mHideHandler.removeMessages(MSG_SHOW_BUTTON_BAR);
+ mHideHandler.sendEmptyMessageDelayed(MSG_SHOW_BUTTON_BAR, 50);
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ switch (id) {
+ case R.id.permission_allow_button:
+ case R.id.vertical_button1:
+ onButton1();
+ break;
+ case R.id.permission_deny_button:
+ case R.id.vertical_button2:
+ onButton2();
+ break;
+ case R.id.vertical_button3:
+ onButton3();
+ break;
+ }
+ }
+
+ @Override
+ public boolean handleMessage (Message msg) {
+ switch (msg.what) {
+ case MSG_SHOW_BUTTON_BAR:
+ showButtonBar();
+ return true;
+ case MSG_HIDE_BUTTON_BAR:
+ hideButtonBar();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onScrollChanged () {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onScrollChanged");
+ }
+ mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR);
+ hideButtonBar();
+ }
+
+ private void showButtonBar() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "showButtonBar");
+ }
+
+ // Setup Button animation.
+ // pop the button bar back to full height, stop all animation
+ if (mButtonBarAnimator != null) {
+ mButtonBarAnimator.cancel();
+ }
+
+ // stop any calls to hide the button bar in the future
+ mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR);
+ mHiddenBefore = false;
+
+ // 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, halfScreenHeight);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, " screenHeight: " + screenHeight);
+ Log.d(TAG, " contentHeight: " + contentHeight);
+ Log.d(TAG, " buttonBarHeight: " + buttonBarHeight);
+ Log.d(TAG, " buttonBarMaxHeight: " + buttonBarMaxHeight);
+ }
+
+ mButtonBarContainer.setTranslationZ(mButtonBarFloatingHeight);
+
+ // Only hide the button bar if it is occluding the content or the button bar is bigger than
+ // half the screen
+ if (contentHeight > (screenHeight - buttonBarHeight)
+ || buttonBarHeight > halfScreenHeight) {
+ mHideHandler.sendEmptyMessageDelayed(MSG_HIDE_BUTTON_BAR, 3000);
+ }
+
+ generateButtonBarAnimator(buttonBarHeight,
+ buttonBarHeight - buttonBarMaxHeight, 0, mButtonBarFloatingHeight, 1000);
+ }
+
+ private void hideButtonBar() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "hideButtonBar");
+ }
+
+ // The desired margin space between the button bar and the bottom of the dialog text
+ final int topMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.conf_diag_button_container_top_margin);
+ final int contentHeight = mContent.getHeight() + topMargin;
+ final int screenHeight = mRoot.getHeight();
+ final int buttonBarHeight = mButtonBarContainer.getHeight();
+
+ final int offset = screenHeight + buttonBarHeight
+ - contentHeight + Math.max(mScrollingContainer.getScrollY(), 0);
+ final int translationY = (offset > 0 ?
+ 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());
+ Log.d(TAG, " translationY: " + translationY);
+ }
+
+ 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,
+ mButtonBarFloatingHeight, 0, HIDE_ANIM_DURATION);
+ } else if (mButtonBarAnimator.isRunning()) {
+ // we are animating the button bar closing, change to animate to the right place
+ if (Math.abs(mCurrentTranslation - translationY) > 1e-2f) {
+ mButtonBarAnimator.cancel(); // stop current animation
+
+ if (Math.abs(mButtonBarContainer.getTranslationY() - translationY) > 1e-2f) {
+ long duration = Math.max((long) (
+ (float) HIDE_ANIM_DURATION
+ * (translationY - mButtonBarContainer.getTranslationY())
+ / mButtonBarContainer.getHeight()), 0);
+
+ generateButtonBarAnimator(
+ mButtonBarContainer.getTranslationY(), translationY,
+ mButtonBarFloatingHeight, 0, duration);
+ } else {
+ mButtonBarContainer.setTranslationY(translationY);
+ mButtonBarContainer.setTranslationZ(0);
+ }
+ }
+ } else {
+ // not currently animating, have already hidden, snap to the right offset
+ mButtonBarContainer.setTranslationY(translationY);
+ mButtonBarContainer.setTranslationZ(0);
+ }
+
+ mHiddenBefore = true;
+ }
+
+ 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,
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, startY, endY),
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_Z, startZ, endZ));
+ mCurrentTranslation = endY;
+ mButtonBarAnimator.setDuration(duration);
+ mButtonBarAnimator.setInterpolator(mInterpolator);
+ mButtonBarAnimator.start();
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java b/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java
new file mode 100644
index 00000000..ef7efb28
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/TitledSettingsFragment.java
@@ -0,0 +1,234 @@
+/*
+ * 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.wear;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.widget.RecyclerView;
+import android.support.wearable.view.WearableListView;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.packageinstaller.permission.ui.wear.settings.ViewUtils;
+import com.android.packageinstaller.R;
+
+/**
+ * Base settings Fragment that shows a title at the top of the page.
+ */
+public abstract class TitledSettingsFragment extends Fragment implements
+ View.OnLayoutChangeListener, WearableListView.ClickListener {
+
+ private static final int ITEM_CHANGE_DURATION_MS = 120;
+
+ private static final String TAG = "TitledSettingsFragment";
+ private int mInitialHeaderHeight;
+
+ protected TextView mHeader;
+ protected WearableListView mWheel;
+
+ private int mCharLimitShortTitle;
+ private int mCharLimitLine;
+ private int mChinOffset;
+
+ private TextWatcher mHeaderTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ adjustHeaderSize();
+ }
+ };
+
+ private void adjustHeaderTranslation() {
+ int translation = 0;
+ if (mWheel.getChildCount() > 0) {
+ translation = mWheel.getCentralViewTop() - mWheel.getChildAt(0).getTop();
+ }
+
+ float newTranslation = Math.min(Math.max(-mInitialHeaderHeight, -translation), 0);
+
+ int position = mWheel.getChildAdapterPosition(mWheel.getChildAt(0));
+ if (position == 0 || newTranslation < 0) {
+ mHeader.setTranslationY(newTranslation);
+ }
+ }
+
+ @Override
+ public void onTopEmptyRegionClick() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCharLimitShortTitle = getResources().getInteger(R.integer.short_title_length);
+ mCharLimitLine = getResources().getInteger(R.integer.char_limit_per_line);
+ }
+
+ @Override
+ public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ if (view == mHeader) {
+ mInitialHeaderHeight = bottom - top;
+ if (ViewUtils.getIsCircular(getContext())) {
+ // We are adding more margin on circular screens, so we need to account for it and use
+ // it for hiding the header.
+ mInitialHeaderHeight +=
+ ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).topMargin;
+ }
+ } else if (view == mWheel) {
+ adjustHeaderTranslation();
+ }
+ }
+
+ protected void initializeLayout(RecyclerView.Adapter adapter) {
+ View v = getView();
+ mWheel = (WearableListView) v.findViewById(R.id.wheel);
+
+ mHeader = (TextView) v.findViewById(R.id.header);
+ mHeader.addOnLayoutChangeListener(this);
+ mHeader.addTextChangedListener(mHeaderTextWatcher);
+
+ mWheel.setAdapter(adapter);
+ mWheel.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ adjustHeaderTranslation();
+ }
+ });
+ mWheel.setClickListener(this);
+ mWheel.addOnLayoutChangeListener(this);
+
+ // Decrease item change animation duration to approximately half of the default duration.
+ RecyclerView.ItemAnimator itemAnimator = mWheel.getItemAnimator();
+ itemAnimator.setChangeDuration(ITEM_CHANGE_DURATION_MS);
+
+ adjustHeaderSize();
+
+ positionOnCircular(getContext(), mHeader, mWheel);
+ }
+
+ public void positionOnCircular(Context context, View header, final ViewGroup wheel) {
+ if (ViewUtils.getIsCircular(context)) {
+ FrameLayout.LayoutParams params =
+ (FrameLayout.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
+ // the smaller value to maximize usable width.
+ final int margin = (int) Math.min(context.getResources().getDimension(
+ R.dimen.round_content_padding_left), context.getResources().getDimension(
+ R.dimen.round_content_padding_right));
+ params.leftMargin = margin;
+ params.rightMargin = margin;
+ params.gravity = Gravity.CENTER_HORIZONTAL;
+ header.setLayoutParams(params);
+
+ if (header instanceof TextView) {
+ ((TextView) header).setGravity(Gravity.CENTER);
+ }
+
+ final int leftPadding = (int) context.getResources().getDimension(
+ R.dimen.round_content_padding_left);
+ final int rightPadding = (int) context.getResources().getDimension(
+ R.dimen.round_content_padding_right);
+ final int topPadding = (int) context.getResources().getDimension(
+ R.dimen.settings_wearable_list_view_vertical_padding_round);
+ final int bottomPadding = (int) context.getResources().getDimension(
+ R.dimen.settings_wearable_list_view_vertical_padding_round);
+ wheel.setPadding(leftPadding, topPadding, rightPadding, mChinOffset + bottomPadding);
+ wheel.setClipToPadding(false);
+
+ wheel.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+ @Override
+ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+ mChinOffset = insets.getSystemWindowInsetBottom();
+ wheel.setPadding(leftPadding, topPadding, rightPadding,
+ mChinOffset + bottomPadding);
+ // This listener is invoked after each time we navigate to SettingsActivity and
+ // it keeps adding padding. We need to disable it after the first update.
+ v.setOnApplyWindowInsetsListener(null);
+ return insets.consumeSystemWindowInsets();
+ }
+ });
+ } else {
+ int leftPadding = (int) context.getResources().getDimension(
+ R.dimen.content_padding_left);
+ wheel.setPadding(leftPadding, wheel.getPaddingTop(), wheel.getPaddingRight(),
+ wheel.getPaddingBottom());
+ }
+ }
+
+ private void adjustHeaderSize() {
+ int length = mHeader.length();
+
+ if (length <= mCharLimitShortTitle) {
+ mHeader.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getResources().getDimensionPixelSize(
+ R.dimen.setting_short_header_text_size));
+ } else {
+ mHeader.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getResources().getDimensionPixelSize(
+ R.dimen.setting_long_header_text_size));
+ }
+
+ boolean singleLine = length <= mCharLimitLine;
+
+ float height = getResources().getDimension(R.dimen.settings_header_base_height);
+ if (!singleLine) {
+ height += getResources().getDimension(R.dimen.setting_header_extra_line_height);
+ }
+ mHeader.setMinHeight((int) height);
+
+ FrameLayout.LayoutParams params = (FrameLayout.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.
+ if (ViewUtils.getIsCircular(context)) {
+ params.topMargin = getResources().getDimensionPixelSize(
+ R.dimen.settings_header_top_margin_circular_multiline);
+ } else {
+ params.topMargin = getResources().getDimensionPixelSize(
+ R.dimen.settings_header_top_margin_multiline);
+ }
+ } else {
+ if (ViewUtils.getIsCircular(context)) {
+ params.topMargin = getResources().getDimensionPixelSize(
+ R.dimen.settings_header_top_margin_circular);
+ } else {
+ params.topMargin = getResources().getDimensionPixelSize(
+ R.dimen.settings_header_top_margin);
+ }
+ }
+ mHeader.setLayoutParams(params);
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java b/src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java
new file mode 100644
index 00000000..03713419
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/WarningConfirmationActivity.java
@@ -0,0 +1,118 @@
+/*
+* 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.wear;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+
+import com.android.packageinstaller.R;
+
+public final class WarningConfirmationActivity extends Activity {
+ public final static String EXTRA_WARNING_MESSAGE = "EXTRA_WARNING_MESSAGE";
+ // Saved index that will be returned in the onActivityResult() callback
+ public final static String EXTRA_INDEX = "EXTRA_INDEX";
+
+ private ConfirmationViewHandler mViewHandler;
+ private String mMessage;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mMessage = getIntent().getStringExtra(EXTRA_WARNING_MESSAGE);
+
+ mViewHandler = new ConfirmationViewHandler(this) {
+ @Override // ConfirmationViewHandler
+ public int getButtonBarMode() {
+ return MODE_VERTICAL_BUTTONS;
+ }
+
+ @Override
+ public void onButton1() {
+ setResultAndFinish(Activity.RESULT_CANCELED);
+ }
+
+ @Override
+ public void onButton2() {
+ setResultAndFinish(Activity.RESULT_OK);
+ }
+
+ @Override
+ public void onButton3() {
+ // no-op
+ }
+
+ @Override
+ public CharSequence getVerticalButton1Text() {
+ return getString(R.string.cancel);
+ }
+
+ @Override
+ public CharSequence getVerticalButton2Text() {
+ return getString(R.string.grant_dialog_button_deny);
+ }
+
+ @Override
+ public CharSequence getVerticalButton3Text() {
+ return null;
+ }
+
+ @Override
+ public Drawable getVerticalButton1Icon() {
+ return getDrawable(R.drawable.cancel_button);
+ }
+
+ @Override
+ public Drawable getVerticalButton2Icon() {
+ return getDrawable(R.drawable.confirm_button);
+ }
+
+ @Override
+ public Drawable getVerticalButton3Icon() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getCurrentPageText() {
+ return null;
+ }
+
+ @Override
+ public Icon getPermissionIcon() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+ };
+
+ setContentView(mViewHandler.createView());
+ mViewHandler.invalidate();
+ }
+
+ private void setResultAndFinish(int result) {
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_INDEX, getIntent().getIntExtra(EXTRA_INDEX, -1));
+ setResult(result, intent);
+ finish();
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/wear/settings/ExtendedOnCenterProximityListener.java b/src/com/android/packageinstaller/permission/ui/wear/settings/ExtendedOnCenterProximityListener.java
new file mode 100644
index 00000000..02c203b3
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/settings/ExtendedOnCenterProximityListener.java
@@ -0,0 +1,30 @@
+/*
+ * 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.wear.settings;
+
+import android.support.wearable.view.WearableListView;
+
+public interface ExtendedOnCenterProximityListener
+ extends WearableListView.OnCenterProximityListener {
+ float getProximityMinValue();
+
+ float getProximityMaxValue();
+
+ float getCurrentProximityValue();
+
+ void setScalingAnimatorValue(float value);
+}
diff --git a/src/com/android/packageinstaller/permission/ui/wear/settings/ExtendedViewHolder.java b/src/com/android/packageinstaller/permission/ui/wear/settings/ExtendedViewHolder.java
new file mode 100644
index 00000000..6b725419
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/settings/ExtendedViewHolder.java
@@ -0,0 +1,84 @@
+/*
+ * 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.wear.settings;
+
+import android.animation.ObjectAnimator;
+import android.support.wearable.view.WearableListView;
+import android.view.View;
+
+
+public class ExtendedViewHolder extends WearableListView.ViewHolder {
+ public static final long DEFAULT_ANIMATION_DURATION = 150;
+
+ private ObjectAnimator mScalingUpAnimator;
+
+ private ObjectAnimator mScalingDownAnimator;
+
+ private float mMinValue;
+
+ private float mMaxValue;
+
+ public ExtendedViewHolder(View itemView) {
+ super(itemView);
+ if (itemView instanceof ExtendedOnCenterProximityListener) {
+ ExtendedOnCenterProximityListener item =
+ (ExtendedOnCenterProximityListener) itemView;
+ mMinValue = item.getProximityMinValue();
+ item.setScalingAnimatorValue(mMinValue);
+ mMaxValue = item.getProximityMaxValue();
+ mScalingUpAnimator = ObjectAnimator.ofFloat(item, "scalingAnimatorValue", mMinValue,
+ mMaxValue);
+ mScalingUpAnimator.setDuration(DEFAULT_ANIMATION_DURATION);
+ mScalingDownAnimator = ObjectAnimator.ofFloat(item, "scalingAnimatorValue",
+ mMaxValue, mMinValue);
+ mScalingDownAnimator.setDuration(DEFAULT_ANIMATION_DURATION);
+ }
+ }
+
+ public void onCenterProximity(boolean isCentralItem, boolean animate) {
+ if (!(itemView instanceof ExtendedOnCenterProximityListener)) {
+ return;
+ }
+ ExtendedOnCenterProximityListener item = (ExtendedOnCenterProximityListener) itemView;
+ if (isCentralItem) {
+ if (animate) {
+ mScalingDownAnimator.cancel();
+ if (!mScalingUpAnimator.isRunning()) {
+ mScalingUpAnimator.setFloatValues(item.getCurrentProximityValue(),
+ mMaxValue);
+ mScalingUpAnimator.start();
+ }
+ } else {
+ mScalingUpAnimator.cancel();
+ item.setScalingAnimatorValue(item.getProximityMaxValue());
+ }
+ } else {
+ mScalingUpAnimator.cancel();
+ if (animate) {
+ if (!mScalingDownAnimator.isRunning()) {
+ mScalingDownAnimator.setFloatValues(item.getCurrentProximityValue(),
+ mMinValue);
+ mScalingDownAnimator.start();
+ }
+ } else {
+ mScalingDownAnimator.cancel();
+ item.setScalingAnimatorValue(item.getProximityMinValue());
+ }
+ }
+ super.onCenterProximity(isCentralItem, animate);
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/wear/settings/PermissionsSettingsAdapter.java b/src/com/android/packageinstaller/permission/ui/wear/settings/PermissionsSettingsAdapter.java
new file mode 100644
index 00000000..0e0adcbb
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/settings/PermissionsSettingsAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.wear.settings;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.wearable.view.WearableListView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.model.AppPermissionGroup;
+
+public final class PermissionsSettingsAdapter extends SettingsAdapter<AppPermissionGroup> {
+ private Resources mRes;
+
+ public PermissionsSettingsAdapter(Context context) {
+ super(context, R.layout.permissions_settings_item);
+ mRes = context.getResources();
+ }
+
+ @Override
+ public WearableListView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new PermissionsViewHolder(new PermissionsSettingsItem(parent.getContext()));
+ }
+
+ @Override
+ public void onBindViewHolder(WearableListView.ViewHolder holder, int position) {
+ super.onBindViewHolder(holder, position);
+ PermissionsViewHolder viewHolder = (PermissionsViewHolder) holder;
+ AppPermissionGroup group = get(position).data;
+
+ if (group.isPolicyFixed()) {
+ viewHolder.imageView.setEnabled(false);
+ viewHolder.textView.setEnabled(false);
+ viewHolder.state.setEnabled(false);
+ viewHolder.state.setText(
+ mRes.getString(R.string.permission_summary_enforced_by_policy));
+ } else {
+ viewHolder.imageView.setEnabled(true);
+ viewHolder.textView.setEnabled(true);
+ viewHolder.state.setEnabled(true);
+
+ if (group.areRuntimePermissionsGranted()) {
+ viewHolder.state.setText(R.string.generic_enabled);
+ } else {
+ viewHolder.state.setText(R.string.generic_disabled);
+ }
+ }
+ }
+
+ private static final class PermissionsViewHolder extends SettingsAdapter.SettingsItemHolder {
+ public final TextView state;
+
+ public PermissionsViewHolder(View view) {
+ super(view);
+ state = (TextView) view.findViewById(R.id.state);
+ }
+ }
+
+ private class PermissionsSettingsItem extends SettingsItem {
+ private final TextView mState;
+ private final float mCenteredAlpha = 1.0f;
+ private final float mNonCenteredAlpha = 0.5f;
+
+ public PermissionsSettingsItem (Context context) {
+ super(context);
+ mState = (TextView) findViewById(R.id.state);
+ }
+
+ @Override
+ public void onCenterPosition(boolean animate) {
+ mImage.setAlpha(mImage.isEnabled() ? mCenteredAlpha : mNonCenteredAlpha);
+ mText.setAlpha(mText.isEnabled() ? mCenteredAlpha : mNonCenteredAlpha);
+ mState.setAlpha(mState.isEnabled() ? mCenteredAlpha : mNonCenteredAlpha);
+ }
+
+ @Override
+ public void onNonCenterPosition(boolean animate) {
+ mImage.setAlpha(mNonCenteredAlpha);
+ mText.setAlpha(mNonCenteredAlpha);
+ mState.setAlpha(mNonCenteredAlpha);
+ }
+ }
+}
+
diff --git a/src/com/android/packageinstaller/permission/ui/wear/settings/SettingsAdapter.java b/src/com/android/packageinstaller/permission/ui/wear/settings/SettingsAdapter.java
new file mode 100644
index 00000000..baf1a2b4
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/settings/SettingsAdapter.java
@@ -0,0 +1,276 @@
+/*
+ * 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.wear.settings;
+
+import android.content.Context;
+import android.support.wearable.view.CircledImageView;
+import android.support.wearable.view.WearableListView;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.packageinstaller.R;
+
+import java.util.ArrayList;
+
+/**
+ * Common adapter for settings views. Maintains a list of 'Settings', consisting of a name,
+ * icon and optional activity-specific data.
+ */
+public class SettingsAdapter<T> extends WearableListView.Adapter {
+ private static final String TAG = "SettingsAdapter";
+ private final Context mContext;
+
+ public static final class Setting<S> {
+ public static final int ID_INVALID = -1;
+
+ public final int id;
+ public int nameResourceId;
+ public CharSequence name;
+ public int iconResource;
+ public boolean inProgress;
+ public S data;
+
+ public Setting(CharSequence name, int iconResource, S data) {
+ this(name, iconResource, data, ID_INVALID);
+ }
+
+ public Setting(CharSequence name, int iconResource, S data, int id) {
+ this.name = name;
+ this.iconResource = iconResource;
+ this.data = data;
+ this.inProgress = false;
+ this.id = id;
+ }
+
+ public Setting(int nameResource, int iconResource, S data, int id) {
+ this.nameResourceId = nameResource;
+ this.iconResource = iconResource;
+ this.data = data;
+ this.inProgress = false;
+ this.id = id;
+ }
+
+ public Setting(int nameResource, int iconResource, int id) {
+ this.nameResourceId = nameResource;
+ this.iconResource = iconResource;
+ this.data = null;
+ this.inProgress = false;
+ this.id = id;
+ }
+
+ public Setting(CharSequence name, int iconResource, int id) {
+ this(name, iconResource, null, id);
+ }
+
+ }
+
+ private final int mItemLayoutId;
+ private final float mDefaultCircleRadiusPercent;
+ private final float mSelectedCircleRadiusPercent;
+
+ protected ArrayList<Setting<T>> mSettings = new ArrayList<Setting<T>>();
+
+ public SettingsAdapter(Context context, int itemLayoutId) {
+ mContext = context;
+ mItemLayoutId = itemLayoutId;
+ mDefaultCircleRadiusPercent = context.getResources().getFraction(
+ R.dimen.default_settings_circle_radius_percent, 1, 1);
+ mSelectedCircleRadiusPercent = context.getResources().getFraction(
+ R.dimen.selected_settings_circle_radius_percent, 1, 1);
+ }
+
+ @Override
+ public WearableListView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new SettingsItemHolder(new SettingsItem(parent.getContext()));
+ }
+
+ @Override
+ public void onBindViewHolder(WearableListView.ViewHolder holder, int position) {
+ Setting<T> setting = mSettings.get(position);
+ if (setting.iconResource == -1) {
+ ((SettingsItemHolder) holder).imageView.setVisibility(View.GONE);
+ } else {
+ ((SettingsItemHolder) holder).imageView.setVisibility(View.VISIBLE);
+ ((SettingsItemHolder) holder).imageView.setImageResource(
+ mSettings.get(position).iconResource);
+ }
+ Log.d(TAG, "onBindViewHolder " + setting.name + " " + setting.id + " " + setting
+ .nameResourceId);
+ if (setting.name == null && setting.nameResourceId != 0) {
+ setting.name = mContext.getString(setting.nameResourceId);
+ }
+ ((SettingsItemHolder) holder).textView.setText(setting.name);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSettings.size();
+ }
+
+ public void addSetting(CharSequence name, int iconResource) {
+ addSetting(name, iconResource, null);
+ }
+
+ public void addSetting(CharSequence name, int iconResource, T intent) {
+ addSetting(mSettings.size(), name, iconResource, intent);
+ }
+
+ public void addSetting(int index, CharSequence name, int iconResource, T intent) {
+ addSetting(Setting.ID_INVALID, index, name, iconResource, intent);
+ }
+
+ public void addSetting(int id, int index, CharSequence name, int iconResource, T intent) {
+ mSettings.add(index, new Setting<T>(name, iconResource, intent, id));
+ notifyItemInserted(index);
+ }
+
+ public void addSettingDontNotify(Setting<T> setting) {
+ mSettings.add(setting);
+ }
+
+ public void addSetting(Setting<T> setting) {
+ mSettings.add(setting);
+ notifyItemInserted(mSettings.size() - 1);
+ }
+
+ public void addSetting(int index, Setting<T> setting) {
+ mSettings.add(index, setting);
+ notifyItemInserted(index);
+ }
+
+ /**
+ * Returns the index of the setting in the adapter based on the ID supplied when it was
+ * originally added.
+ * @param id the setting's id
+ * @return index in the adapter of the setting. -1 if not found.
+ */
+ public int findSetting(int id) {
+ for (int i = mSettings.size() - 1; i >= 0; --i) {
+ Setting setting = mSettings.get(i);
+
+ if (setting.id == id) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Removes a setting at the given index.
+ * @param index the index of the setting to be removed
+ */
+ public void removeSetting(int index) {
+ mSettings.remove(index);
+ notifyDataSetChanged();
+ }
+
+ public void clearSettings() {
+ mSettings.clear();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Updates a setting in place.
+ * @param index the index of the setting
+ * @param name the updated setting name
+ * @param iconResource the update setting icon
+ * @param intent the updated intent for the setting
+ */
+ public void updateSetting(int index, CharSequence name, int iconResource, T intent) {
+ Setting<T> setting = mSettings.get(index);
+ setting.iconResource = iconResource;
+ setting.name = name;
+ setting.data = intent;
+ notifyItemChanged(index);
+ }
+
+ public Setting<T> get(int position) {
+ return mSettings.get(position);
+ }
+
+ protected static class SettingsItemHolder extends ExtendedViewHolder {
+ public final CircledImageView imageView;
+ public final TextView textView;
+
+ public SettingsItemHolder(View itemView) {
+ super(itemView);
+
+ imageView = ((CircledImageView) itemView.findViewById(R.id.image));
+ textView = ((TextView) itemView.findViewById(R.id.text));
+ }
+ }
+
+ protected class SettingsItem extends FrameLayout implements ExtendedOnCenterProximityListener {
+
+ protected final CircledImageView mImage;
+ protected final TextView mText;
+
+ public SettingsItem(Context context) {
+ super(context);
+ View view = View.inflate(context, mItemLayoutId, null);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT);
+ params.gravity = Gravity.CENTER_VERTICAL;
+ addView(view, params);
+ mImage = (CircledImageView) findViewById(R.id.image);
+ mText = (TextView) findViewById(R.id.text);
+ }
+
+ @Override
+ public float getProximityMinValue() {
+ return mDefaultCircleRadiusPercent;
+ }
+
+ @Override
+ public float getProximityMaxValue() {
+ return mSelectedCircleRadiusPercent;
+ }
+
+ @Override
+ public float getCurrentProximityValue() {
+ return mImage.getCircleRadiusPressedPercent();
+ }
+
+ @Override
+ public void setScalingAnimatorValue(float value) {
+ mImage.setCircleRadiusPercent(value);
+ mImage.setCircleRadiusPressedPercent(value);
+ }
+
+ @Override
+ public void onCenterPosition(boolean animate) {
+ mImage.setAlpha(1f);
+ mText.setAlpha(1f);
+ }
+
+ @Override
+ public void onNonCenterPosition(boolean animate) {
+ mImage.setAlpha(0.5f);
+ mText.setAlpha(0.5f);
+ }
+
+ TextView getTextView() {
+ return mText;
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/ui/wear/settings/ViewUtils.java b/src/com/android/packageinstaller/permission/ui/wear/settings/ViewUtils.java
new file mode 100644
index 00000000..cf1c0fd0
--- /dev/null
+++ b/src/com/android/packageinstaller/permission/ui/wear/settings/ViewUtils.java
@@ -0,0 +1,48 @@
+/*
+ * 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.wear.settings;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Utility to determine screen shape
+ */
+public class ViewUtils {
+
+ public static boolean getIsCircular(Context context) {
+ return context.getResources().getConfiguration().isScreenRound();
+ }
+
+ /**
+ * Set the given {@code view} and all descendants to the given {@code enabled} state.
+ *
+ * @param view the parent view of a subtree of components whose enabled state must be set
+ * @param enabled the new enabled state of the subtree of components
+ */
+ public static void setEnabled(View view, boolean enabled) {
+ view.setEnabled(enabled);
+
+ if (view instanceof ViewGroup) {
+ final ViewGroup viewGroup = (ViewGroup) view;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ setEnabled(viewGroup.getChildAt(i), enabled);
+ }
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/permission/utils/LocationUtils.java b/src/com/android/packageinstaller/permission/utils/LocationUtils.java
index 512fcf44..0296ae80 100644
--- a/src/com/android/packageinstaller/permission/utils/LocationUtils.java
+++ b/src/com/android/packageinstaller/permission/utils/LocationUtils.java
@@ -36,23 +36,9 @@ public class LocationUtils {
public static final String LOCATION_PERMISSION = Manifest.permission_group.LOCATION;
- public static ArrayList<String> getLocationProviders() {
- ArrayList<String> providers = new ArrayList<>();
- Resources res = Resources.getSystem();
- providers.add(res.getString(
- com.android.internal.R.string.config_networkLocationProviderPackageName));
-
- for (String provider :
- res.getStringArray(com.android.internal.R.array.config_locationProviderPackageNames)) {
- providers.add(provider);
- }
-
- return providers;
- }
-
public static void showLocationDialog(final Context context, CharSequence label) {
new AlertDialog.Builder(context)
- .setIcon(com.android.internal.R.drawable.ic_dialog_alert_material)
+ .setIcon(R.drawable.ic_dialog_alert_material)
.setTitle(android.R.string.dialog_alert_title)
.setMessage(context.getString(R.string.location_warning, label))
.setNegativeButton(R.string.ok, null)
@@ -83,5 +69,4 @@ public class LocationUtils {
return false;
}
}
-
}
diff --git a/src/com/android/packageinstaller/permission/utils/Utils.java b/src/com/android/packageinstaller/permission/utils/Utils.java
index 2940a729..21830378 100644
--- a/src/com/android/packageinstaller/permission/utils/Utils.java
+++ b/src/com/android/packageinstaller/permission/utils/Utils.java
@@ -31,8 +31,11 @@ import android.util.Log;
import android.util.TypedValue;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
+import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
+import java.util.List;
+
public class Utils {
private static final String LOG_TAG = "Utils";
@@ -127,14 +130,20 @@ public class Utils {
return launcherPkgs;
}
+ public static List<ApplicationInfo> getAllInstalledApplications(Context context) {
+ return context.getPackageManager().getInstalledApplications(0);
+ }
+
public static boolean isSystem(PermissionApp app, ArraySet<String> launcherPkgs) {
- ApplicationInfo info = app.getAppInfo();
- return info.isSystemApp() && (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0
- && !launcherPkgs.contains(info.packageName);
+ return isSystem(app.getAppInfo(), launcherPkgs);
}
- public static boolean isTelevision(Context context) {
- int uiMode = context.getResources().getConfiguration().uiMode;
- return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
+ public static boolean isSystem(AppPermissions app, ArraySet<String> launcherPkgs) {
+ return isSystem(app.getPackageInfo().applicationInfo, launcherPkgs);
+ }
+
+ public static boolean isSystem(ApplicationInfo info, ArraySet<String> launcherPkgs) {
+ return info.isSystemApp() && (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0
+ && !launcherPkgs.contains(info.packageName);
}
}
diff --git a/src/com/android/packageinstaller/wear/WearPackageArgs.java b/src/com/android/packageinstaller/wear/WearPackageArgs.java
new file mode 100644
index 00000000..67051da0
--- /dev/null
+++ b/src/com/android/packageinstaller/wear/WearPackageArgs.java
@@ -0,0 +1,92 @@
+/*
+ * 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.wear;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Installation Util that contains a list of parameters that are needed for
+ * installing/uninstalling.
+ */
+public class WearPackageArgs {
+ private static final String KEY_ASSET_URI =
+ "com.google.android.clockwork.EXTRA_ASSET_URI";
+ private static final String KEY_START_ID =
+ "com.google.android.clockwork.EXTRA_START_ID";
+ private static final String KEY_PERM_URI =
+ "com.google.android.clockwork.EXTRA_PERM_URI";
+ private static final String KEY_CHECK_PERMS =
+ "com.google.android.clockwork.EXTRA_CHECK_PERMS";
+ private static final String KEY_SKIP_IF_SAME_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
+ private static final String KEY_COMPRESSION_ALG =
+ "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
+ private static final String KEY_COMPANION_SDK_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
+ private static final String KEY_COMPANION_DEVICE_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
+ private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
+ "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
+
+ public static String getPackageName(Bundle b) {
+ return b.getString(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ }
+
+ public static Uri getAssetUri(Bundle b) {
+ return b.getParcelable(KEY_ASSET_URI);
+ }
+
+ public static Bundle setAssetUri(Bundle b, Uri assetUri) {
+ b.putParcelable(KEY_ASSET_URI, assetUri);
+ return b;
+ }
+
+ public static Uri getPermUri(Bundle b) {
+ return b.getParcelable(KEY_PERM_URI);
+ }
+
+ public static boolean checkPerms(Bundle b) {
+ return b.getBoolean(KEY_CHECK_PERMS);
+ }
+
+ public static boolean skipIfSameVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
+ }
+
+ public static int getCompanionSdkVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_SDK_VERSION);
+ }
+
+ public static int getCompanionDeviceVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_DEVICE_VERSION);
+ }
+
+ public static String getCompressionAlg(Bundle b) {
+ return b.getString(KEY_COMPRESSION_ALG);
+ }
+
+ public static int getStartId(Bundle b) {
+ return b.getInt(KEY_START_ID);
+ }
+
+ public static Bundle setStartId(Bundle b, int startId) {
+ b.putInt(KEY_START_ID, startId);
+ return b;
+ }
+}
diff --git a/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
new file mode 100644
index 00000000..02b9d298
--- /dev/null
+++ b/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
@@ -0,0 +1,202 @@
+/*
+ * 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.wear;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+public class WearPackageIconProvider extends ContentProvider {
+ private static final String TAG = "WearPackageIconProvider";
+ public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
+
+ private static final String REQUIRED_PERMISSION =
+ "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
+
+ /** MIME types. */
+ public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("Query is not supported.");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ if (AUTHORITY.equals(uri.getEncodedAuthority())) {
+ return ICON_TYPE;
+ }
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Insert is not supported.");
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Update is not supported.");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(
+ Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+ }
+ return null;
+ }
+
+ public static Uri getUriForPackage(final String packageName) {
+ return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
+ }
+
+ private String getPackageNameFromUri(Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ List<String> pathSegments = uri.getPathSegments();
+ String packageName = pathSegments.get(pathSegments.size() - 1);
+
+ if (packageName.endsWith(".icon")) {
+ packageName = packageName.substring(0, packageName.lastIndexOf("."));
+ }
+ return packageName;
+ }
+
+ /**
+ * Make sure the calling app is either a system app or the same app or has the right permission.
+ * @throws SecurityException if the caller has insufficient permissions.
+ */
+ @TargetApi(Build.VERSION_CODES.BASE_1_1)
+ private void enforcePermissions(Uri uri) {
+ // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
+ // allow System process to access this provider.
+ Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int myUid = android.os.Process.myUid();
+
+ if (uid == myUid || isSystemApp(context, pid)) {
+ return;
+ }
+
+ if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ }
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PERMISSION_GRANTED) {
+ return;
+ }
+
+ throw new SecurityException("Permission Denial: reading "
+ + getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid);
+ }
+
+ /**
+ * From the pid of the calling process, figure out whether this is a system app or not. We do
+ * this by checking the application information corresponding to the pid and then checking if
+ * FLAG_SYSTEM is set.
+ */
+ @TargetApi(Build.VERSION_CODES.CUPCAKE)
+ private boolean isSystemApp(Context context, int pid) {
+ // Get the Activity Manager Object
+ ActivityManager aManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ // Get the list of running Applications
+ List<ActivityManager.RunningAppProcessInfo> rapInfoList =
+ aManager.getRunningAppProcesses();
+ for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
+ if (rapInfo.pid == pid) {
+ try {
+ PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
+ rapInfo.pkgList[0], 0);
+ if (pkgInfo != null && pkgInfo.applicationInfo != null &&
+ (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ Log.d(TAG, pid + " is a system app.");
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not find package information.", e);
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
new file mode 100644
index 00000000..3874c0a4
--- /dev/null
+++ b/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -0,0 +1,602 @@
+/*
+ * 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.wear;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallObserver;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.PackageUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Service that will install/uninstall packages. It will check for permissions and features as well.
+ *
+ * -----------
+ *
+ * Debugging information:
+ *
+ * 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 \
+ * --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
+ *
+ * Retry GMS:
+ * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ */
+public class WearPackageInstallerService extends Service {
+ private static final String TAG = "WearPkgInstallerService";
+
+ private static final String KEY_PACKAGE_NAME =
+ "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
+ private static final String KEY_APP_LABEL = "com.google.android.clockwork.EXTRA_APP_LABEL";
+ private static final String KEY_APP_ICON_URI =
+ "com.google.android.clockwork.EXTRA_APP_ICON_URI";
+ private static final String KEY_PERMS_LIST = "com.google.android.clockwork.EXTRA_PERMS_LIST";
+ private static final String KEY_HAS_LAUNCHER =
+ "com.google.android.clockwork.EXTRA_HAS_LAUNCHER";
+
+ private static final String HOME_APP_PACKAGE_NAME = "com.google.android.wearable.app";
+ private static final String SHOW_PERMS_SERVICE_CLASS =
+ "com.google.android.clockwork.packagemanager.ShowPermsService";
+
+ /**
+ * Normally sent by the Play store (See http://go/playstore-gms_updated), we instead
+ * broadcast, ourselves. http://b/17387718
+ */
+ private static final String GMS_UPDATED_BROADCAST = "com.google.android.gms.GMS_UPDATED";
+ public static final String GMS_PACKAGE_NAME = "com.google.android.gms";
+
+ private final int START_INSTALL = 1;
+ private final int START_UNINSTALL = 2;
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case START_INSTALL:
+ installPackage(msg.getData());
+ break;
+ case START_UNINSTALL:
+ uninstallPackage(msg.getData());
+ break;
+ }
+ }
+ }
+ private ServiceHandler mServiceHandler;
+
+ private static volatile PowerManager.WakeLock lockStatic = null;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread thread = new HandlerThread("PackageInstallerThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ mServiceHandler = new ServiceHandler(thread.getLooper());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (!DeviceUtils.isWear(this)) {
+ Log.w(TAG, "Not running on wearable");
+ 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);
+ }
+ 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);
+ }
+ }
+ return START_NOT_STICKY;
+ }
+
+ private void installPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+ final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
+ final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
+ boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
+ boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
+ int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
+ int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
+ String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
+ ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
+ checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
+ ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
+ companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion);
+ }
+ final PackageManager pm = getPackageManager();
+ File tempFile = null;
+ int installFlags = 0;
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ boolean messageSent = false;
+ try {
+ PackageInfo existingPkgInfo = null;
+ try {
+ existingPkgInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_PERMISSIONS);
+ if(existingPkgInfo != null) {
+ installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore this exception. We could not find the package, will treat as a new
+ // installation.
+ }
+ if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Replacing package:" + packageName);
+ }
+ }
+ ParcelFileDescriptor parcelFd = getContentResolver()
+ .openFileDescriptor(assetUri, "r");
+ tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
+ parcelFd, packageName, compressionAlg);
+ if (tempFile == null) {
+ Log.e(TAG, "Could not create a temp file from FD for " + packageName);
+ return;
+ }
+ PackageParser.Package pkg = PackageUtil.getPackageInfo(tempFile);
+ if (pkg == null) {
+ Log.e(TAG, "Could not parse apk information for " + packageName);
+ return;
+ }
+
+ if (!pkg.packageName.equals(packageName)) {
+ Log.e(TAG, "Wearable Package Name has to match what is provided for " +
+ packageName);
+ return;
+ }
+
+ List<String> wearablePerms = pkg.requestedPermissions;
+
+ // Log if the installed pkg has a higher version number.
+ if (existingPkgInfo != null) {
+ if (existingPkgInfo.versionCode == pkg.mVersionCode) {
+ if (skipIfSameVersion) {
+ Log.w(TAG, "Version number (" + pkg.mVersionCode +
+ ") of new app is equal to existing app for " + packageName +
+ "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + pkg.mVersionCode +
+ ") is equal to existing app for " + packageName);
+ }
+ } else if (existingPkgInfo.versionCode > pkg.mVersionCode) {
+ Log.w(TAG, "Version number of new app (" + pkg.mVersionCode +
+ ") is lower than existing app ( " + existingPkgInfo.versionCode +
+ ") for " + packageName);
+ }
+
+ // Following the Android Phone model, we should only check for permissions for any
+ // newly defined perms.
+ if (existingPkgInfo.requestedPermissions != null) {
+ for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
+ // If the permission is granted, then we will not ask to request it again.
+ if ((existingPkgInfo.requestedPermissionsFlags[i] &
+ PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
+ " is already granted for " + packageName);
+ }
+ wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
+ }
+ }
+ }
+ }
+
+ // Check permissions on both the new wearable package and also on the already installed
+ // wearable package.
+ // If the app is targeting API level 23, we will also start a service in ClockworkHome
+ // which will ultimately prompt the user to accept/reject permissions.
+ if (checkPerms && !checkPermissions(pkg, companionSdkVersion, companionDeviceVersion,
+ permUri, wearablePerms, tempFile)) {
+ Log.w(TAG, "Wearable does not have enough permissions.");
+ return;
+ }
+
+ // Check that the wearable has all the features.
+ boolean hasAllFeatures = true;
+ if (pkg.reqFeatures != null) {
+ for (FeatureInfo feature : pkg.reqFeatures) {
+ if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
+ (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
+ Log.e(TAG, "Wearable does not have required feature: " + feature +
+ " for " + packageName);
+ hasAllFeatures = false;
+ }
+ }
+ }
+
+ if (!hasAllFeatures) {
+ return;
+ }
+
+ // Finally install the package.
+ pm.installPackage(Uri.fromFile(tempFile),
+ new PackageInstallObserver(this, lock, startId, packageName),
+ installFlags, packageName);
+
+ messageSent = true;
+ Log.i(TAG, "Sent installation request for " + packageName);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Could not find the file with URI " + assetUri, e);
+ } finally {
+ if (!messageSent) {
+ // Some error happened. If the message has been sent, we can wait for the observer
+ // which will finish the service.
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ finishService(lock, startId);
+ }
+ }
+ }
+
+ private void uninstallPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+
+ final PackageManager pm = getPackageManager();
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ pm.deletePackage(packageName, new PackageDeleteObserver(lock, startId),
+ PackageManager.DELETE_ALL_USERS);
+ startPermsServiceForUninstall(packageName);
+ Log.i(TAG, "Sent delete request for " + packageName);
+ }
+
+ 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 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 null;
+ }
+
+ Set<String> grantedPerms = new HashSet<>();
+ Set<String> ungrantedPerms = new HashSet<>();
+ while(permCursor.moveToNext()) {
+ // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
+ // verify their types.
+ if (permCursor.getColumnCount() == 2
+ && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
+ && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
+ String perm = permCursor.getString(0);
+ Integer granted = permCursor.getInt(1);
+ if (granted == 1) {
+ grantedPerms.add(perm);
+ } else {
+ ungrantedPerms.add(perm);
+ }
+ }
+ }
+ permCursor.close();
+
+ ArrayList<String> unavailableWearablePerms = new ArrayList<>();
+ for (String wearablePerm : wearablePermissions) {
+ if (!grantedPerms.contains(wearablePerm)) {
+ unavailableWearablePerms.add(wearablePerm);
+ if (!ungrantedPerms.contains(wearablePerm)) {
+ // This is an error condition. This means that the wearable has permissions that
+ // are not even declared in its host app. This is a developer error.
+ Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
+ + "\" that is not defined in the host application's manifest.");
+ } else {
+ Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
+ "\" that is not granted in the host application.");
+ }
+ }
+ }
+ return unavailableWearablePerms;
+ }
+
+ private void finishService(PowerManager.WakeLock lock, int startId) {
+ if (lock.isHeld()) {
+ lock.release();
+ }
+ stopSelf(startId);
+ }
+
+ private synchronized PowerManager.WakeLock getLock(Context context) {
+ if (lockStatic == null) {
+ PowerManager mgr =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ lockStatic = mgr.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
+ lockStatic.setReferenceCounted(true);
+ }
+ return lockStatic;
+ }
+
+ private void startPermsServiceForInstall(final PackageParser.Package pkg, final File apkFile,
+ List<String> unavailableWearablePerms) {
+ final String packageName = pkg.packageName;
+
+ Intent showPermsIntent = new Intent()
+ .setComponent(new ComponentName(HOME_APP_PACKAGE_NAME, SHOW_PERMS_SERVICE_CLASS))
+ .setAction(Intent.ACTION_INSTALL_PACKAGE);
+ final PackageManager pm = getPackageManager();
+ pkg.applicationInfo.publicSourceDir = apkFile.getPath();
+ final CharSequence label = pkg.applicationInfo.loadLabel(pm);
+ final Uri iconUri = getIconFileUri(packageName, pkg.applicationInfo.loadIcon(pm));
+ if (TextUtils.isEmpty(label) || iconUri == null) {
+ Log.e(TAG, "MNC: Could not launch service since either label " + label +
+ ", or icon Uri " + iconUri + " is invalid.");
+ } else {
+ showPermsIntent.putExtra(KEY_APP_LABEL, label);
+ showPermsIntent.putExtra(KEY_APP_ICON_URI, iconUri);
+ showPermsIntent.putExtra(KEY_PACKAGE_NAME, packageName);
+ showPermsIntent.putExtra(KEY_PERMS_LIST,
+ unavailableWearablePerms.toArray(new String[0]));
+ showPermsIntent.putExtra(KEY_HAS_LAUNCHER, WearPackageUtil.hasLauncherActivity(pkg));
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "MNC: Launching Intent " + showPermsIntent + " for " + packageName +
+ " with name " + label);
+ }
+ startService(showPermsIntent);
+ }
+ }
+
+ private void startPermsServiceForUninstall(final String packageName) {
+ Intent showPermsIntent = new Intent()
+ .setComponent(new ComponentName(HOME_APP_PACKAGE_NAME, SHOW_PERMS_SERVICE_CLASS))
+ .setAction(Intent.ACTION_UNINSTALL_PACKAGE);
+ showPermsIntent.putExtra(KEY_PACKAGE_NAME, packageName);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Launching Intent " + showPermsIntent + " for " + packageName);
+ }
+ startService(showPermsIntent);
+ }
+
+ private Uri getIconFileUri(final String packageName, final Drawable d) {
+ if (d == null || !(d instanceof BitmapDrawable)) {
+ Log.e(TAG, "Drawable is not a BitmapDrawable for " + packageName);
+ return null;
+ }
+ File iconFile = WearPackageUtil.getIconFile(this, packageName);
+
+ if (iconFile == null) {
+ Log.e(TAG, "Could not get icon file for " + packageName);
+ return null;
+ }
+
+ FileOutputStream fos = null;
+ try {
+ // Convert bitmap to byte array
+ Bitmap bitmap = ((BitmapDrawable) d).getBitmap();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
+
+ // Write the bytes into the file
+ fos = new FileOutputStream(iconFile);
+ fos.write(bos.toByteArray());
+ fos.flush();
+
+ return WearPackageIconProvider.getUriForPackage(packageName);
+ } catch (IOException e) {
+ Log.e(TAG, "Could not convert drawable to icon file for package " + packageName, e);
+ return null;
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ private class PackageInstallObserver extends IPackageInstallObserver.Stub {
+ private Context mContext;
+ private PowerManager.WakeLock mWakeLock;
+ private int mStartId;
+ private String mApplicationPackageName;
+ private PackageInstallObserver(Context context, PowerManager.WakeLock wakeLock,
+ int startId, String applicationPackageName) {
+ mContext = context;
+ mWakeLock = wakeLock;
+ mStartId = startId;
+ mApplicationPackageName = applicationPackageName;
+ }
+
+ public void packageInstalled(String packageName, int returnCode) {
+ try {
+ // If installation failed, bail out and remove the ShowPermsStore entry
+ if (returnCode < 0) {
+ Log.e(TAG, "Package install failed " + mApplicationPackageName
+ + ", returnCode " + returnCode);
+ WearPackageUtil.removeFromPermStore(mContext, mApplicationPackageName);
+ return;
+ }
+
+ Log.i(TAG, "Package " + packageName + " was installed.");
+
+ // Delete tempFile from the file system.
+ File tempFile = WearPackageUtil.getTemporaryFile(mContext, packageName);
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+
+ // Broadcast the "UPDATED" gmscore intent, normally sent by play store.
+ // TODO: Remove this broadcast if/when we get the play store to do this for us.
+ if (GMS_PACKAGE_NAME.equals(packageName)) {
+ Intent gmsInstalledIntent = new Intent(GMS_UPDATED_BROADCAST);
+ gmsInstalledIntent.setPackage(GMS_PACKAGE_NAME);
+ mContext.sendBroadcast(gmsInstalledIntent);
+ }
+ } finally {
+ finishService(mWakeLock, mStartId);
+ }
+ }
+ }
+
+ private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
+ private PowerManager.WakeLock mWakeLock;
+ private int mStartId;
+
+ private PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId) {
+ mWakeLock = wakeLock;
+ mStartId = startId;
+ }
+
+ public void packageDeleted(String packageName, int returnCode) {
+ try {
+ if (returnCode >= 0) {
+ Log.i(TAG, "Package " + packageName + " was uninstalled.");
+ } else {
+ Log.e(TAG, "Package uninstall failed " + packageName + ", returnCode " +
+ returnCode);
+ }
+ } finally {
+ finishService(mWakeLock, mStartId);
+ }
+ }
+ }
+}
diff --git a/src/com/android/packageinstaller/wear/WearPackageUtil.java b/src/com/android/packageinstaller/wear/WearPackageUtil.java
new file mode 100644
index 00000000..688d6167
--- /dev/null
+++ b/src/com/android/packageinstaller/wear/WearPackageUtil.java
@@ -0,0 +1,167 @@
+/*
+ * 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.wear;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageParser;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.tukaani.xz.LZMAInputStream;
+import org.tukaani.xz.XZInputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+public class WearPackageUtil {
+ private static final String TAG = "WearablePkgInstaller";
+
+ private static final String COMPRESSION_LZMA = "lzma";
+ private static final String COMPRESSION_XZ = "xz";
+
+ private static final String SHOW_PERMS_SERVICE_PKG_NAME = "com.google.android.wearable.app";
+ private static final String SHOW_PERMS_SERVICE_CLASS_NAME =
+ "com.google.android.clockwork.packagemanager.ShowPermsService";
+ private static final String EXTRA_PACKAGE_NAME
+ = "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
+
+ public static File getTemporaryFile(Context context, String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "tmp");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ File newFile = new File(newFileDir, packageName + ".apk");
+ return newFile;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ public static File getIconFile(final Context context, final String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "images/icons");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ return new File(newFileDir, packageName + ".icon");
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ /**
+ * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
+ * by the PackageManager, we will parse it before sending it to the PackageManager.
+ * Unfortunately, PackageParser needs a file to parse. So, we have to temporarily convert the fd
+ * to a File.
+ *
+ * @param context
+ * @param fd FileDescriptor to convert to File
+ * @param packageName Name of package, will define the name of the file
+ * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
+ * decompress it here
+ */
+ public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
+ String packageName, @Nullable String compressionAlg) {
+ File newFile = getTemporaryFile(context, packageName);
+ if (fd == null || fd.getFileDescriptor() == null) {
+ return null;
+ }
+ InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ try {
+ if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
+ fr = new XZInputStream(fr);
+ } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
+ fr = new LZMAInputStream(fr);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
+ return null;
+ }
+
+ int nRead;
+ byte[] data = new byte[1024];
+ try {
+ final FileOutputStream fo = new FileOutputStream(newFile);
+ while ((nRead = fr.read(data, 0, data.length)) != -1) {
+ fo.write(data, 0, nRead);
+ }
+ fo.flush();
+ fo.close();
+ Os.chmod(newFile.getAbsolutePath(), 0644);
+ return newFile;
+ } catch (IOException e) {
+ Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
+ return null;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Could not set permissions on file ", e);
+ return null;
+ } finally {
+ try {
+ fr.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close the file from FD ", e);
+ }
+ }
+ }
+
+ public static boolean hasLauncherActivity(PackageParser.Package pkg) {
+ if (pkg == null || pkg.activities == null) {
+ return false;
+ }
+
+ final int activityCount = pkg.activities.size();
+ for (int i = 0; i < activityCount; ++i) {
+ if (pkg.activities.get(i).intents != null) {
+ ArrayList<PackageParser.ActivityIntentInfo> intents =
+ pkg.activities.get(i).intents;
+ final int intentsCount = intents.size();
+ for (int j = 0; j < intentsCount; ++j) {
+ final PackageParser.ActivityIntentInfo intentInfo = intents.get(j);
+ if (intentInfo.hasAction(Intent.ACTION_MAIN)) {
+ if (intentInfo.hasCategory(Intent.CATEGORY_INFO) ||
+ intentInfo .hasCategory(Intent.CATEGORY_LAUNCHER)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public static void removeFromPermStore(Context context, String wearablePackageName) {
+ Intent newIntent = new Intent()
+ .setComponent(new ComponentName(
+ SHOW_PERMS_SERVICE_PKG_NAME, SHOW_PERMS_SERVICE_CLASS_NAME))
+ .setAction(Intent.ACTION_UNINSTALL_PACKAGE);
+ newIntent.putExtra(EXTRA_PACKAGE_NAME, wearablePackageName);
+ Log.i(TAG, "Sending removeFromPermStore to ShowPermsService " + newIntent
+ + " for " + wearablePackageName);
+ context.startService(newIntent);
+ }
+}