1/* 2 * Copyright (C) 2011 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 17package android.support.v4.app; 18 19import android.app.Activity; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.PackageManager; 23import android.graphics.Matrix; 24import android.graphics.RectF; 25import android.net.Uri; 26import android.os.Build; 27import android.os.Bundle; 28import android.os.Handler; 29import android.os.Looper; 30import android.os.Parcelable; 31import android.support.annotation.NonNull; 32import android.support.annotation.Nullable; 33import android.support.v4.content.ContextCompat; 34import android.view.View; 35 36import java.util.Arrays; 37import java.util.List; 38import java.util.Map; 39 40/** 41 * Helper for accessing features in {@link android.app.Activity} 42 * introduced after API level 4 in a backwards compatible fashion. 43 */ 44public class ActivityCompat extends ContextCompat { 45 46 /** 47 * This interface is the contract for receiving the results for permission requests. 48 */ 49 public interface OnRequestPermissionsResultCallback { 50 51 /** 52 * Callback for the result from requesting permissions. This method 53 * is invoked for every call on {@link #requestPermissions(android.app.Activity, 54 * String[], int)}. 55 * <p> 56 * <strong>Note:</strong> It is possible that the permissions request interaction 57 * with the user is interrupted. In this case you will receive empty permissions 58 * and results arrays which should be treated as a cancellation. 59 * </p> 60 * 61 * @param requestCode The request code passed in {@link #requestPermissions( 62 * android.app.Activity, String[], int)} 63 * @param permissions The requested permissions. Never null. 64 * @param grantResults The grant results for the corresponding permissions 65 * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED} 66 * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null. 67 * 68 * @see #requestPermissions(android.app.Activity, String[], int) 69 */ 70 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 71 @NonNull int[] grantResults); 72 } 73 74 /** 75 * Invalidate the activity's options menu, if able. 76 * 77 * <p>Before API level 11 (Android 3.0/Honeycomb) the lifecycle of the 78 * options menu was controlled primarily by the user's operation of 79 * the hardware menu key. When the user presses down on the menu key 80 * for the first time the menu was created and prepared by calls 81 * to {@link Activity#onCreateOptionsMenu(android.view.Menu)} and 82 * {@link Activity#onPrepareOptionsMenu(android.view.Menu)} respectively. 83 * Subsequent presses of the menu key kept the existing instance of the 84 * Menu itself and called {@link Activity#onPrepareOptionsMenu(android.view.Menu)} 85 * to give the activity an opportunity to contextually alter the menu 86 * before the menu panel was shown.</p> 87 * 88 * <p>In Android 3.0+ the Action Bar forces the options menu to be built early 89 * so that items chosen to show as actions may be displayed when the activity 90 * first becomes visible. The Activity method invalidateOptionsMenu forces 91 * the entire menu to be destroyed and recreated from 92 * {@link Activity#onCreateOptionsMenu(android.view.Menu)}, offering a similar 93 * though heavier-weight opportunity to change the menu's contents. Normally 94 * this functionality is used to support a changing configuration of Fragments.</p> 95 * 96 * <p>Applications may use this support helper to signal a significant change in 97 * activity state that should cause the options menu to be rebuilt. If the app 98 * is running on an older platform version that does not support menu invalidation 99 * the app will still receive {@link Activity#onPrepareOptionsMenu(android.view.Menu)} 100 * the next time the user presses the menu key and this method will return false. 101 * If this method returns true the options menu was successfully invalidated.</p> 102 * 103 * @param activity Invalidate the options menu of this activity 104 * @return true if this operation was supported and it completed; false if it was not available. 105 */ 106 public static boolean invalidateOptionsMenu(Activity activity) { 107 if (Build.VERSION.SDK_INT >= 11) { 108 ActivityCompatHoneycomb.invalidateOptionsMenu(activity); 109 return true; 110 } 111 return false; 112 } 113 114 /** 115 * Start an activity with additional launch information, if able. 116 * 117 * <p>In Android 4.1+ additional options were introduced to allow for more 118 * control on activity launch animations. Applications can use this method 119 * along with {@link ActivityOptionsCompat} to use these animations when 120 * available. When run on versions of the platform where this feature does 121 * not exist the activity will be launched normally.</p> 122 * 123 * @param activity Context to launch activity from. 124 * @param intent The description of the activity to start. 125 * @param options Additional options for how the Activity should be started. 126 * May be null if there are no options. See 127 * {@link ActivityOptionsCompat} for how to build the Bundle 128 * supplied here; there are no supported definitions for 129 * building it manually. 130 */ 131 public static void startActivity(Activity activity, Intent intent, @Nullable Bundle options) { 132 if (Build.VERSION.SDK_INT >= 16) { 133 ActivityCompatJB.startActivity(activity, intent, options); 134 } else { 135 activity.startActivity(intent); 136 } 137 } 138 139 /** 140 * Start new activity with options, if able, for which you would like a 141 * result when it finished. 142 * 143 * <p>In Android 4.1+ additional options were introduced to allow for more 144 * control on activity launch animations. Applications can use this method 145 * along with {@link ActivityOptionsCompat} to use these animations when 146 * available. When run on versions of the platform where this feature does 147 * not exist the activity will be launched normally.</p> 148 * 149 * @param activity Origin activity to launch from. 150 * @param intent The description of the activity to start. 151 * @param requestCode If >= 0, this code will be returned in 152 * onActivityResult() when the activity exits. 153 * @param options Additional options for how the Activity should be started. 154 * May be null if there are no options. See 155 * {@link ActivityOptionsCompat} for how to build the Bundle 156 * supplied here; there are no supported definitions for 157 * building it manually. 158 */ 159 public static void startActivityForResult(Activity activity, Intent intent, int requestCode, 160 @Nullable Bundle options) { 161 if (Build.VERSION.SDK_INT >= 16) { 162 ActivityCompatJB.startActivityForResult(activity, intent, requestCode, options); 163 } else { 164 activity.startActivityForResult(intent, requestCode); 165 } 166 } 167 168 /** 169 * Finish this activity, and tries to finish all activities immediately below it 170 * in the current task that have the same affinity. 171 * 172 * <p>On Android 4.1+ calling this method will call through to the native version of this 173 * method. For other platforms {@link Activity#finish()} will be called instead.</p> 174 */ 175 public static void finishAffinity(Activity activity) { 176 if (Build.VERSION.SDK_INT >= 16) { 177 ActivityCompatJB.finishAffinity(activity); 178 } else { 179 activity.finish(); 180 } 181 } 182 183 /** 184 * Reverses the Activity Scene entry Transition and triggers the calling Activity 185 * to reverse its exit Transition. When the exit Transition completes, 186 * {@link Activity#finish()} is called. If no entry Transition was used, finish() is called 187 * immediately and the Activity exit Transition is run. 188 * 189 * <p>On Android 4.4 or lower, this method only finishes the Activity with no 190 * special exit transition.</p> 191 */ 192 public static void finishAfterTransition(Activity activity) { 193 if (Build.VERSION.SDK_INT >= 21) { 194 ActivityCompat21.finishAfterTransition(activity); 195 } else { 196 activity.finish(); 197 } 198 } 199 200 /** 201 * Backwards compatible implementation of {@link android.app.Activity#getReferrer() 202 * Activity.getReferrer}. Uses the platform's implementation if available, otherwise 203 * only falls back to digging any explicitly specified referrer from the activity's intent. 204 */ 205 public Uri getReferrer(Activity activity) { 206 if (Build.VERSION.SDK_INT >= 22) { 207 return ActivityCompat22.getReferrer(activity); 208 } 209 Intent intent = activity.getIntent(); 210 Uri referrer = intent.getParcelableExtra("android.intent.extra.REFERRER"); 211 if (referrer != null) { 212 return referrer; 213 } 214 String referrerName = intent.getStringExtra("android.intent.extra.REFERRER_NAME"); 215 if (referrerName != null) { 216 return Uri.parse(referrerName); 217 } 218 return null; 219 } 220 221 /** 222 * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, 223 * android.view.View, String)} was used to start an Activity, <var>callback</var> 224 * will be called to handle shared elements on the <i>launched</i> Activity. This requires 225 * {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS}. 226 * 227 * @param callback Used to manipulate shared element transitions on the launched Activity. 228 */ 229 public static void setEnterSharedElementCallback(Activity activity, 230 SharedElementCallback callback) { 231 if (Build.VERSION.SDK_INT >= 21) { 232 ActivityCompat21.setEnterSharedElementCallback(activity, createCallback(callback)); 233 } 234 } 235 236 /** 237 * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, 238 * android.view.View, String)} was used to start an Activity, <var>callback</var> 239 * will be called to handle shared elements on the <i>launching</i> Activity. Most 240 * calls will only come when returning from the started Activity. 241 * This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS}. 242 * 243 * @param callback Used to manipulate shared element transitions on the launching Activity. 244 */ 245 public static void setExitSharedElementCallback(Activity activity, 246 SharedElementCallback callback) { 247 if (Build.VERSION.SDK_INT >= 21) { 248 ActivityCompat21.setExitSharedElementCallback(activity, createCallback(callback)); 249 } 250 } 251 252 public static void postponeEnterTransition(Activity activity) { 253 if (Build.VERSION.SDK_INT >= 21) { 254 ActivityCompat21.postponeEnterTransition(activity); 255 } 256 } 257 258 public static void startPostponedEnterTransition(Activity activity) { 259 if (Build.VERSION.SDK_INT >= 21) { 260 ActivityCompat21.startPostponedEnterTransition(activity); 261 } 262 } 263 264 /** 265 * Requests permissions to be granted to this application. These permissions 266 * must be requested in your manifest, they should not be granted to your app, 267 * and they should have protection level {@link android.content.pm.PermissionInfo 268 * #PROTECTION_DANGEROUS dangerous}, regardless whether they are declared by 269 * the platform or a third-party app. 270 * <p> 271 * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL} 272 * are granted at install time if requested in the manifest. Signature permissions 273 * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at 274 * install time if requested in the manifest and the signature of your app matches 275 * the signature of the app declaring the permissions. 276 * </p> 277 * <p> 278 * If your app does not have the requested permissions the user will be presented 279 * with UI for accepting them. After the user has accepted or rejected the 280 * requested permissions you will receive a callback reporting whether the 281 * permissions were granted or not. Your activity has to implement {@link 282 * android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback} 283 * and the results of permission requests will be delivered to its {@link 284 * android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult( 285 * int, String[], int[])} method. 286 * </p> 287 * <p> 288 * Note that requesting a permission does not guarantee it will be granted and 289 * your app should be able to run without having this permission. 290 * </p> 291 * <p> 292 * This method may start an activity allowing the user to choose which permissions 293 * to grant and which to reject. Hence, you should be prepared that your activity 294 * may be paused and resumed. Further, granting some permissions may require 295 * a restart of you application. In such a case, the system will recreate the 296 * activity stack before delivering the result to your onRequestPermissionsResult( 297 * int, String[], int[]). 298 * </p> 299 * <p> 300 * When checking whether you have a permission you should use {@link 301 * #checkSelfPermission(android.content.Context, String)}. 302 * </p> 303 * 304 * @param activity The target activity. 305 * @param permissions The requested permissions. 306 * @param requestCode Application specific request code to match with a result 307 * reported to {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult( 308 * int, String[], int[])}. 309 * 310 * @see #checkSelfPermission(android.content.Context, String) 311 * @see #shouldShowRequestPermissionRationale(android.app.Activity, String) 312 */ 313 public static void requestPermissions(final @NonNull Activity activity, 314 final @NonNull String[] permissions, final int requestCode) { 315 if (Build.VERSION.SDK_INT >= 23) { 316 ActivityCompatApi23.requestPermissions(activity, permissions, requestCode); 317 } else if (activity instanceof OnRequestPermissionsResultCallback) { 318 Handler handler = new Handler(Looper.getMainLooper()); 319 handler.post(new Runnable() { 320 @Override 321 public void run() { 322 final int[] grantResults = new int[permissions.length]; 323 324 PackageManager packageManager = activity.getPackageManager(); 325 String packageName = activity.getPackageName(); 326 327 final int permissionCount = permissions.length; 328 for (int i = 0; i < permissionCount; i++) { 329 grantResults[i] = packageManager.checkPermission( 330 permissions[i], packageName); 331 } 332 333 ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult( 334 requestCode, permissions, grantResults); 335 } 336 }); 337 } 338 } 339 340 /** 341 * Gets whether you should show UI with rationale for requesting a permission. 342 * You should do this only if you do not have the permission and the context in 343 * which the permission is requested does not clearly communicate to the user 344 * what would be the benefit from granting this permission. 345 * <p> 346 * For example, if you write a camera app, requesting the camera permission 347 * would be expected by the user and no rationale for why it is requested is 348 * needed. If however, the app needs location for tagging photos then a non-tech 349 * savvy user may wonder how location is related to taking photos. In this case 350 * you may choose to show UI with rationale of requesting this permission. 351 * </p> 352 * 353 * @param activity The target activity. 354 * @param permission A permission your app wants to request. 355 * @return Whether you can show permission rationale UI. 356 * 357 * @see #checkSelfPermission(android.content.Context, String) 358 * @see #requestPermissions(android.app.Activity, String[], int) 359 */ 360 public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, 361 @NonNull String permission) { 362 if (Build.VERSION.SDK_INT >= 23) { 363 return ActivityCompatApi23.shouldShowRequestPermissionRationale(activity, permission); 364 } 365 return false; 366 } 367 368 private static ActivityCompat21.SharedElementCallback21 createCallback( 369 SharedElementCallback callback) { 370 ActivityCompat21.SharedElementCallback21 newCallback = null; 371 if (callback != null) { 372 newCallback = new ActivityCompat.SharedElementCallback21Impl(callback); 373 } 374 return newCallback; 375 } 376 377 private static class SharedElementCallback21Impl 378 extends ActivityCompat21.SharedElementCallback21 { 379 380 private SharedElementCallback mCallback; 381 382 public SharedElementCallback21Impl(SharedElementCallback callback) { 383 mCallback = callback; 384 } 385 386 @Override 387 public void onSharedElementStart(List<String> sharedElementNames, 388 List<View> sharedElements, List<View> sharedElementSnapshots) { 389 mCallback.onSharedElementStart(sharedElementNames, sharedElements, 390 sharedElementSnapshots); 391 } 392 393 @Override 394 public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, 395 List<View> sharedElementSnapshots) { 396 mCallback.onSharedElementEnd(sharedElementNames, sharedElements, 397 sharedElementSnapshots); 398 } 399 400 @Override 401 public void onRejectSharedElements(List<View> rejectedSharedElements) { 402 mCallback.onRejectSharedElements(rejectedSharedElements); 403 } 404 405 @Override 406 public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { 407 mCallback.onMapSharedElements(names, sharedElements); 408 } 409 410 @Override 411 public Parcelable onCaptureSharedElementSnapshot(View sharedElement, 412 Matrix viewToGlobalMatrix, RectF screenBounds) { 413 return mCallback.onCaptureSharedElementSnapshot(sharedElement, viewToGlobalMatrix, 414 screenBounds); 415 } 416 417 @Override 418 public View onCreateSnapshotView(Context context, Parcelable snapshot) { 419 return mCallback.onCreateSnapshotView(context, snapshot); 420 } 421 } 422} 423