blob: 6fcbccdcd25456ab2823362b869308bc0c5f9b86 [file] [log] [blame]
/*
* Copyright (C) 2012 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.camera;
import android.Manifest;
import android.animation.Animator;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
import android.app.KeyguardManager.KeyguardDismissCallback;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateBeamUrisCallback;
import android.nfc.NfcEvent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.CameraPerformanceTracker;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnSystemUiVisibilityChangeListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ShareActionProvider;
import com.android.camera.app.AppController;
import com.android.camera.app.CameraAppUI;
import com.android.camera.app.CameraController;
import com.android.camera.app.CameraProvider;
import com.android.camera.app.CameraServices;
import com.android.camera.app.CameraServicesImpl;
import com.android.camera.app.FirstRunDialog;
import com.android.camera.app.LocationManager;
import com.android.camera.app.MemoryManager;
import com.android.camera.app.MemoryQuery;
import com.android.camera.app.ModuleManager;
import com.android.camera.app.ModuleManager.ModuleAgent;
import com.android.camera.app.ModuleManagerImpl;
import com.android.camera.app.MotionManager;
import com.android.camera.app.OrientationManager;
import com.android.camera.app.OrientationManagerImpl;
import com.android.camera.data.CameraFilmstripDataAdapter;
import com.android.camera.data.FilmstripContentObserver;
import com.android.camera.data.FilmstripItem;
import com.android.camera.data.FilmstripItemData;
import com.android.camera.data.FilmstripItemType;
import com.android.camera.data.FilmstripItemUtils;
import com.android.camera.data.FixedLastProxyAdapter;
import com.android.camera.data.GlideFilmstripManager;
import com.android.camera.data.LocalFilmstripDataAdapter;
import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener;
import com.android.camera.data.MediaDetails;
import com.android.camera.data.MetadataLoader;
import com.android.camera.data.PhotoDataFactory;
import com.android.camera.data.PhotoItem;
import com.android.camera.data.PhotoItemFactory;
import com.android.camera.data.PlaceholderItem;
import com.android.camera.data.SessionItem;
import com.android.camera.data.VideoDataFactory;
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.module.ModulesInfo;
import com.android.camera.one.OneCameraException;
import com.android.camera.one.OneCameraManager;
import com.android.camera.one.OneCameraModule;
import com.android.camera.one.OneCameraOpener;
import com.android.camera.one.config.OneCameraFeatureConfig;
import com.android.camera.one.config.OneCameraFeatureConfigCreator;
import com.android.camera.session.CaptureSession;
import com.android.camera.session.CaptureSessionManager;
import com.android.camera.session.CaptureSessionManager.SessionListener;
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.stats.UsageStatistics;
import com.android.camera.stats.profiler.Profile;
import com.android.camera.stats.profiler.Profiler;
import com.android.camera.stats.profiler.Profilers;
import com.android.camera.tinyplanet.TinyPlanetFragment;
import com.android.camera.ui.AbstractTutorialOverlay;
import com.android.camera.ui.DetailsDialog;
import com.android.camera.ui.MainActivityLayout;
import com.android.camera.ui.ModeListView;
import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.Callback;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.GalleryHelper;
import com.android.camera.util.GcamHelper;
import com.android.camera.util.GoogleHelpHelper;
import com.android.camera.util.IntentHelper;
import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
import com.android.camera.util.QuickActivity;
import com.android.camera.util.ReleaseHelper;
import com.android.camera.widget.FilmstripView;
import com.android.camera.widget.Preloader;
import com.android.camera2.R;
import com.android.ex.camera2.portability.CameraAgent;
import com.android.ex.camera2.portability.CameraAgentFactory;
import com.android.ex.camera2.portability.CameraExceptionHandler;
import com.android.ex.camera2.portability.CameraSettings;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.MemoryCategory;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
import com.android.camera.exif.ExifInterface;
import com.google.common.base.Optional;
import com.google.common.logging.eventprotos;
import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
import com.google.common.logging.eventprotos.MediaInteraction;
import com.google.common.logging.eventprotos.NavigationChange;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class CameraActivity extends QuickActivity
implements AppController, CameraAgent.CameraOpenCallback,
ShareActionProvider.OnShareTargetSelectedListener {
private static final Log.Tag TAG = new Log.Tag("CameraActivity");
private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
"android.media.action.STILL_IMAGE_CAMERA_SECURE";
public static final String ACTION_IMAGE_CAPTURE_SECURE =
"android.media.action.IMAGE_CAPTURE_SECURE";
// The intent extra for camera from secure lock screen. True if the gallery
// should only show newly captured pictures. sSecureAlbumId does not
// increment. This is used when switching between camera, camcorder, and
// panorama. If the extra is not set, it is in the normal camera mode.
public static final String SECURE_CAMERA_EXTRA = "secure_camera";
private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
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;
/** Should be used wherever a context is needed. */
private Context mAppContext;
/**
* Camera fatal error handling:
* 1) Present error dialog to guide users to exit the app.
* 2) If users hit home button, onPause should just call finish() to exit the app.
*/
private boolean mCameraFatalError = false;
/**
* Whether onResume should reset the view to the preview.
*/
private boolean mResetToPreviewOnResume = true;
/**
* This data adapter is used by FilmStripView.
*/
private VideoItemFactory mVideoItemFactory;
private PhotoItemFactory mPhotoItemFactory;
private LocalFilmstripDataAdapter mDataAdapter;
private ActiveCameraDeviceTracker mActiveCameraDeviceTracker;
private OneCameraOpener mOneCameraOpener;
private OneCameraManager mOneCameraManager;
private SettingsManager mSettingsManager;
private ResolutionSetting mResolutionSetting;
private ModeListView mModeListView;
private boolean mModeListVisible = false;
private int mCurrentModeIndex;
private CameraModule mCurrentModule;
private ModuleManagerImpl mModuleManager;
private FrameLayout mAboveFilmstripControlLayout;
private FilmstripController mFilmstripController;
private boolean mFilmstripVisible;
/** Whether the filmstrip fully covers the preview. */
private boolean mFilmstripCoversPreview = false;
private int mResultCodeForTesting;
private Intent mResultDataForTesting;
private OnScreenHint mStorageHint;
private final Object mStorageSpaceLock = new Object();
private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
private boolean mAutoRotateScreen;
private boolean mSecureCamera;
private OrientationManagerImpl mOrientationManager;
private LocationManager mLocationManager;
private ButtonManager mButtonManager;
private Handler mMainHandler;
private PanoramaViewHelper mPanoramaViewHelper;
private ActionBar mActionBar;
private ViewGroup mUndoDeletionBar;
private boolean mIsUndoingDeletion = false;
private boolean mIsActivityRunning = false;
private FatalErrorHandler mFatalErrorHandler;
private boolean mHasCriticalPermissions;
private final Uri[] mNfcPushUris = new Uri[1];
private FilmstripContentObserver mLocalImagesObserver;
private FilmstripContentObserver mLocalVideosObserver;
private boolean mPendingDeletion = false;
private CameraController mCameraController;
private boolean mPaused;
private CameraAppUI mCameraAppUI;
private Intent mGalleryIntent;
private long mOnCreateTime;
private Menu mActionBarMenu;
private Preloader<Integer, AsyncTask> mPreloader;
/** Can be used to play custom sounds. */
private SoundPlayer mSoundPlayer;
/** Holds configuration for various OneCamera features. */
private OneCameraFeatureConfig mFeatureConfig;
private static final int LIGHTS_OUT_DELAY_MS = 4000;
private final int BASE_SYS_UI_VISIBILITY =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
private final Runnable mLightsOutRunnable = new Runnable() {
@Override
public void run() {
getWindow().getDecorView().setSystemUiVisibility(
BASE_SYS_UI_VISIBILITY | View.SYSTEM_UI_FLAG_LOW_PROFILE);
}
};
private MemoryManager mMemoryManager;
private MotionManager mMotionManager;
private final Profiler mProfiler = Profilers.instance().guard();
/** First run dialog */
private FirstRunDialog mFirstRunDialog;
@Override
public CameraAppUI getCameraAppUI() {
return mCameraAppUI;
}
@Override
public ModuleManager getModuleManager() {
return mModuleManager;
}
/**
* Close activity when secure app passes lock screen or screen turns
* off.
*/
private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
/**
* Whether the screen is kept turned on.
*/
private boolean mKeepScreenOn;
private int mLastLayoutOrientation;
private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
new CameraAppUI.BottomPanel.Listener() {
/**
* If the current photo is a photo sphere, this will launch the
* Photo Sphere panorama viewer.
*/
@Override
public void onExternalViewer() {
if (mPanoramaViewHelper == null) {
return;
}
final FilmstripItem data = getCurrentLocalData();
if (data == null) {
Log.w(TAG, "Cannot open null data.");
return;
}
final Uri contentUri = data.getData().getUri();
if (contentUri == Uri.EMPTY) {
Log.w(TAG, "Cannot open empty URL.");
return;
}
if (data.getMetadata().isUsePanoramaViewer()) {
mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
} else if (data.getMetadata().isHasRgbzData()) {
mPanoramaViewHelper.showRgbz(contentUri);
if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING, false);
mCameraAppUI.clearClingForViewer(
CameraAppUI.BottomPanel.VIEWER_REFOCUS);
}
}
}
@Override
public void onEdit() {
FilmstripItem data = getCurrentLocalData();
if (data == null) {
Log.w(TAG, "Cannot edit null data.");
return;
}
final int currentDataId = getCurrentDataId();
UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
currentDataId),
MediaInteraction.InteractionType.EDIT,
NavigationChange.InteractionCause.BUTTON,
fileAgeFromAdapterAtIndex(currentDataId));
launchEditor(data);
}
@Override
public void onTinyPlanet() {
FilmstripItem data = getCurrentLocalData();
if (data == null) {
Log.w(TAG, "Cannot edit tiny planet on null data.");
return;
}
launchTinyPlanetEditor(data);
}
@Override
public void onDelete() {
final int currentDataId = getCurrentDataId();
UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
currentDataId),
MediaInteraction.InteractionType.DELETE,
NavigationChange.InteractionCause.BUTTON,
fileAgeFromAdapterAtIndex(currentDataId));
removeItemAt(currentDataId);
}
@Override
public void onShare() {
final FilmstripItem data = getCurrentLocalData();
if (data == null) {
Log.w(TAG, "Cannot share null data.");
return;
}
final int currentDataId = getCurrentDataId();
UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
currentDataId),
MediaInteraction.InteractionType.SHARE,
NavigationChange.InteractionCause.BUTTON,
fileAgeFromAdapterAtIndex(currentDataId));
// If applicable, show release information before this item
// is shared.
if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
new Callback<Void>() {
@Override
public void onCallback(Void result) {
share(data);
}
});
} else {
share(data);
}
}
private void share(FilmstripItem data) {
Intent shareIntent = getShareIntentByData(data);
if (shareIntent != null) {
try {
launchActivityByIntent(shareIntent);
mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
} catch (ActivityNotFoundException ex) {
// Nothing.
}
}
}
private int getCurrentDataId() {
return mFilmstripController.getCurrentAdapterIndex();
}
private FilmstripItem getCurrentLocalData() {
return mDataAdapter.getItemAt(getCurrentDataId());
}
/**
* Sets up the share intent and NFC properly according to the
* data.
*
* @param item The data to be shared.
*/
private Intent getShareIntentByData(final FilmstripItem item) {
Intent intent = null;
final Uri contentUri = item.getData().getUri();
final String msgShareTo = getResources().getString(R.string.share_to);
if (item.getMetadata().isPanorama360() &&
item.getData().getUri() != Uri.EMPTY) {
intent = new Intent(Intent.ACTION_SEND);
intent.setType(FilmstripItemData.MIME_TYPE_PHOTOSPHERE);
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
} else if (item.getAttributes().canShare()) {
final String mimeType = item.getData().getMimeType();
intent = getShareIntentFromType(mimeType);
if (intent != null) {
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent = Intent.createChooser(intent, msgShareTo);
}
return intent;
}
/**
* Get the share intent according to the mimeType
*
* @param mimeType The mimeType of current data.
* @return the video/image's ShareIntent or null if mimeType is
* invalid.
*/
private Intent getShareIntentFromType(String mimeType) {
// Lazily create the intent object.
Intent intent = new Intent(Intent.ACTION_SEND);
if (mimeType.startsWith("video/")) {
intent.setType("video/*");
} else {
if (mimeType.startsWith("image/")) {
intent.setType("image/*");
} else {
Log.w(TAG, "unsupported mimeType " + mimeType);
}
}
return intent;
}
@Override
public void onProgressErrorClicked() {
FilmstripItem data = getCurrentLocalData();
getServices().getCaptureSessionManager().removeErrorMessage(
data.getData().getUri());
updateBottomControlsByData(data);
}
};
@Override
public void onCameraOpened(CameraAgent.CameraProxy camera) {
Log.v(TAG, "onCameraOpened");
if (mPaused) {
// We've paused, but just asynchronously opened the camera. Close it
// because we should be releasing the camera when paused to allow
// other apps to access it.
Log.v(TAG, "received onCameraOpened but activity is paused, closing Camera");
mCameraController.closeCamera(false);
return;
}
if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
// We shouldn't be here. Just close the camera and leave.
mCameraController.closeCamera(false);
throw new IllegalStateException("Camera opened but the module shouldn't be " +
"requesting");
}
if (mCurrentModule != null) {
resetExposureCompensationToDefault(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");
}
Log.v(TAG, "invoking onChangeCamera");
mCameraAppUI.onChangeCamera();
}
private void resetExposureCompensationToDefault(CameraAgent.CameraProxy camera) {
// Reset the exposure compensation before handing the camera to module.
CameraSettings cameraSettings = camera.getSettings();
cameraSettings.setExposureCompensationIndex(0);
camera.applySettings(cameraSettings);
}
@Override
public void onCameraDisabled(int cameraId) {
Log.w(TAG, "Camera disabled: " + cameraId);
mFatalErrorHandler.onCameraDisabledFailure();
}
@Override
public void onDeviceOpenFailure(int cameraId, String info) {
Log.w(TAG, "Camera open failure: " + info);
mFatalErrorHandler.onCameraOpenFailure();
}
@Override
public void onDeviceOpenedAlready(int cameraId, String info) {
Log.w(TAG, "Camera open already: " + cameraId + "," + info);
mFatalErrorHandler.onGenericCameraAccessFailure();
}
@Override
public void onReconnectionFailure(CameraAgent mgr, String info) {
Log.w(TAG, "Camera reconnection failure:" + info);
mFatalErrorHandler.onCameraReconnectFailure();
}
private static class MainHandler extends Handler {
final WeakReference<CameraActivity> mActivity;
public MainHandler(CameraActivity activity, Looper looper) {
super(looper);
mActivity = new WeakReference<CameraActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
CameraActivity activity = mActivity.get();
if (activity == null) {
return;
}
switch (msg.what) {
case MSG_CLEAR_SCREEN_ON_FLAG: {
if (!activity.mPaused) {
activity.getWindow().clearFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
break;
}
}
}
}
private String fileNameFromAdapterAtIndex(int index) {
final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
if (filmstripItem == null) {
return "";
}
File localFile = new File(filmstripItem.getData().getFilePath());
return localFile.getName();
}
private float fileAgeFromAdapterAtIndex(int index) {
final FilmstripItem filmstripItem = mDataAdapter.getItemAt(index);
if (filmstripItem == null) {
return 0;
}
File localFile = new File(filmstripItem.getData().getFilePath());
return 0.001f * (System.currentTimeMillis() - localFile.lastModified());
}
private final FilmstripContentPanel.Listener mFilmstripListener =
new FilmstripContentPanel.Listener() {
@Override
public void onSwipeOut() {
}
@Override
public void onSwipeOutBegin() {
mActionBar.hide();
mCameraAppUI.hideBottomControls();
mFilmstripCoversPreview = false;
updatePreviewVisibility();
}
@Override
public void onFilmstripHidden() {
mFilmstripVisible = false;
UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
NavigationChange.InteractionCause.SWIPE_RIGHT);
CameraActivity.this.setFilmstripUiVisibility(false);
// When the user hide the filmstrip (either swipe out or
// tap on back key) we move to the first item so next time
// when the user swipe in the filmstrip, the most recent
// one is shown.
mFilmstripController.goToFirstItem();
}
@Override
public void onFilmstripShown() {
mFilmstripVisible = true;
mCameraAppUI.hideCaptureIndicator();
UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
NavigationChange.InteractionCause.SWIPE_LEFT);
updateUiByData(mFilmstripController.getCurrentAdapterIndex());
}
@Override
public void onFocusedDataLongPressed(int adapterIndex) {
// Do nothing.
}
@Override
public void onFocusedDataPromoted(int adapterIndex) {
UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
adapterIndex),
MediaInteraction.InteractionType.DELETE,
NavigationChange.InteractionCause.SWIPE_UP, fileAgeFromAdapterAtIndex(
adapterIndex));
removeItemAt(adapterIndex);
}
@Override
public void onFocusedDataDemoted(int adapterIndex) {
UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(
adapterIndex),
MediaInteraction.InteractionType.DELETE,
NavigationChange.InteractionCause.SWIPE_DOWN,
fileAgeFromAdapterAtIndex(adapterIndex));
removeItemAt(adapterIndex);
}
@Override
public void onEnterFullScreenUiShown(int adapterIndex) {
if (mFilmstripVisible) {
CameraActivity.this.setFilmstripUiVisibility(true);
}
}
@Override
public void onLeaveFullScreenUiShown(int adapterIndex) {
// Do nothing.
}
@Override
public void onEnterFullScreenUiHidden(int adapterIndex) {
if (mFilmstripVisible) {
CameraActivity.this.setFilmstripUiVisibility(false);
}
}
@Override
public void onLeaveFullScreenUiHidden(int adapterIndex) {
// Do nothing.
}
@Override
public void onEnterFilmstrip(int adapterIndex) {
if (mFilmstripVisible) {
CameraActivity.this.setFilmstripUiVisibility(true);
}
}
@Override
public void onLeaveFilmstrip(int adapterIndex) {
// Do nothing.
}
@Override
public void onDataReloaded() {
if (!mFilmstripVisible) {
return;
}
updateUiByData(mFilmstripController.getCurrentAdapterIndex());
}
@Override
public void onDataUpdated(int adapterIndex) {
if (!mFilmstripVisible) {
return;
}
updateUiByData(mFilmstripController.getCurrentAdapterIndex());
}
@Override
public void onEnterZoomView(int adapterIndex) {
if (mFilmstripVisible) {
CameraActivity.this.setFilmstripUiVisibility(false);
}
}
@Override
public void onZoomAtIndexChanged(int adapterIndex, float zoom) {
final FilmstripItem filmstripItem = mDataAdapter.getItemAt(adapterIndex);
long ageMillis = System.currentTimeMillis()
- filmstripItem.getData().getLastModifiedDate().getTime();
// Do not log if items is to old or does not have a path (which is
// being used as a key).
if (TextUtils.isEmpty(filmstripItem.getData().getFilePath()) ||
ageMillis > UsageStatistics.VIEW_TIMEOUT_MILLIS) {
return;
}
File localFile = new File(filmstripItem.getData().getFilePath());
UsageStatistics.instance().mediaView(localFile.getName(),
filmstripItem.getData().getLastModifiedDate().getTime(), zoom);
}
@Override
public void onDataFocusChanged(final int prevIndex, final int newIndex) {
if (!mFilmstripVisible) {
return;
}
// TODO: This callback is UI event callback, should always
// happen on UI thread. Find the reason for this
// runOnUiThread() and fix it.
runOnUiThread(new Runnable() {
@Override
public void run() {
updateUiByData(newIndex);
}
});
}
@Override
public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
}
};
private final FilmstripItemListener mFilmstripItemListener =
new FilmstripItemListener() {
@Override
public void onMetadataUpdated(List<Integer> indexes) {
if (mPaused) {
// Callback after the activity is paused.
return;
}
int currentIndex = mFilmstripController.getCurrentAdapterIndex();
for (Integer index : indexes) {
if (index == currentIndex) {
updateUiByData(index);
// Currently we have only 1 data can be matched.
// No need to look for more, break.
break;
}
}
}
};
public void gotoGallery() {
UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
NavigationChange.InteractionCause.BUTTON);
mFilmstripController.goToNextItem();
}
/**
* If 'visible' is false, this hides the action bar. Also maintains
* lights-out at all times.
*
* @param visible is false, this hides the action bar and filmstrip bottom
* controls.
*/
private void setFilmstripUiVisibility(boolean visible) {
mLightsOutRunnable.run();
mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
if (visible != mActionBar.isShowing()) {
if (visible) {
mActionBar.show();
mCameraAppUI.showBottomControls();
} else {
mActionBar.hide();
mCameraAppUI.hideBottomControls();
}
}
mFilmstripCoversPreview = visible;
updatePreviewVisibility();
}
private void hideSessionProgress() {
mCameraAppUI.getFilmstripBottomControls().hideProgress();
}
private void showSessionProgress(int messageId) {
CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
controls.setProgressText(messageId > 0 ? getString(messageId) : "");
controls.hideControls();
controls.hideProgressError();
controls.showProgress();
}
private void showProcessError(int messageId) {
mCameraAppUI.getFilmstripBottomControls().showProgressError(
messageId > 0 ? getString(messageId) : "");
}
private void updateSessionProgress(int progress) {
mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
}
private void updateSessionProgressText(int messageId) {
mCameraAppUI.getFilmstripBottomControls().setProgressText(
messageId > 0 ? getString(messageId) : "");
}
// Candidate for deletion as Android Beam is deprecated in Android Q
private void setupNfcBeamPush() {
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
if (adapter == null) {
return;
}
if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
// Disable beaming
adapter.setNdefPushMessage(null, CameraActivity.this);
return;
}
adapter.setBeamPushUris(null, CameraActivity.this);
adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
@Override
public Uri[] createBeamUris(NfcEvent event) {
return mNfcPushUris;
}
}, CameraActivity.this);
}
@Override
public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
int currentIndex = mFilmstripController.getCurrentAdapterIndex();
if (currentIndex < 0) {
return false;
}
UsageStatistics.instance().mediaInteraction(fileNameFromAdapterAtIndex(currentIndex),
MediaInteraction.InteractionType.SHARE,
NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(currentIndex));
// TODO add intent.getComponent().getPackageName()
return true;
}
// Note: All callbacks come back on the main thread.
private final SessionListener mSessionListener =
new SessionListener() {
@Override
public void onSessionQueued(final Uri uri) {
Log.v(TAG, "onSessionQueued: " + uri);
if (!Storage.isSessionUri(uri)) {
return;
}
Optional<SessionItem> newData = SessionItem.create(getApplicationContext(), uri);
if (newData.isPresent()) {
mDataAdapter.addOrUpdate(newData.get());
}
}
@Override
public void onSessionUpdated(Uri uri) {
Log.v(TAG, "onSessionUpdated: " + uri);
mDataAdapter.refresh(uri);
}
@Override
public void onSessionDone(final Uri sessionUri) {
Log.v(TAG, "onSessionDone:" + sessionUri);
Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
if (contentUri == null) {
mDataAdapter.refresh(sessionUri);
return;
}
PhotoItem newData = mPhotoItemFactory.queryContentUri(contentUri);
// This can be null if e.g. a session is canceled (e.g.
// through discard panorama). It might be worth adding
// onSessionCanceled or the like this interface.
if (newData == null) {
Log.i(TAG, "onSessionDone: Could not find LocalData for URI: " + contentUri);
return;
}
final int pos = mDataAdapter.findByContentUri(sessionUri);
if (pos == -1) {
// We do not have a placeholder for this image, perhaps
// due to the activity crashing or being killed.
mDataAdapter.addOrUpdate(newData);
} else {
// Make the PhotoItem aware of the session placeholder, to
// allow it to make a smooth transition to its content if it
// the session item is currently visible.
FilmstripItem oldSessionData = mDataAdapter.getFilmstripItemAt(pos);
if (mCameraAppUI.getFilmstripVisibility() == View.VISIBLE
&& mFilmstripController.isVisible(oldSessionData)) {
Log.v(TAG, "session item visible, setting transition placeholder");
newData.setSessionPlaceholderBitmap(
Storage.getPlaceholderForSession(sessionUri));
}
mDataAdapter.updateItemAt(pos, newData);
}
}
@Override
public void onSessionProgress(final Uri uri, final int progress) {
if (progress < 0) {
// Do nothing, there is no task for this URI.
return;
}
int currentIndex = mFilmstripController.getCurrentAdapterIndex();
if (currentIndex == -1) {
return;
}
if (uri.equals(
mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
updateSessionProgress(progress);
}
}
@Override
public void onSessionProgressText(final Uri uri, final int messageId) {
int currentIndex = mFilmstripController.getCurrentAdapterIndex();
if (currentIndex == -1) {
return;
}
if (uri.equals(
mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
updateSessionProgressText(messageId);
}
}
@Override
public void onSessionCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
// Don't show capture indicator in Photo Sphere.
final int photosphereModuleId = getApplicationContext().getResources()
.getInteger(
R.integer.camera_mode_photosphere);
if (mCurrentModeIndex == photosphereModuleId) {
return;
}
indicateCapture(indicator, rotationDegrees);
}
@Override
public void onSessionFailed(Uri uri, int failureMessageId,
boolean removeFromFilmstrip) {
Log.v(TAG, "onSessionFailed:" + uri);
int failedIndex = mDataAdapter.findByContentUri(uri);
int currentIndex = mFilmstripController.getCurrentAdapterIndex();
if (currentIndex == failedIndex) {
updateSessionProgress(0);
showProcessError(failureMessageId);
mDataAdapter.refresh(uri);
}
if (removeFromFilmstrip) {
mFatalErrorHandler.onMediaStorageFailure();
mDataAdapter.removeAt(failedIndex);
}
}
@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) {
}
@Override
public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) {
}
};
@Override
public Context getAndroidContext() {
return mAppContext;
}
@Override
public OneCameraFeatureConfig getCameraFeatureConfig() {
return mFeatureConfig;
}
@Override
public Dialog createDialog() {
return new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
}
@Override
public void launchActivityByIntent(Intent intent) {
// Starting from L, we prefer not to start edit activity within camera's task.
mResetToPreviewOnResume = false;
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
startActivity(intent);
}
@Override
public int getCurrentModuleIndex() {
return mCurrentModeIndex;
}
@Override
public String getModuleScope() {
ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex);
return SettingsManager.getModuleSettingScope(agent.getScopeNamespace());
}
@Override
public String getCameraScope() {
// if an unopen camera i.e. negative ID is returned, which we've observed in
// some automated scenarios, just return it as a valid separate scope
// this could cause user issues, so log a stack trace noting the call path
// which resulted in this scenario.
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
public ModuleController getCurrentModuleController() {
return mCurrentModule;
}
@Override
public int getQuickSwitchToModuleId(int currentModuleIndex) {
return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
mAppContext);
}
@Override
public SurfaceTexture getPreviewBuffer() {
// TODO: implement this
return null;
}
@Override
public void onPreviewReadyToStart() {
mCameraAppUI.onPreviewReadyToStart();
}
@Override
public void onPreviewStarted() {
mCameraAppUI.onPreviewStarted();
}
@Override
public void addPreviewAreaSizeChangedListener(
PreviewStatusListener.PreviewAreaChangedListener listener) {
mCameraAppUI.addPreviewAreaChangedListener(listener);
}
@Override
public void removePreviewAreaSizeChangedListener(
PreviewStatusListener.PreviewAreaChangedListener listener) {
mCameraAppUI.removePreviewAreaChangedListener(listener);
}
@Override
public void setupOneShotPreviewListener() {
mCameraController.setOneShotPreviewCallback(mMainHandler,
new CameraAgent.CameraPreviewDataCallback() {
@Override
public void onPreviewFrame(byte[] data, CameraAgent.CameraProxy camera) {
mCurrentModule.onPreviewInitialDataReceived();
mCameraAppUI.onNewPreviewFrame();
}
}
);
}
@Override
public void updatePreviewAspectRatio(float aspectRatio) {
mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
}
@Override
public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
mCameraAppUI.updatePreviewTransformFullscreen(matrix, aspectRatio);
}
@Override
public RectF getFullscreenRect() {
return mCameraAppUI.getFullscreenRect();
}
@Override
public void updatePreviewTransform(Matrix matrix) {
mCameraAppUI.updatePreviewTransform(matrix);
}
@Override
public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
mCameraAppUI.setPreviewStatusListener(previewStatusListener);
}
@Override
public FrameLayout getModuleLayoutRoot() {
return mCameraAppUI.getModuleRootView();
}
@Override
public void setShutterEventsListener(ShutterEventsListener listener) {
// TODO: implement this
}
@Override
public void setShutterEnabled(boolean enabled) {
mCameraAppUI.setShutterButtonEnabled(enabled);
}
@Override
public boolean isShutterEnabled() {
return mCameraAppUI.isShutterButtonEnabled();
}
@Override
public void startFlashAnimation(boolean shortFlash) {
mCameraAppUI.startFlashAnimation(shortFlash);
}
@Override
public void startPreCaptureAnimation() {
// TODO: implement this
}
@Override
public void cancelPreCaptureAnimation() {
// TODO: implement this
}
@Override
public void startPostCaptureAnimation() {
// TODO: implement this
}
@Override
public void startPostCaptureAnimation(Bitmap thumbnail) {
// TODO: implement this
}
@Override
public void cancelPostCaptureAnimation() {
// TODO: implement this
}
@Override
public OrientationManager getOrientationManager() {
return mOrientationManager;
}
@Override
public LocationManager getLocationManager() {
return mLocationManager;
}
@Override
public void lockOrientation() {
if (mOrientationManager != null) {
mOrientationManager.lockOrientation();
}
}
@Override
public void unlockOrientation() {
if (mOrientationManager != null) {
mOrientationManager.unlockOrientation();
}
}
/**
* If not in filmstrip, this shows the capture indicator.
*/
private void indicateCapture(final Bitmap indicator, final int rotationDegrees) {
if (mFilmstripVisible) {
return;
}
// Don't show capture indicator in Photo Sphere.
// TODO: Don't reach into resources to figure out the current mode.
final int photosphereModuleId = getApplicationContext().getResources().getInteger(
R.integer.camera_mode_photosphere);
if (mCurrentModeIndex == photosphereModuleId) {
return;
}
mMainHandler.post(new Runnable() {
@Override
public void run() {
mCameraAppUI.startCaptureIndicatorRevealAnimation(mCurrentModule
.getPeekAccessibilityString());
mCameraAppUI.updateCaptureIndicatorThumbnail(indicator, rotationDegrees);
}
});
}
@Override
public void notifyNewMedia(Uri uri) {
// TODO: This method is running on the main thread. Also we should get
// rid of that AsyncTask.
updateStorageSpaceAndHint(null);
ContentResolver cr = getContentResolver();
String mimeType = cr.getType(uri);
FilmstripItem newData = null;
if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
newData = mVideoItemFactory.queryContentUri(uri);
if (newData == null) {
Log.e(TAG, "Can't find video data in content resolver:" + uri);
return;
}
} else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
CameraUtil.broadcastNewPicture(mAppContext, uri);
newData = mPhotoItemFactory.queryContentUri(uri);
if (newData == null) {
Log.e(TAG, "Can't find photo data in content resolver:" + uri);
return;
}
} else {
Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
return;
}
// We are preloading the metadata for new video since we need the
// rotation info for the thumbnail.
new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
@Override
protected FilmstripItem doInBackground(FilmstripItem... params) {
FilmstripItem data = params[0];
MetadataLoader.loadMetadata(getAndroidContext(), data);
return data;
}
@Override
protected void onPostExecute(final FilmstripItem data) {
// TODO: Figure out why sometimes the data is aleady there.
mDataAdapter.addOrUpdate(data);
// Legacy modules don't use CaptureSession, so we show the capture indicator when
// the item was safed.
if (mCurrentModule instanceof PhotoModule ||
mCurrentModule instanceof VideoModule) {
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
final Optional<Bitmap> bitmap = data.generateThumbnail(
mAboveFilmstripControlLayout.getWidth(),
mAboveFilmstripControlLayout.getMeasuredHeight());
if (bitmap.isPresent()) {
indicateCapture(bitmap.get(), 0);
}
}
});
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
}
@Override
public void enableKeepScreenOn(boolean enabled) {
if (mPaused) {
return;
}
mKeepScreenOn = enabled;
if (mKeepScreenOn) {
mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
keepScreenOnForAWhile();
}
}
@Override
public CameraProvider getCameraProvider() {
return mCameraController;
}
@Override
public OneCameraOpener getCameraOpener() {
return mOneCameraOpener;
}
private void removeItemAt(int index) {
mDataAdapter.removeAt(index);
if (mDataAdapter.getTotalNumber() > 0) {
showUndoDeletionBar();
} else {
// If camera preview is the only view left in filmstrip,
// no need to show undo bar.
mPendingDeletion = true;
performDeletion();
if (mFilmstripVisible) {
mCameraAppUI.getFilmstripContentPanel().animateHide();
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
case R.id.action_details:
showDetailsDialog(mFilmstripController.getCurrentAdapterIndex());
return true;
case R.id.action_help_and_feedback:
mResetToPreviewOnResume = false;
new GoogleHelpHelper(this).launchGoogleHelp();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private boolean isCaptureIntent() {
if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
|| MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
|| MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
return true;
} else {
return false;
}
}
/**
* Note: Make sure this callback is unregistered properly when the activity
* is destroyed since we're otherwise leaking the Activity reference.
*/
private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
= new CameraExceptionHandler.CameraExceptionCallback() {
@Override
public void onCameraError(int errorCode) {
// Not a fatal error. only do Log.e().
Log.e(TAG, "Camera error callback. error=" + errorCode);
}
@Override
public void onCameraException(
RuntimeException ex, String commandHistory, int action, int state) {
Log.e(TAG, "Camera Exception", ex);
UsageStatistics.instance().cameraFailure(
eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
commandHistory, action, state);
onFatalError();
}
@Override
public void onDispatchThreadException(RuntimeException ex) {
Log.e(TAG, "DispatchThread Exception", ex);
UsageStatistics.instance().cameraFailure(
eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
null, UsageStatistics.NONE, UsageStatistics.NONE);
onFatalError();
}
private void onFatalError() {
if (mCameraFatalError) {
return;
}
mCameraFatalError = true;
// If the activity receives exception during onPause, just exit the app.
if (mPaused && !isFinishing()) {
Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
finish();
} else {
mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA);
}
}
};
@Override
public void onNewIntentTasks(Intent intent) {
onModeSelected(getModeIndex());
}
@Override
public void onCreateTasks(Bundle state) {
Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
mOnCreateTime = System.currentTimeMillis();
mAppContext = getApplicationContext();
mMainHandler = new MainHandler(this, getMainLooper());
mLocationManager = new LocationManager(mAppContext, shouldUseNoOpLocation());
mOrientationManager = new OrientationManagerImpl(this, mMainHandler);
mSettingsManager = getServices().getSettingsManager();
mSoundPlayer = new SoundPlayer(mAppContext);
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();
Glide.setup(new GlideBuilder(context)
.setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888)
.setResizeService(new FifoPriorityThreadPoolExecutor(2)));
Glide glide = Glide.get(context);
// As a camera we will use a large amount of memory
// for displaying images.
glide.setMemoryCategory(MemoryCategory.HIGH);
}
profile.mark("Glide.setup");
mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance();
try {
mOneCameraOpener = OneCameraModule.provideOneCameraOpener(
mFeatureConfig,
mAppContext,
mActiveCameraDeviceTracker,
ResolutionUtil.getDisplayMetrics(this));
mOneCameraManager = OneCameraModule.provideOneCameraManager();
} catch (OneCameraException e) {
// Log error and continue start process while showing error dialog..
Log.e(TAG, "Creating camera manager failed.", e);
mFatalErrorHandler.onGenericCameraAccessFailure();
}
profile.mark("OneCameraManager.get");
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.
mModuleManager = new ModuleManagerImpl();
ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
AppUpgrader appUpgrader = new AppUpgrader(this);
appUpgrader.upgrade(mSettingsManager);
// Make sure the picture sizes are correctly cached for the current OS
// version.
profile.mark();
try {
PictureSizeLoader pictureSizeLoader = new PictureSizeLoader(mAppContext);
pictureSizeLoader.computePictureSizes();
pictureSizeLoader.release();
} 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,
getContentResolver());
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
// We suppress this flag via theme when drawing the system preview
// background, but once we create activity here, reactivate to the
// default value. The default is important for L, we don't want to
// change app behavior, just starting background drawable layout.
if (ApiHelper.isLOrHigher()) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
}
profile.mark();
setContentView(R.layout.activity_main);
profile.mark("setContentView()");
// A window background is set in styles.xml for the system to show a
// drawable background with gray color and camera icon before the
// activity is created. We set the background to null here to prevent
// overdraw, all views must take care of drawing backgrounds if
// necessary. This call to setBackgroundDrawable must occur after
// setContentView, otherwise a background may be set again from the
// style.
getWindow().setBackgroundDrawable(null);
mActionBar = getActionBar();
// set actionbar background to 100% or 50% transparent
if (ApiHelper.isLOrHigher()) {
mActionBar.setBackgroundDrawable(new ColorDrawable(0x00000000));
} else {
mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
}
mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
mModeListView.init(mModuleManager.getSupportedModeIndexList());
if (ApiHelper.HAS_ROTATION_ANIMATION) {
setRotationAnimation();
}
mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
@Override
public void onVisibilityChanged(boolean visible) {
mModeListVisible = visible;
mCameraAppUI.setShutterButtonImportantToA11y(!visible);
updatePreviewVisibility();
}
});
// Check if this is in the secure camera mode.
Intent intent = getIntent();
String action = intent.getAction();
if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
|| ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
mSecureCamera = true;
} else {
mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
}
if (mSecureCamera) {
// Change the window flags so that secure camera can show when
// locked
Window win = getWindow();
WindowManager.LayoutParams params = win.getAttributes();
params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
win.setAttributes(params);
// Filter for screen off so that we can finish secure camera
// activity when screen is off.
IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(mShutdownReceiver, filter_screen_off);
// Filter for phone unlock so that we can finish secure camera
// via this UI path:
// 1. from secure lock screen, user starts secure camera
// 2. user presses home button
// 3. user unlocks phone
IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
registerReceiver(mShutdownReceiver, filter_user_unlock);
}
mCameraAppUI = new CameraAppUI(this,
(MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
mAboveFilmstripControlLayout =
(FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
// Add the session listener so we can track the session progress
// updates.
getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
mFilmstripController.setImageGap(
getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
profile.mark("Configure Camera UI");
mPanoramaViewHelper = new PanoramaViewHelper(this);
mPanoramaViewHelper.onCreate();
ContentResolver appContentResolver = mAppContext.getContentResolver();
GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
new PhotoDataFactory());
mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
new VideoDataFactory());
mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
}
setModuleFromModeIndex(getModeIndex());
profile.mark();
mCameraAppUI.prepareModuleUI();
profile.mark("Init Current Module UI");
mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
profile.mark("Init CurrentModule");
preloadFilmstripItems();
// Candidate for deletion as Android Beam is deprecated in Android Q
setupNfcBeamPush();
mLocalImagesObserver = new FilmstripContentObserver();
mLocalVideosObserver = new FilmstripContentObserver();
getContentResolver().registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
mLocalImagesObserver);
getContentResolver().registerContentObserver(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
mLocalVideosObserver);
mMemoryManager = getServices().getMemoryManager();
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
HashMap memoryData = mMemoryManager.queryMemory();
UsageStatistics.instance().reportMemoryConsumed(memoryData,
MemoryQuery.REPORT_LABEL_LAUNCH);
}
});
mMotionManager = getServices().getMotionManager();
mFirstRunDialog = new FirstRunDialog(this,
this /* as context */,
mResolutionSetting,
mSettingsManager,
mOneCameraManager,
new FirstRunDialog.FirstRunDialogListener() {
@Override
public void onFirstRunStateReady() {
// Run normal resume tasks.
resume();
}
@Override
public void onFirstRunDialogCancelled() {
// App isn't functional until users finish first run dialog.
// We need to finish here since users hit back button during
// first run dialog (b/19593942).
finish();
}
@Override
public void onCameraAccessException() {
mFatalErrorHandler.onGenericCameraAccessFailure();
}
});
profile.stop();
}
/**
* Get the current mode index from the Intent or from persistent
* settings.
*/
private int getModeIndex() {
int modeIndex = -1;
int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
int captureIntentIndex =
getResources().getInteger(R.integer.camera_mode_capture_intent);
String intentAction = getIntent().getAction();
if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(intentAction)
|| MediaStore.ACTION_VIDEO_CAPTURE.equals(intentAction)) {
modeIndex = videoIndex;
} else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intentAction)
|| MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
// Capture intent.
modeIndex = captureIntentIndex;
} else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(intentAction)
||MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(intentAction)
|| MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intentAction)) {
modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_CAMERA_MODULE_LAST_USED);
// For upgraders who have not seen the aspect ratio selection screen,
// we need to drop them back in the photo module and have them select
// aspect ratio.
// TODO: Move this to SettingsManager as an upgrade procedure.
if (!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
modeIndex = photoIndex;
}
} else {
// If the activity has not been started using an explicit intent,
// read the module index from the last time the user changed modes
modeIndex = mSettingsManager.getInteger(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_STARTUP_MODULE_INDEX);
if ((modeIndex == gcamIndex &&
!GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) || modeIndex < 0) {
modeIndex = photoIndex;
}
}
return modeIndex;
}
/**
* Incase the calling package doesn't have ACCESS_FINE_LOCATION permissions, we should not pass
* it valid location information in exif.
*/
private boolean shouldUseNoOpLocation () {
String callingPackage = getCallingPackage();
if (callingPackage == null) {
// Activity not started through startActivityForResult.
return false;
}
PackageInfo packageInfo = null;
try {
packageInfo = getPackageManager().getPackageInfo(callingPackage,
PackageManager.GET_PERMISSIONS);
} catch (Exception e) {
Log.w(TAG, "Unable to get PackageInfo for callingPackage " + callingPackage);
}
if (packageInfo != null) {
for (int i = 0; i < packageInfo.requestedPermissions.length; i++) {
if (packageInfo.requestedPermissions[i].equals(
Manifest.permission.ACCESS_FINE_LOCATION) &&
(packageInfo.requestedPermissionsFlags[i] &
PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
return false;
}
}
}
return true;
}
/**
* Call this whenever the mode drawer or filmstrip change the visibility
* state.
*/
private void updatePreviewVisibility() {
if (mCurrentModule == null) {
return;
}
int visibility = getPreviewVisibility();
mCameraAppUI.onPreviewVisiblityChanged(visibility);
updatePreviewRendering(visibility);
mCurrentModule.onPreviewVisibilityChanged(visibility);
}
private void updatePreviewRendering(int visibility) {
if (visibility == ModuleController.VISIBILITY_HIDDEN) {
mCameraAppUI.pausePreviewRendering();
} else {
mCameraAppUI.resumePreviewRendering();
}
}
private int getPreviewVisibility() {
if (mFilmstripCoversPreview) {
return ModuleController.VISIBILITY_HIDDEN;
} else if (mModeListVisible){
return ModuleController.VISIBILITY_COVERED;
} else {
return ModuleController.VISIBILITY_VISIBLE;
}
}
private void setRotationAnimation() {
int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
Window win = getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
winParams.rotationAnimation = rotationAnimation;
win.setAttributes(winParams);
}
@Override
public void onUserInteraction() {
super.onUserInteraction();
if (!isFinishing()) {
keepScreenOnForAWhile();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = super.dispatchTouchEvent(ev);
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
// Real deletion is postponed until the next user interaction after
// the gesture that triggers deletion. Until real deletion is
// performed, users can click the undo button to bring back the
// image that they chose to delete.
if (mPendingDeletion && !mIsUndoingDeletion) {
performDeletion();
}
}
return result;
}
@Override
public void onPauseTasks() {
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
Profile profile = mProfiler.create("CameraActivity.onPause").start();
/*
* Save the last module index after all secure camera and icon launches,
* not just on mode switches.
*
* Right now we exclude capture intents from this logic, because we also
* ignore the cross-Activity recovery logic in onStart for capture intents.
*/
if (!isCaptureIntent()) {
mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_STARTUP_MODULE_INDEX,
mCurrentModeIndex);
}
mPaused = true;
mCameraAppUI.hideCaptureIndicator();
mFirstRunDialog.dismiss();
// Delete photos that are pending deletion
performDeletion();
mCurrentModule.pause();
mOrientationManager.pause();
mPanoramaViewHelper.onPause();
mLocalImagesObserver.setForegroundChangeListener(null);
mLocalImagesObserver.setActivityPaused(true);
mLocalVideosObserver.setActivityPaused(true);
if (mPreloader != null) {
mPreloader.cancelAllLoads();
}
resetScreenOn();
mMotionManager.stop();
// Always stop recording location when paused. Resume will start
// location recording again if the location setting is on.
mLocationManager.recordLocation(false);
UsageStatistics.instance().backgrounded();
// Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
// button. Let's just kill the process.
if (mCameraFatalError && !isFinishing()) {
Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
finish();
} else {
// Close the camera and wait for the operation done.
Log.v(TAG, "onPause closing camera");
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 (!isSecureCamera() && !isCaptureIntent()) {
// 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;
}
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 (!mHasCriticalPermissions || (mSettingsManager.getBoolean(
SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION) &&
(checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) &&
!mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_HAS_SEEN_PERMISSIONS_DIALOGS))) {
// TODO: Convert PermissionsActivity into a dialog so we
// don't lose the state of CameraActivity.
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;
// TODO: Handle this in OrientationManager.
// Auto-rotate off
if (Settings.System.getInt(getContentResolver(),
Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
mAutoRotateScreen = false;
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
mAutoRotateScreen = true;
}
// Foreground event logging. ACTION_STILL_IMAGE_CAMERA and
// INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE are double logged due to
// lockscreen onResume->onPause->onResume sequence.
int source;
String action = getIntent().getAction();
if (action == null) {
source = ForegroundSource.UNKNOWN_SOURCE;
} else {
switch (action) {
case MediaStore.ACTION_IMAGE_CAPTURE:
source = ForegroundSource.ACTION_IMAGE_CAPTURE;
break;
case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA:
// was UNKNOWN_SOURCE in Fishlake.
source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA;
break;
case MediaStore.INTENT_ACTION_VIDEO_CAMERA:
// was UNKNOWN_SOURCE in Fishlake.
source = ForegroundSource.ACTION_VIDEO_CAMERA;
break;
case MediaStore.ACTION_VIDEO_CAPTURE:
source = ForegroundSource.ACTION_VIDEO_CAPTURE;
break;
case MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE:
// was ACTION_IMAGE_CAPTURE_SECURE in Fishlake.
source = ForegroundSource.ACTION_STILL_IMAGE_CAMERA_SECURE;
break;
case MediaStore.ACTION_IMAGE_CAPTURE_SECURE:
source = ForegroundSource.ACTION_IMAGE_CAPTURE_SECURE;
break;
case Intent.ACTION_MAIN:
source = ForegroundSource.ACTION_MAIN;
break;
default:
source = ForegroundSource.UNKNOWN_SOURCE;
break;
}
}
UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(),
isKeyguardSecure(), isKeyguardLocked(),
mStartupOnCreate, mExecutionStartNanoTime);
mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
if (ApiHelper.isLOrHigher()) {
// hide the up affordance for L devices, it's not very Materially
mActionBar.setDisplayShowHomeEnabled(false);
}
mOrientationManager.resume();
mCurrentModule.hardResetSettings(mSettingsManager);
profile.mark();
mCurrentModule.resume();
UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
NavigationChange.InteractionCause.BUTTON);
setSwipingEnabled(true);
profile.mark("mCurrentModule.resume");
if (!mResetToPreviewOnResume) {
FilmstripItem item = mDataAdapter.getItemAt(
mFilmstripController.getCurrentAdapterIndex());
if (item != null) {
mDataAdapter.refresh(item.getData().getUri());
}
}
// The share button might be disabled to avoid double tapping.
mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
// Default is showing the preview, unless disabled by explicitly
// starting an activity we want to return from to the filmstrip rather
// than the preview.
mResetToPreviewOnResume = true;
if (mLocalVideosObserver.isMediaDataChangedDuringPause()
|| mLocalImagesObserver.isMediaDataChangedDuringPause()) {
if (!mSecureCamera) {
// If it's secure camera, requestLoad() should not be called
// as it will load all the data.
if (!mFilmstripVisible) {
mDataAdapter.requestLoad(new Callback<Void>() {
@Override
public void onCallback(Void result) {
fillTemporarySessions();
}
});
} else {
mDataAdapter.requestLoadNewPhotos();
}
}
}
mLocalImagesObserver.setActivityPaused(false);
mLocalVideosObserver.setActivityPaused(false);
if (!mSecureCamera) {
mLocalImagesObserver.setForegroundChangeListener(
new FilmstripContentObserver.ChangeListener() {
@Override
public void onChange() {
mDataAdapter.requestLoadNewPhotos();
}
});
}
keepScreenOnForAWhile();
// Lights-out mode at all times.
final View rootView = findViewById(R.id.activity_root_view);
mLightsOutRunnable.run();
getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
new OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
mMainHandler.removeCallbacks(mLightsOutRunnable);
mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
}
});
profile.mark();
mPanoramaViewHelper.onResume();
profile.mark("mPanoramaViewHelper.onResume()");
ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
// Enable location recording if the setting is on.
final boolean locationRecordingEnabled =
mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_RECORD_LOCATION);
mLocationManager.recordLocation(locationRecordingEnabled);
final int previewVisibility = getPreviewVisibility();
updatePreviewRendering(previewVisibility);
mMotionManager.start();
profile.stop();
}
private void fillTemporarySessions() {
if (mSecureCamera) {
return;
}
// There might be sessions still in flight (processed by our service).
// Make sure they're added to the filmstrip.
getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
}
@Override
public void onStartTasks() {
mIsActivityRunning = true;
mPanoramaViewHelper.onStart();
/*
* If we're starting after launching a different Activity (lockscreen),
* we need to use the last mode used in the other Activity, and
* not the old one from this Activity.
*
* This needs to happen before CameraAppUI.resume() in order to set the
* mode cover icon to the actual last mode used.
*
* Right now we exclude capture intents from this logic.
*/
int modeIndex = getModeIndex();
if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
onModeSelected(modeIndex);
}
if (mResetToPreviewOnResume) {
mCameraAppUI.resume();
mResetToPreviewOnResume = false;
}
}
@Override
protected void onStopTasks() {
mIsActivityRunning = false;
mPanoramaViewHelper.onStop();
mLocationManager.disconnect();
}
@Override
public void onDestroyTasks() {
if (mSecureCamera) {
unregisterReceiver(mShutdownReceiver);
}
// Ensure anything that checks for "isPaused" returns true.
mPaused = true;
mSettingsManager.removeAllListeners();
if (mCameraController != null) {
mCameraController.removeCallbackReceiver();
mCameraController.setCameraExceptionHandler(null);
}
if (mLocalImagesObserver != null) {
getContentResolver().unregisterContentObserver(mLocalImagesObserver);
}
if (mLocalVideosObserver != null) {
getContentResolver().unregisterContentObserver(mLocalVideosObserver);
}
getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
if (mCameraAppUI != null) {
mCameraAppUI.onDestroy();
}
if (mModeListView != null) {
mModeListView.setVisibilityChangedListener(null);
}
mCameraController = null;
mSettingsManager = null;
mOrientationManager = null;
mButtonManager = null;
if (mSoundPlayer != null) {
mSoundPlayer.release();
}
if (mFirstRunDialog != null) {
mFirstRunDialog.dismiss();
}
CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
}
@Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
Log.v(TAG, "onConfigurationChanged");
if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
return;
}
if (mLastLayoutOrientation != config.orientation) {
mLastLayoutOrientation = config.orientation;
mCurrentModule.onLayoutOrientationChanged(
mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (!mFilmstripVisible) {
if (mCurrentModule.onKeyDown(keyCode, event)) {
return true;
}
// Prevent software keyboard or voice search from showing up.
if (keyCode == KeyEvent.KEYCODE_SEARCH
|| keyCode == KeyEvent.KEYCODE_MENU) {
if (event.isLongPress()) {
return true;
}
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (!mFilmstripVisible) {
// If a module is in the middle of capture, it should
// consume the key event.
if (mCurrentModule.onKeyUp(keyCode, event)) {
return true;
} else if (keyCode == KeyEvent.KEYCODE_MENU
|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
// Let the mode list view consume the event.
mCameraAppUI.openModeList();
return true;
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
mCameraAppUI.showFilmstrip();
return true;
}
} else {
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
mFilmstripController.goToNextItem();
return true;
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
boolean wentToPrevious = mFilmstripController.goToPreviousItem();
if (!wentToPrevious) {
// at beginning of filmstrip, hide and go back to preview
mCameraAppUI.hideFilmstrip();
}
return true;
}
}
return super.onKeyUp(keyCode, event);
}
@Override
public void onBackPressed() {
if (!mCameraAppUI.onBackPressed()) {
if (!mCurrentModule.onBackPressed()) {
super.onBackPressed();
}
}
}
@Override
public boolean isAutoRotateScreen() {
// TODO: Move to OrientationManager.
return mAutoRotateScreen;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.filmstrip_menu, menu);
mActionBarMenu = menu;
// add a button for launching the gallery
if (mGalleryIntent != null) {
CharSequence appName = IntentHelper.getGalleryAppName(mAppContext, mGalleryIntent);
if (appName != null) {
MenuItem menuItem = menu.add(appName);
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menuItem.setIntent(mGalleryIntent);
Drawable galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
if (galleryLogo != null) {
menuItem.setIcon(galleryLogo);
}
}
}
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
// Compatibility pre-L: launching new activities right above
// lockscreen does not reliably work, only show help if not secure
menu.removeItem(R.id.action_help_and_feedback);
}
return super.onPrepareOptionsMenu(menu);
}
protected long getStorageSpaceBytes() {
synchronized (mStorageSpaceLock) {
return mStorageSpaceBytes;
}
}
protected interface OnStorageUpdateDoneListener {
public void onStorageUpdateDone(long bytes);
}
protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
/*
* We execute disk operations on a background thread in order to
* free up the UI thread. Synchronizing on the lock below ensures
* that when getStorageSpaceBytes is called, the main thread waits
* until this method has completed.
*
* However, .execute() does not ensure this execution block will be
* run right away (.execute() schedules this AsyncTask for sometime
* in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
* tries to execute the task in parellel with other AsyncTasks, but
* there's still no guarantee).
* e.g. don't call this then immediately call getStorageSpaceBytes().
* Instead, pass in an OnStorageUpdateDoneListener.
*/
(new AsyncTask<Void, Void, Long>() {
@Override
protected Long doInBackground(Void ... arg) {
synchronized (mStorageSpaceLock) {
mStorageSpaceBytes = Storage.getAvailableSpace();
return mStorageSpaceBytes;
}
}
@Override
protected void onPostExecute(Long bytes) {
updateStorageHint(bytes);
// This callback returns after I/O to check disk, so we could be
// pausing and shutting down. If so, don't bother invoking.
if (callback != null && !mPaused) {
callback.onStorageUpdateDone(bytes);
} else {
Log.v(TAG, "ignoring storage callback after activity pause");
}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
protected void updateStorageHint(long storageSpace) {
if (!mIsActivityRunning) {
return;
}
String message = null;
if (storageSpace == Storage.UNAVAILABLE) {
message = getString(R.string.no_storage);
} else if (storageSpace == Storage.PREPARING) {
message = getString(R.string.preparing_sd);
} else if (storageSpace == Storage.UNKNOWN_SIZE) {
message = getString(R.string.access_sd_fail);
} else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
message = getString(R.string.spaceIsLow_content);
}
if (message != null) {
Log.w(TAG, "Storage warning: " + message);
if (mStorageHint == null) {
mStorageHint = OnScreenHint.makeText(CameraActivity.this, message);
} else {
mStorageHint.setText(message);
}
mStorageHint.show();
UsageStatistics.instance().storageWarning(storageSpace);
// Disable all user interactions,
mCameraAppUI.setDisableAllUserInteractions(true);
} else if (mStorageHint != null) {
mStorageHint.cancel();
mStorageHint = null;
// Re-enable all user interactions.
mCameraAppUI.setDisableAllUserInteractions(false);
}
}
protected void setResultEx(int resultCode) {
mResultCodeForTesting = resultCode;
setResult(resultCode);
}
protected void setResultEx(int resultCode, Intent data) {
mResultCodeForTesting = resultCode;
mResultDataForTesting = data;
setResult(resultCode, data);
}
public int getResultCode() {
return mResultCodeForTesting;
}
public Intent getResultData() {
return mResultDataForTesting;
}
public boolean isSecureCamera() {
return mSecureCamera;
}
@Override
public boolean isPaused() {
return mPaused;
}
@Override
public int getPreferredChildModeIndex(int modeIndex) {
if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
boolean hdrPlusOn = Keys.isHdrPlusOn(mSettingsManager);
if (hdrPlusOn && GcamHelper.hasGcamAsSeparateModule(mFeatureConfig)) {
modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
}
}
return modeIndex;
}
@Override
public void onModeSelected(int modeIndex) {
if (mCurrentModeIndex == modeIndex) {
return;
}
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
// Record last used camera mode for quick switching
if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
|| modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_CAMERA_MODULE_LAST_USED,
modeIndex);
}
closeModule(mCurrentModule);
// Select the correct module index from the mode switcher index.
modeIndex = getPreferredChildModeIndex(modeIndex);
setModuleFromModeIndex(modeIndex);
mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
mCameraAppUI.addShutterListener(mCurrentModule);
openModule(mCurrentModule);
// Store the module index so we can use it the next time the Camera
// starts up.
mSettingsManager.set(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_STARTUP_MODULE_INDEX, modeIndex);
}
/**
* Shows the settings dialog.
*/
@Override
public void onSettingsSelected() {
UsageStatistics.instance().controlUsed(
eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
Intent intent = new Intent(this, CameraSettingsActivity.class);
if (!isKeyguardLocked()) {
startActivity(intent);
} else {
/* Need to explicitly request keyguard dismissal for PIN/pattern
* entry to show up directly. */
requestDismissKeyguard(
/* requesting Activity: */ CameraActivity.this,
new KeyguardDismissCallback() {
@Override
public void onDismissSucceeded() {
/* Need to use launchActivityByIntent() so that going
* back from settings after unlock leads to main
* activity instead of dismissing camera entirely. */
launchActivityByIntent(intent);
}
@Override
public void onDismissError() {
Log.e(TAG, "Keyguard dismissal failed.");
}
@Override
public void onDismissCancelled() {
Log.d(TAG, "Keyguard dismissal canceled.");
}
}
);
}
}
@Override
public void freezeScreenUntilPreviewReady() {
mCameraAppUI.freezeScreenUntilPreviewReady();
}
@Override
public int getModuleId(int modeIndex) {
ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
if (agent == null) {
return -1;
}
return agent.getModuleId();
}
/**
* Sets the mCurrentModuleIndex, creates a new module instance for the given
* index an sets it as mCurrentModule.
*/
private void setModuleFromModeIndex(int modeIndex) {
ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
if (agent == null) {
return;
}
if (!agent.requestAppForCamera()) {
mCameraController.closeCamera(true);
}
mCurrentModeIndex = agent.getModuleId();
mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
}
@Override
public SettingsManager getSettingsManager() {
return mSettingsManager;
}
@Override
public ResolutionSetting getResolutionSetting() {
return mResolutionSetting;
}
@Override
public CameraServices getServices() {
return CameraServicesImpl.instance();
}
@Override
public FatalErrorHandler getFatalErrorHandler() {
return mFatalErrorHandler;
}
public List<String> getSupportedModeNames() {
List<Integer> indices = mModuleManager.getSupportedModeIndexList();
List<String> supported = new ArrayList<String>();
for (Integer modeIndex : indices) {
String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
if (name != null && !name.equals("")) {
supported.add(name);
}
}
return supported;
}
@Override
public ButtonManager getButtonManager() {
if (mButtonManager == null) {
mButtonManager = new ButtonManager(this);
}
return mButtonManager;
}
@Override
public SoundPlayer getSoundPlayer() {
return mSoundPlayer;
}
/**
* Launches an ACTION_EDIT intent for the given local data item. If
* 'withTinyPlanet' is set, this will show a disambig dialog first to let
* the user start either the tiny planet editor or another photo editor.
*
* @param data The data item to edit.
*/
public void launchEditor(FilmstripItem data) {
Intent intent = new Intent(Intent.ACTION_EDIT)
.setDataAndType(data.getData().getUri(), data.getData().getMimeType())
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
launchActivityByIntent(intent);
} catch (ActivityNotFoundException e) {
final String msgEditWith = getResources().getString(R.string.edit_with);
launchActivityByIntent(Intent.createChooser(intent, msgEditWith));
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.filmstrip_context_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.tiny_planet_editor:
mMyFilmstripBottomControlListener.onTinyPlanet();
return true;
case R.id.photo_editor:
mMyFilmstripBottomControlListener.onEdit();
return true;
}
return false;
}
/**
* Launch the tiny planet editor.
*
* @param data The data must be a 360 degree stereographically mapped
* panoramic image. It will not be modified, instead a new item
* with the result will be added to the filmstrip.
*/
public void launchTinyPlanetEditor(FilmstripItem data) {
TinyPlanetFragment fragment = new TinyPlanetFragment();
Bundle bundle = new Bundle();
bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getData().getUri().toString());
bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getData().getTitle());
fragment.setArguments(bundle);
fragment.show(getFragmentManager(), "tiny_planet");
}
/**
* Returns what UI mode (capture mode or filmstrip) we are in.
* Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
*/
private int currentUserInterfaceMode() {
int mode = NavigationChange.Mode.UNKNOWN_MODE;
if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
mode = NavigationChange.Mode.PHOTO_CAPTURE;
}
if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
mode = NavigationChange.Mode.VIDEO_CAPTURE;
}
if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
mode = NavigationChange.Mode.LENS_BLUR;
}
if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
mode = NavigationChange.Mode.HDR_PLUS;
}
if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
mode = NavigationChange.Mode.PHOTO_SPHERE;
}
if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
mode = NavigationChange.Mode.PANORAMA;
}
if (mFilmstripVisible) {
mode = NavigationChange.Mode.FILMSTRIP;
}
return mode;
}
private void openModule(CameraModule module) {
module.init(this, isSecureCamera(), isCaptureIntent());
module.hardResetSettings(mSettingsManager);
// Hide accessibility zoom UI by default. Modules will enable it themselves if required.
getCameraAppUI().hideAccessibilityZoomUI();
if (!mPaused) {
module.resume();
UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
NavigationChange.InteractionCause.BUTTON);
updatePreviewVisibility();
}
}
private void closeModule(CameraModule module) {
module.pause();
mCameraAppUI.clearModuleUI();
}
private void performDeletion() {
if (!mPendingDeletion) {
return;
}
hideUndoDeletionBar(false);
mDataAdapter.executeDeletion();
}
public void showUndoDeletionBar() {
if (mPendingDeletion) {
performDeletion();
}
Log.v(TAG, "showing undo bar");
mPendingDeletion = true;
if (mUndoDeletionBar == null) {
ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
mAboveFilmstripControlLayout, true);
mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
button.setOnClickListener(new View.OnClickListener() {
@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);
}
});
// Setting undo bar clickable to avoid touch events going through
// the bar to the buttons (eg. edit button, etc) underneath the bar.
mUndoDeletionBar.setClickable(true);
// When there is user interaction going on with the undo button, we
// do not want to hide the undo bar.
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mIsUndoingDeletion = true;
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
mIsUndoingDeletion = false;
}
return false;
}
});
}
mUndoDeletionBar.setAlpha(0f);
mUndoDeletionBar.setVisibility(View.VISIBLE);
mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
}
private void hideUndoDeletionBar(boolean withAnimation) {
Log.v(TAG, "Hiding undo deletion bar");
mPendingDeletion = false;
if (mUndoDeletionBar != null) {
if (withAnimation) {
mUndoDeletionBar.animate().setDuration(200).alpha(0f)
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
// Do nothing.
}
@Override
public void onAnimationEnd(Animator animation) {
mUndoDeletionBar.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animation) {
// Do nothing.
}
@Override
public void onAnimationRepeat(Animator animation) {
// Do nothing.
}
}).start();
} else {
mUndoDeletionBar.setVisibility(View.GONE);
}
}
}
/**
* Enable/disable swipe-to-filmstrip. Will always disable swipe if in
* capture intent.
*
* @param enable {@code true} to enable swipe.
*/
public void setSwipingEnabled(boolean enable) {
// TODO: Bring back the functionality.
if (isCaptureIntent()) {
// lockPreview(true);
} else {
// lockPreview(!enable);
}
}
// Accessor methods for getting latency times used in performance testing
public long getFirstPreviewTime() {
if (mCurrentModule instanceof PhotoModule) {
long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
if (coverHiddenTime != -1) {
return coverHiddenTime - mOnCreateTime;
}
}
return -1;
}
public long getAutoFocusTime() {
return (mCurrentModule instanceof PhotoModule) ?
((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
}
public long getShutterLag() {
return (mCurrentModule instanceof PhotoModule) ?
((PhotoModule) mCurrentModule).mShutterLag : -1;
}
public long getShutterToPictureDisplayedTime() {
return (mCurrentModule instanceof PhotoModule) ?
((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
}
public long getPictureDisplayedToJpegCallbackTime() {
return (mCurrentModule instanceof PhotoModule) ?
((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
}
public long getJpegCallbackFinishTime() {
return (mCurrentModule instanceof PhotoModule) ?
((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
}
public long getCaptureStartTime() {
return (mCurrentModule instanceof PhotoModule) ?
((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
}
public boolean isRecording() {
return (mCurrentModule instanceof VideoModule) ?
((VideoModule) mCurrentModule).isRecording() : false;
}
public CameraAgent.CameraOpenCallback getCameraOpenErrorCallback() {
return mCameraController;
}
// For debugging purposes only.
public CameraModule getCurrentModule() {
return mCurrentModule;
}
@Override
public void showTutorial(AbstractTutorialOverlay tutorial) {
mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
}
@Override
public void finishActivityWithIntentCompleted(Intent resultIntent) {
finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
}
@Override
public void finishActivityWithIntentCanceled() {
finishActivityWithIntentResult(Activity.RESULT_CANCELED, new Intent());
}
private void finishActivityWithIntentResult(int resultCode, Intent resultIntent) {
mResultCodeForTesting = resultCode;
mResultDataForTesting = resultIntent;
setResult(resultCode, resultIntent);
finish();
}
private void keepScreenOnForAWhile() {
if (mKeepScreenOn) {
return;
}
mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
}
private void resetScreenOn() {
mKeepScreenOn = false;
mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
/**
* @return {@code true} if the Gallery is launched successfully.
*/
private boolean startGallery() {
if (mGalleryIntent == null) {
return false;
}
try {
UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
NavigationChange.InteractionCause.BUTTON);
Intent startGalleryIntent = new Intent(mGalleryIntent);
int currentIndex = mFilmstripController.getCurrentAdapterIndex();
FilmstripItem currentFilmstripItem = mDataAdapter.getItemAt(currentIndex);
if (currentFilmstripItem != null) {
GalleryHelper.setContentUri(startGalleryIntent,
currentFilmstripItem.getData().getUri());
}
launchActivityByIntent(startGalleryIntent);
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Failed to launch gallery activity, closing");
}
return false;
}
private void setNfcBeamPushUriFromData(FilmstripItem data) {
final Uri uri = data.getData().getUri();
if (uri != Uri.EMPTY) {
mNfcPushUris[0] = uri;
} else {
mNfcPushUris[0] = null;
}
}
/**
* Updates the visibility of the filmstrip bottom controls and action bar.
*/
private void updateUiByData(final int index) {
final FilmstripItem currentData = mDataAdapter.getItemAt(index);
if (currentData == null) {
Log.w(TAG, "Current data ID not found.");
hideSessionProgress();
return;
}
updateActionBarMenu(currentData);
/* Bottom controls. */
updateBottomControlsByData(currentData);
if (isSecureCamera()) {
// We cannot show buttons in secure camera since go to other
// activities might create a security hole.
mCameraAppUI.getFilmstripBottomControls().hideControls();
return;
}
setNfcBeamPushUriFromData(currentData);
if (!mDataAdapter.isMetadataUpdatedAt(index)) {
mDataAdapter.updateMetadataAt(index);
}
}
/**
* Updates the bottom controls based on the data.
*/
private void updateBottomControlsByData(final FilmstripItem currentData) {
final CameraAppUI.BottomPanel filmstripBottomPanel =
mCameraAppUI.getFilmstripBottomControls();
filmstripBottomPanel.showControls();
filmstripBottomPanel.setEditButtonVisibility(
currentData.getAttributes().canEdit());
filmstripBottomPanel.setShareButtonVisibility(
currentData.getAttributes().canShare());
filmstripBottomPanel.setDeleteButtonVisibility(
currentData.getAttributes().canDelete());
/* Progress bar */
Uri contentUri = currentData.getData().getUri();
CaptureSessionManager sessionManager = getServices()
.getCaptureSessionManager();
if (sessionManager.hasErrorMessage(contentUri)) {
showProcessError(sessionManager.getErrorMessageId(contentUri));
} else {
filmstripBottomPanel.hideProgressError();
CaptureSession session = sessionManager.getSession(contentUri);
if (session != null) {
int sessionProgress = session.getProgress();
if (sessionProgress < 0) {
hideSessionProgress();
} else {
int progressMessageId = session.getProgressMessageId();
showSessionProgress(progressMessageId);
updateSessionProgress(sessionProgress);
}
} else {
hideSessionProgress();
}
}
/* View button */
// We need to add this to a separate DB.
final int viewButtonVisibility;
if (currentData.getMetadata().isUsePanoramaViewer()) {
viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
} else if (currentData.getMetadata().isHasRgbzData()) {
viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
} else {
viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
}
filmstripBottomPanel.setTinyPlanetEnabled(
currentData.getMetadata().isPanorama360());
filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
}
private void showDetailsDialog(int index) {
final FilmstripItem data = mDataAdapter.getItemAt(index);
if (data == null) {
return;
}
Optional<MediaDetails> details = data.getMediaDetails();
if (!details.isPresent()) {
return;
}
Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details.get());
detailDialog.show();
UsageStatistics.instance().mediaInteraction(
fileNameFromAdapterAtIndex(index), MediaInteraction.InteractionType.DETAILS,
NavigationChange.InteractionCause.BUTTON, fileAgeFromAdapterAtIndex(index));
}
/**
* Show or hide action bar items depending on current data type.
*/
private void updateActionBarMenu(FilmstripItem data) {
if (mActionBarMenu == null) {
return;
}
MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
if (detailsMenuItem == null) {
return;
}
boolean showDetails = data.getAttributes().hasDetailedCaptureInfo();
detailsMenuItem.setVisible(showDetails);
}
}