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