package com.android.camera;
+import android.Manifest;
import android.animation.Animator;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import com.android.camera.data.VideoItemFactory;
import com.android.camera.debug.Log;
import com.android.camera.device.ActiveCameraDeviceTracker;
+import com.android.camera.device.CameraId;
import com.android.camera.filmstrip.FilmstripContentPanel;
import com.android.camera.filmstrip.FilmstripController;
import com.android.camera.module.ModuleController;
import com.android.camera.settings.AppUpgrader;
import com.android.camera.settings.CameraSettingsActivity;
import com.android.camera.settings.Keys;
+import com.android.camera.settings.PictureSizeLoader;
import com.android.camera.settings.ResolutionSetting;
import com.android.camera.settings.ResolutionUtil;
import com.android.camera.settings.SettingsManager;
import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
import com.android.camera.util.QuickActivity;
import com.android.camera.util.ReleaseHelper;
-import com.android.camera.util.Size;
import com.android.camera.widget.FilmstripView;
import com.android.camera.widget.Preloader;
import com.android.camera2.R;
import com.bumptech.glide.MemoryCategory;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
-import com.bumptech.glide.load.engine.prefill.PreFillType;
+
import com.google.common.base.Optional;
import com.google.common.logging.eventprotos;
import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
/** Load metadata for 10 items ahead of our current. */
private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
+ private static final int PERMISSIONS_ACTIVITY_REQUEST_CODE = 1;
+ private static final int PERMISSIONS_RESULT_CODE_OK = 1;
+ private static final int PERMISSIONS_RESULT_CODE_FAILED = 2;
/** Should be used wherever a context is needed. */
private Context mAppContext;
private boolean mIsUndoingDeletion = false;
private boolean mIsActivityRunning = false;
private FatalErrorHandler mFatalErrorHandler;
+ private boolean mHasCriticalPermissions;
private final Uri[] mNfcPushUris = new Uri[1];
}
if (mCurrentModule != null) {
resetExposureCompensationToDefault(camera);
- mCurrentModule.onCameraAvailable(camera);
+ try {
+ mCurrentModule.onCameraAvailable(camera);
+ } catch (RuntimeException ex) {
+ Log.e(TAG, "Error connecting to camera", ex);
+ mFatalErrorHandler.onCameraOpenFailure();
+ }
} else {
Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
}
int currentIndex = mFilmstripController.getCurrentAdapterIndex();
for (Integer index : indexes) {
if (index == currentIndex) {
- updateBottomControlsByData(mDataAdapter.getItemAt(index));
+ updateUiByData(index);
// Currently we have only 1 data can be matched.
// No need to look for more, break.
break;
if (!Storage.isSessionUri(uri)) {
return;
}
- SessionItem newData = new SessionItem(getApplicationContext(), uri);
- mDataAdapter.addOrUpdate(newData);
+ Optional<SessionItem> newData = SessionItem.create(getApplicationContext(), uri);
+ if (newData.isPresent()) {
+ mDataAdapter.addOrUpdate(newData.get());
+ }
}
@Override
}
@Override
+ public void onSessionCanceled(Uri uri) {
+ Log.v(TAG, "onSessionCanceled:" + uri);
+ int failedIndex = mDataAdapter.findByContentUri(uri);
+ mDataAdapter.removeAt(failedIndex);
+ }
+
+ @Override
public void onSessionThumbnailUpdate(Bitmap bitmap) {
}
// this could cause user issues, so log a stack trace noting the call path
// which resulted in this scenario.
- return SettingsManager.getCameraSettingScope(
- mCameraController.getCurrentCameraId().getValue());
+ CameraId cameraId = mCameraController.getCurrentCameraId();
+
+ if(cameraId == null) {
+ Log.e(TAG, "Retrieving Camera Setting Scope with -1");
+ return SettingsManager.getCameraSettingScope("-1");
+ }
+
+ return SettingsManager.getCameraSettingScope(cameraId.getValue());
}
@Override
mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(),
getServices().getMemoryManager());
mFatalErrorHandler = new FatalErrorHandlerImpl(this);
-
+ checkPermissions();
+ if (!mHasCriticalPermissions) {
+ Log.v(TAG, "onCreate: Missing critical permissions.");
+ finish();
+ return;
+ }
profile.mark();
if (!Glide.isSetup()) {
Context context = getAndroidContext();
// As a camera we will use a large amount of memory
// for displaying images.
glide.setMemoryCategory(MemoryCategory.HIGH);
-
- Size glTextureSize = GlideFilmstripManager.getMaxGlTextureSize();
-
- // Prefill glides bitmap pool to prevent excessive jank
- // when loading large images.
- glide.preFillBitmapPool(
- new PreFillType.Builder(
- glTextureSize.width(),
- glTextureSize.height())
- .setWeight(5),
- // It's more important for jank and GC to have
- // A larger weight of max texture size images than
- // media store sized images.
- new PreFillType.Builder(
- GlideFilmstripManager.MEDIASTORE_THUMB_SIZE.width(),
- GlideFilmstripManager.MEDIASTORE_THUMB_SIZE.height()));
}
profile.mark("Glide.setup");
}
profile.mark("OneCameraManager.get");
- mCameraController = new CameraController(mAppContext, this, mMainHandler,
- CameraAgentFactory.getAndroidCameraAgent(mAppContext,
- CameraAgentFactory.CameraApi.API_1),
- CameraAgentFactory.getAndroidCameraAgent(mAppContext,
- CameraAgentFactory.CameraApi.AUTO),
- mActiveCameraDeviceTracker);
- mCameraController.setCameraExceptionHandler(
- new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
+ try {
+ mCameraController = new CameraController(mAppContext, this, mMainHandler,
+ CameraAgentFactory.getAndroidCameraAgent(mAppContext,
+ CameraAgentFactory.CameraApi.API_1),
+ CameraAgentFactory.getAndroidCameraAgent(mAppContext,
+ CameraAgentFactory.CameraApi.AUTO),
+ mActiveCameraDeviceTracker);
+ mCameraController.setCameraExceptionHandler(
+ new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
+ } catch (AssertionError e) {
+ Log.e(TAG, "Creating camera controller failed.", e);
+ mFatalErrorHandler.onGenericCameraAccessFailure();
+ }
// TODO: Try to move all the resources allocation to happen as soon as
// possible so we can call module.init() at the earliest time.
AppUpgrader appUpgrader = new AppUpgrader(this);
appUpgrader.upgrade(mSettingsManager);
+
+ // Make sure the picture sizes are correctly cached for the current OS
+ // version.
+ profile.mark();
+ try {
+ (new PictureSizeLoader(mAppContext)).computePictureSizes();
+ } catch (AssertionError e) {
+ Log.e(TAG, "Creating camera controller failed.", e);
+ mFatalErrorHandler.onGenericCameraAccessFailure();
+ }
+ profile.mark("computePictureSizes");
Keys.setDefaults(mSettingsManager, mAppContext);
mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager,
new PhotoDataFactory());
mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
new VideoDataFactory());
- mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
- mPhotoItemFactory, mVideoItemFactory);
- mDataAdapter.setLocalDataListener(mFilmstripItemListener);
-
- mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
- mDataAdapter);
-
mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
profile.mark("Init CurrentModule");
- if (!mSecureCamera) {
- mFilmstripController.setDataAdapter(mDataAdapter);
- if (!isCaptureIntent()) {
- mDataAdapter.requestLoad(new Callback<Void>() {
- @Override
- public void onCallback(Void result) {
- fillTemporarySessions();
- }
- });
- }
- } else {
- // Put a lock placeholder as the last image by setting its date to
- // 0.
- ImageView v = (ImageView) getLayoutInflater().inflate(
- R.layout.secure_album_placeholder, null);
- v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
- v.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
- NavigationChange.InteractionCause.BUTTON);
- startGallery();
- finish();
- }
- });
- v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
- mDataAdapter = new FixedLastProxyAdapter(
- mAppContext,
- mDataAdapter,
- new PlaceholderItem(
- v,
- FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
- v.getDrawable().getIntrinsicWidth(),
- v.getDrawable().getIntrinsicHeight()));
- // Flush out all the original data.
- mDataAdapter.clear();
- mFilmstripController.setDataAdapter(mDataAdapter);
- }
+ preloadFilmstripItems();
setupNfcBeamPush();
mLocalImagesObserver.setForegroundChangeListener(null);
mLocalImagesObserver.setActivityPaused(true);
mLocalVideosObserver.setActivityPaused(true);
- mPreloader.cancelAllLoads();
+ if (mPreloader != null) {
+ mPreloader.cancelAllLoads();
+ }
resetScreenOn();
mMotionManager.stop();
} else {
// Close the camera and wait for the operation done.
Log.v(TAG, "onPause closing camera");
- mCameraController.closeCamera(true);
+ if (mCameraController != null) {
+ mCameraController.closeCamera(true);
+ }
}
profile.stop();
@Override
public void onResumeTasks() {
mPaused = false;
+ checkPermissions();
+ if (!mHasCriticalPermissions) {
+ Log.v(TAG, "onResume: Missing critical permissions.");
+ finish();
+ return;
+ }
+ if (!mSecureCamera) {
+ // Show the dialog if necessary. The rest resume logic will be invoked
+ // at the onFirstRunStateReady() callback.
+ try {
+ mFirstRunDialog.showIfNecessary();
+ } catch (AssertionError e) {
+ Log.e(TAG, "Creating camera controller failed.", e);
+ mFatalErrorHandler.onGenericCameraAccessFailure();
+ }
+ } else {
+ // In secure mode from lockscreen, we go straight to camera and will
+ // show first run dialog next time user enters launcher.
+ Log.v(TAG, "in secure mode, skipping first run dialog check");
+ resume();
+ }
+ }
+
+ /**
+ * Checks if any of the needed Android runtime permissions are missing.
+ * If they are, then launch the permissions activity under one of the following conditions:
+ * a) The permissions dialogs have not run yet. We will ask for permission only once.
+ * b) If the missing permissions are critical to the app running, we will display a fatal error dialog.
+ * Critical permissions are: camera, microphone and storage. The app cannot run without them.
+ * Non-critical permission is location.
+ */
+ private void checkPermissions() {
+ if (!ApiHelper.isMOrHigher()) {
+ Log.v(TAG, "not running on M, skipping permission checks");
+ mHasCriticalPermissions = true;
+ return;
+ }
- // Show the dialog if necessary. The rest resume logic will be invoked
- // at the onFirstRunStateReady() callback.
- mFirstRunDialog.showIfNecessary();
+ if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
+ checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED &&
+ checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ mHasCriticalPermissions = true;
+ } else {
+ mHasCriticalPermissions = false;
+ }
+
+ if ((checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
+ !mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_HAS_SEEN_PERMISSIONS_DIALOGS)) ||
+ !mHasCriticalPermissions) {
+ Intent intent = new Intent(this, PermissionsActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ }
+
+ private void preloadFilmstripItems() {
+ if (mDataAdapter == null) {
+ mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
+ mPhotoItemFactory, mVideoItemFactory);
+ mDataAdapter.setLocalDataListener(mFilmstripItemListener);
+ mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
+ mDataAdapter);
+ if (!mSecureCamera) {
+ mFilmstripController.setDataAdapter(mDataAdapter);
+ if (!isCaptureIntent()) {
+ mDataAdapter.requestLoad(new Callback<Void>() {
+ @Override
+ public void onCallback(Void result) {
+ fillTemporarySessions();
+ }
+ });
+ }
+ } else {
+ // Put a lock placeholder as the last image by setting its date to
+ // 0.
+ ImageView v = (ImageView) getLayoutInflater().inflate(
+ R.layout.secure_album_placeholder, null);
+ v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
+ v.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
+ NavigationChange.InteractionCause.BUTTON);
+ startGallery();
+ finish();
+ }
+ });
+ v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
+ mDataAdapter = new FixedLastProxyAdapter(
+ mAppContext,
+ mDataAdapter,
+ new PlaceholderItem(
+ v,
+ FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
+ v.getDrawable().getIntrinsicWidth(),
+ v.getDrawable().getIntrinsicHeight()));
+ // Flush out all the original data.
+ mDataAdapter.clear();
+ mFilmstripController.setDataAdapter(mDataAdapter);
+ }
+ }
}
private void resume() {
Profile profile = mProfiler.create("CameraActivity.resume").start();
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
Log.v(TAG, "Build info: " + Build.DISPLAY);
-
updateStorageSpaceAndHint(null);
mLastLayoutOrientation = getResources().getConfiguration().orientation;
unregisterReceiver(mShutdownReceiver);
}
+ // Ensure anything that checks for "isPaused" returns true.
+ mPaused = true;
+
mSettingsManager.removeAllListeners();
- mCameraController.removeCallbackReceiver();
- mCameraController.setCameraExceptionHandler(null);
- getContentResolver().unregisterContentObserver(mLocalImagesObserver);
- getContentResolver().unregisterContentObserver(mLocalVideosObserver);
+ if (mCameraController != null) {
+ mCameraController.removeCallbackReceiver();
+ mCameraController.setCameraExceptionHandler(null);
+ }
+ if (mLocalImagesObserver != null) {
+ getContentResolver().unregisterContentObserver(mLocalImagesObserver);
+ }
+ if (mLocalVideosObserver != null) {
+ getContentResolver().unregisterContentObserver(mLocalVideosObserver);
+ }
getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
- mCameraAppUI.onDestroy();
- mModeListView.setVisibilityChangedListener(null);
+ if (mCameraAppUI != null) {
+ mCameraAppUI.onDestroy();
+ }
+ if (mModeListView != null) {
+ mModeListView.setVisibilityChangedListener(null);
+ }
mCameraController = null;
mSettingsManager = null;
mOrientationManager = null;
mButtonManager = null;
- mSoundPlayer.release();
+ if (mSoundPlayer != null) {
+ mSoundPlayer.release();
+ }
CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
}
if (message != null) {
Log.w(TAG, "Storage warning: " + message);
if (mStorageHint == null) {
- mStorageHint = OnScreenHint.makeText(message);
+ mStorageHint = OnScreenHint.makeText(CameraActivity.this, message);
} else {
mStorageHint.setText(message);
}
@Override
public void onClick(View view) {
mDataAdapter.undoDeletion();
+ // Fix for b/21666018: When undoing a delete in Fullscreen
+ // mode, just flip
+ // back to the filmstrip to force a refresh.
+ if (mFilmstripController.inFullScreen()) {
+ mFilmstripController.goToFilmstrip();
+ }
hideUndoDeletionBar(true);
}
});