Home | History | Annotate | Download | only in handheld
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.packageinstaller.permission.ui.handheld;
     18 
     19 import android.app.Activity;
     20 import android.content.Intent;
     21 import android.graphics.drawable.Icon;
     22 import android.os.Bundle;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.view.View.OnClickListener;
     26 import android.view.ViewGroup;
     27 import android.view.WindowManager.LayoutParams;
     28 import android.view.animation.AnimationUtils;
     29 import android.view.animation.Interpolator;
     30 import android.widget.Button;
     31 import android.widget.CheckBox;
     32 import android.widget.ImageView;
     33 import android.widget.TextView;
     34 
     35 import com.android.packageinstaller.R;
     36 import com.android.packageinstaller.permission.ui.ButtonBarLayout;
     37 import com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler;
     38 import com.android.packageinstaller.permission.ui.ManagePermissionsActivity;
     39 import com.android.packageinstaller.permission.ui.ManualLayoutFrame;
     40 
     41 public class GrantPermissionsViewHandlerImpl implements GrantPermissionsViewHandler,
     42         OnClickListener {
     43 
     44     public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME";
     45     public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT";
     46     public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX";
     47     public static final String ARG_GROUP_ICON = "ARG_GROUP_ICON";
     48     public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE";
     49     public static final String ARG_GROUP_SHOW_DO_NOT_ASK = "ARG_GROUP_SHOW_DO_NOT_ASK";
     50     public static final String ARG_GROUP_DO_NOT_ASK_CHECKED = "ARG_GROUP_DO_NOT_ASK_CHECKED";
     51 
     52     // Animation parameters.
     53     private static final long OUT_DURATION = 200;
     54     private static final long IN_DURATION = 300;
     55 
     56     private final Activity mActivity;
     57     private final String mAppPackageName;
     58     private final boolean mPermissionReviewRequired;
     59 
     60     private ResultListener mResultListener;
     61 
     62     private String mGroupName;
     63     private int mGroupCount;
     64     private int mGroupIndex;
     65     private Icon mGroupIcon;
     66     private CharSequence mGroupMessage;
     67     private boolean mShowDonNotAsk;
     68     private boolean mDoNotAskChecked;
     69 
     70     private ImageView mIconView;
     71     private TextView mCurrentGroupView;
     72     private TextView mMessageView;
     73     private CheckBox mDoNotAskCheckbox;
     74     private Button mAllowButton;
     75     private Button mMoreInfoButton;
     76 
     77     private ManualLayoutFrame mRootView;
     78 
     79     // Needed for animation
     80     private ViewGroup mDescContainer;
     81     private ViewGroup mCurrentDesc;
     82     private ViewGroup mDialogContainer;
     83     private ButtonBarLayout mButtonBar;
     84 
     85     public GrantPermissionsViewHandlerImpl(Activity activity, String appPackageName) {
     86         mActivity = activity;
     87         mAppPackageName = appPackageName;
     88         mPermissionReviewRequired = activity.getPackageManager().isPermissionReviewModeEnabled();
     89     }
     90 
     91     @Override
     92     public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) {
     93         mResultListener = listener;
     94         return this;
     95     }
     96 
     97     @Override
     98     public void saveInstanceState(Bundle arguments) {
     99         arguments.putString(ARG_GROUP_NAME, mGroupName);
    100         arguments.putInt(ARG_GROUP_COUNT, mGroupCount);
    101         arguments.putInt(ARG_GROUP_INDEX, mGroupIndex);
    102         arguments.putParcelable(ARG_GROUP_ICON, mGroupIcon);
    103         arguments.putCharSequence(ARG_GROUP_MESSAGE, mGroupMessage);
    104         arguments.putBoolean(ARG_GROUP_SHOW_DO_NOT_ASK, mShowDonNotAsk);
    105         arguments.putBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED, mDoNotAskCheckbox.isChecked());
    106     }
    107 
    108     @Override
    109     public void loadInstanceState(Bundle savedInstanceState) {
    110         mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
    111         mGroupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE);
    112         mGroupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON);
    113         mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT);
    114         mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX);
    115         mShowDonNotAsk = savedInstanceState.getBoolean(ARG_GROUP_SHOW_DO_NOT_ASK);
    116         mDoNotAskChecked = savedInstanceState.getBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED);
    117     }
    118 
    119     @Override
    120     public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
    121             CharSequence message, boolean showDonNotAsk) {
    122         mGroupName = groupName;
    123         mGroupCount = groupCount;
    124         mGroupIndex = groupIndex;
    125         mGroupIcon = icon;
    126         mGroupMessage = message;
    127         mShowDonNotAsk = showDonNotAsk;
    128         mDoNotAskChecked = false;
    129         // If this is a second (or later) permission and the views exist, then animate.
    130         if (mIconView != null) {
    131             if (mGroupIndex > 0) {
    132                 // The first message will be announced as the title of the activity, all others
    133                 // we need to announce ourselves.
    134                 mDescContainer.announceForAccessibility(message);
    135                 animateToPermission();
    136             } else {
    137                 updateDescription();
    138                 updateGroup();
    139                 updateDoNotAskCheckBox();
    140             }
    141         }
    142     }
    143 
    144     public void onConfigurationChanged() {
    145         mRootView.onConfigurationChanged();
    146     }
    147 
    148     private void animateOldContent(Runnable callback) {
    149         // Fade out old description group and scale out the icon for it.
    150         Interpolator interpolator = AnimationUtils.loadInterpolator(mActivity,
    151                 android.R.interpolator.fast_out_linear_in);
    152 
    153         // Icon scale to zero
    154         mIconView.animate()
    155                 .scaleX(0)
    156                 .scaleY(0)
    157                 .setDuration(OUT_DURATION)
    158                 .setInterpolator(interpolator)
    159                 .start();
    160 
    161         // Description fade out
    162         mCurrentDesc.animate()
    163                 .alpha(0)
    164                 .setDuration(OUT_DURATION)
    165                 .setInterpolator(interpolator)
    166                 .withEndAction(callback)
    167                 .start();
    168 
    169         // Checkbox fade out if needed
    170         if (!mShowDonNotAsk && mDoNotAskCheckbox.getVisibility() == View.VISIBLE) {
    171             mDoNotAskCheckbox.animate()
    172                     .alpha(0)
    173                     .setDuration(OUT_DURATION)
    174                     .setInterpolator(interpolator)
    175                     .start();
    176         }
    177     }
    178 
    179     private void attachNewContent(final Runnable callback) {
    180         mCurrentDesc = (ViewGroup) LayoutInflater.from(mActivity).inflate(
    181                 R.layout.permission_description, mDescContainer, false);
    182         mDescContainer.removeAllViews();
    183         mDescContainer.addView(mCurrentDesc);
    184 
    185         mDialogContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    186                 @Override
    187                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
    188                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
    189                     mDialogContainer.removeOnLayoutChangeListener(this);
    190 
    191                     // Prepare new content to the right to be moved in
    192                     final int containerWidth = mDescContainer.getWidth();
    193                     mCurrentDesc.setTranslationX(containerWidth);
    194 
    195                     // How much scale for the dialog to appear the same?
    196                     final int oldDynamicHeight = oldBottom - oldTop - mButtonBar.getHeight();
    197                     final float scaleY = (float) oldDynamicHeight / mDescContainer.getHeight();
    198 
    199                     // How much to translate for the dialog to appear the same?
    200                     final int translationCompensatingScale = (int) (scaleY
    201                             * mDescContainer.getHeight() - mDescContainer.getHeight()) / 2;
    202                     final int translationY = (oldTop - top) + translationCompensatingScale;
    203 
    204                     // Animate to the current layout
    205                     mDescContainer.setScaleY(scaleY);
    206                     mDescContainer.setTranslationY(translationY);
    207                     mDescContainer.animate()
    208                             .translationY(0)
    209                             .scaleY(1.0f)
    210                             .setInterpolator(AnimationUtils.loadInterpolator(mActivity,
    211                                     android.R.interpolator.linear_out_slow_in))
    212                             .setDuration(IN_DURATION)
    213                             .withEndAction(callback)
    214                             .start();
    215                 }
    216             }
    217         );
    218 
    219         mMessageView = (TextView) mCurrentDesc.findViewById(R.id.permission_message);
    220         mIconView = (ImageView) mCurrentDesc.findViewById(R.id.permission_icon);
    221 
    222         final boolean doNotAskWasShown = mDoNotAskCheckbox.getVisibility() == View.VISIBLE;
    223 
    224         updateDescription();
    225         updateGroup();
    226         updateDoNotAskCheckBox();
    227 
    228         if (!doNotAskWasShown && mShowDonNotAsk) {
    229             mDoNotAskCheckbox.setAlpha(0);
    230         }
    231     }
    232 
    233     private void animateNewContent() {
    234         Interpolator interpolator = AnimationUtils.loadInterpolator(mActivity,
    235                 android.R.interpolator.linear_out_slow_in);
    236 
    237         // Description slide in
    238         mCurrentDesc.animate()
    239                 .translationX(0)
    240                 .setDuration(IN_DURATION)
    241                 .setInterpolator(interpolator)
    242                 .start();
    243 
    244         // Checkbox fade in if needed
    245         if (mShowDonNotAsk && mDoNotAskCheckbox.getVisibility() == View.VISIBLE
    246                 && mDoNotAskCheckbox.getAlpha() < 1.0f) {
    247             mDoNotAskCheckbox.setAlpha(0);
    248             mDoNotAskCheckbox.animate()
    249                     .alpha(1.0f)
    250                     .setDuration(IN_DURATION)
    251                     .setInterpolator(interpolator)
    252                     .start();
    253         }
    254     }
    255 
    256     private void animateToPermission() {
    257         // Remove the old content
    258         animateOldContent(new Runnable() {
    259             @Override
    260             public void run() {
    261                 // Add the new content
    262                 attachNewContent(new Runnable() {
    263                     @Override
    264                     public void run() {
    265                         // Animate the new content
    266                         animateNewContent();
    267                     }
    268                 });
    269             }
    270         });
    271     }
    272 
    273     @Override
    274     public View createView() {
    275         mRootView = (ManualLayoutFrame) LayoutInflater.from(mActivity)
    276                 .inflate(R.layout.grant_permissions, null);
    277         mButtonBar = (ButtonBarLayout) mRootView.findViewById(R.id.button_group);
    278         mButtonBar.setAllowStacking(true);
    279         mMessageView = (TextView) mRootView.findViewById(R.id.permission_message);
    280         mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon);
    281         mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text);
    282         mDoNotAskCheckbox = (CheckBox) mRootView.findViewById(R.id.do_not_ask_checkbox);
    283 
    284         mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button);
    285         mAllowButton.setOnClickListener(this);
    286 
    287         if (mPermissionReviewRequired) {
    288             mMoreInfoButton = (Button) mRootView.findViewById(R.id.permission_more_info_button);
    289             mMoreInfoButton.setVisibility(View.VISIBLE);
    290             mMoreInfoButton.setOnClickListener(this);
    291         }
    292 
    293         mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container);
    294         mDescContainer = (ViewGroup) mRootView.findViewById(R.id.desc_container);
    295         mCurrentDesc = (ViewGroup) mRootView.findViewById(R.id.perm_desc_root);
    296 
    297         mRootView.findViewById(R.id.permission_deny_button).setOnClickListener(this);
    298         mDoNotAskCheckbox.setOnClickListener(this);
    299 
    300         if (mGroupName != null) {
    301             updateDescription();
    302             updateGroup();
    303             updateDoNotAskCheckBox();
    304         }
    305 
    306         return mRootView;
    307     }
    308 
    309     @Override
    310     public void updateWindowAttributes(LayoutParams outLayoutParams) {
    311         // No-op
    312     }
    313 
    314     private void updateDescription() {
    315         mIconView.setImageDrawable(mGroupIcon.loadDrawable(mActivity));
    316         mMessageView.setText(mGroupMessage);
    317     }
    318 
    319     private void updateGroup() {
    320         if (mGroupCount > 1) {
    321             mCurrentGroupView.setVisibility(View.VISIBLE);
    322             mCurrentGroupView.setText(mActivity.getString(R.string.current_permission_template,
    323                     mGroupIndex + 1, mGroupCount));
    324         } else {
    325             mCurrentGroupView.setVisibility(View.GONE);
    326         }
    327     }
    328 
    329     private void updateDoNotAskCheckBox() {
    330         if (mShowDonNotAsk) {
    331             mDoNotAskCheckbox.setVisibility(View.VISIBLE);
    332             mDoNotAskCheckbox.setOnClickListener(this);
    333             mDoNotAskCheckbox.setChecked(mDoNotAskChecked);
    334         } else {
    335             mDoNotAskCheckbox.setVisibility(View.GONE);
    336             mDoNotAskCheckbox.setOnClickListener(null);
    337         }
    338     }
    339 
    340     @Override
    341     public void onClick(View view) {
    342         switch (view.getId()) {
    343             case R.id.permission_allow_button:
    344                 if (mResultListener != null) {
    345                     view.clearAccessibilityFocus();
    346                     mResultListener.onPermissionGrantResult(mGroupName, true, false);
    347                 }
    348                 break;
    349             case R.id.permission_deny_button:
    350                 mAllowButton.setEnabled(true);
    351                 if (mResultListener != null) {
    352                     view.clearAccessibilityFocus();
    353                     mResultListener.onPermissionGrantResult(mGroupName, false,
    354                             mShowDonNotAsk && mDoNotAskCheckbox.isChecked());
    355                 }
    356                 break;
    357             case R.id.permission_more_info_button:
    358                 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
    359                 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppPackageName);
    360                 intent.putExtra(ManagePermissionsActivity.EXTRA_ALL_PERMISSIONS, true);
    361                 mActivity.startActivity(intent);
    362                 break;
    363             case R.id.do_not_ask_checkbox:
    364                 mAllowButton.setEnabled(!mDoNotAskCheckbox.isChecked());
    365                 break;
    366         }
    367     }
    368 
    369     @Override
    370     public void onBackPressed() {
    371         if (mResultListener != null) {
    372             final boolean doNotAskAgain = mDoNotAskCheckbox.isChecked();
    373             mResultListener.onPermissionGrantResult(mGroupName, false, doNotAskAgain);
    374         }
    375     }
    376 }
    377