private void updateService() { if (mPassphraseCache.size() > 0) { startForeground(Constants.Notification.PASSPHRASE_CACHE, getNotification()); } else { // stop whole service if no cached passphrases remaining Log.d( Constants.TAG, "PassphraseCacheService: No passphrases remaining in memory, stopping service!"); stopForeground(true); } }
private Notification getNotification() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder .setSmallIcon(R.drawable.ic_stat_notify_24dp) .setColor(getResources().getColor(R.color.primary)) .setContentTitle( getResources() .getQuantityString( R.plurals.passp_cache_notif_n_keys, mPassphraseCache.size(), mPassphraseCache.size())) .setContentText(getString(R.string.passp_cache_notif_touch_to_clear)); NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys)); // Moves events into the big view for (int i = 0; i < mPassphraseCache.size(); i++) { inboxStyle.addLine(mPassphraseCache.valueAt(i).mPrimaryUserId); } // Moves the big view style object into the notification object. builder.setStyle(inboxStyle); Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class); intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR); PendingIntent clearCachePi = PendingIntent.getService( getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); // Add cache clear PI to normal touch builder.setContentIntent(clearCachePi); // Add clear PI action below text builder.addAction( R.drawable.ic_close_white_24dp, getString(R.string.passp_cache_notif_clear), clearCachePi); return builder.build(); }
@Override public long[] getCheckedItemIds() { if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) { return new long[0]; } final LongSparseArray<Integer> idStates = mCheckedIdStates; final int count = idStates.size(); final long[] ids = new long[count]; for (int i = 0; i < count; i++) { ids[i] = idStates.keyAt(i); } return ids; }
@Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeByte((byte) (inActionMode ? 1 : 0)); out.writeInt(checkedItemCount); out.writeSparseBooleanArray(checkState); final int N = checkIdState != null ? checkIdState.size() : 0; out.writeInt(N); for (int i = 0; i < N; i++) { out.writeLong(checkIdState.keyAt(i)); out.writeInt(checkIdState.valueAt(i)); } }
private void removeScreenLockPassphrases() { for (int i = 0; i < mPassphraseCache.size(); ) { CachedPassphrase cPass = mPassphraseCache.valueAt(i); if (cPass.mTimeoutMode == TimeoutMode.LOCK) { // remove passphrase object mPassphraseCache.removeAt(i); continue; } // only do this if we didn't remove at, which continues loop by reducing size! i += 1; } Log.d( Constants.TAG, "PassphraseCacheService Removing all cached-until-lock passphrases from memory!"); updateService(); }
/** * Get the final moves that we want to upsync to the server, setting the status in the DB for all * rows to {@link #STATUS_PROCESSING} that are being updated and to {@link #STATUS_FAILED} for any * old updates. Messages whose sequence of pending moves results in a no-op (i.e. the message has * been moved back to its original folder) have their moves cleared from the DB without any * upsync. * * @param context A {@link Context}. * @param accountId The account we want to update. * @return The final moves to send to the server, or null if there are none. */ public static List<MessageMove> getMoves(final Context context, final long accountId) { final ContentResolver cr = context.getContentResolver(); final Cursor c = getCursor(cr, CONTENT_URI, ProjectionMoveQuery.PROJECTION, accountId); if (c == null) { return null; } // Collapse any rows in the cursor that are acting on the same message. We know the cursor // returned by getRowsToProcess is ordered from oldest to newest, and we use this fact to // get the original and final folder for the message. LongSparseArray<MessageMove> movesMap = new LongSparseArray(); try { while (c.moveToNext()) { final long id = c.getLong(ProjectionMoveQuery.COLUMN_ID); final long messageKey = c.getLong(ProjectionMoveQuery.COLUMN_MESSAGE_KEY); final String serverId = c.getString(ProjectionMoveQuery.COLUMN_SERVER_ID); final long srcFolderKey = c.getLong(ProjectionMoveQuery.COLUMN_SRC_FOLDER_KEY); final long dstFolderKey = c.getLong(ProjectionMoveQuery.COLUMN_DST_FOLDER_KEY); final String srcFolderServerId = c.getString(ProjectionMoveQuery.COLUMN_SRC_FOLDER_SERVER_ID); final String dstFolderServerId = c.getString(ProjectionMoveQuery.COLUMN_DST_FOLDER_SERVER_ID); final MessageMove existingMove = movesMap.get(messageKey); if (existingMove != null) { if (existingMove.mLastId >= id) { LogUtils.w(LOG_TAG, "Moves were not in ascending id order"); } if (!existingMove.mDstFolderServerId.equals(srcFolderServerId) || existingMove.mDstFolderKey != srcFolderKey) { LogUtils.w(LOG_TAG, "existing move's dst not same as this move's src"); } existingMove.mDstFolderKey = dstFolderKey; existingMove.mDstFolderServerId = dstFolderServerId; existingMove.mLastId = id; } else { movesMap.put( messageKey, new MessageMove( messageKey, serverId, id, srcFolderKey, dstFolderKey, srcFolderServerId, dstFolderServerId)); } } } finally { c.close(); } // Prune any no-op moves (i.e. messages that have been moved back to the initial folder). final int moveCount = movesMap.size(); final long[] unmovedMessages = new long[moveCount]; int unmovedMessagesCount = 0; final ArrayList<MessageMove> moves = new ArrayList(moveCount); for (int i = 0; i < movesMap.size(); ++i) { final MessageMove move = movesMap.valueAt(i); // We also treat changes without a server id as a no-op. if ((move.mServerId == null || move.mServerId.length() == 0) || move.mSrcFolderKey == move.mDstFolderKey) { unmovedMessages[unmovedMessagesCount] = move.mMessageKey; ++unmovedMessagesCount; } else { moves.add(move); } } if (unmovedMessagesCount != 0) { deleteRowsForMessages(cr, CONTENT_URI, unmovedMessages, unmovedMessagesCount); } if (moves.isEmpty()) { return null; } return moves; }
/** Executed when service is started by intent */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(Constants.TAG, "PassphraseCacheService.onStartCommand()"); if (intent == null || intent.getAction() == null) { updateService(); return START_STICKY; } String action = intent.getAction(); switch (action) { case ACTION_PASSPHRASE_CACHE_ADD: { long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, -1); long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, -1); long timeoutTtl = intent.getIntExtra(EXTRA_TTL, DEFAULT_TTL); Passphrase passphrase = intent.getParcelableExtra(EXTRA_PASSPHRASE); String primaryUserID = intent.getStringExtra(EXTRA_USER_ID); Log.d( Constants.TAG, "PassphraseCacheService: Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with masterkeyId: " + masterKeyId + ", subKeyId: " + subKeyId + ", ttl: " + timeoutTtl + ", usrId: " + primaryUserID); // if we don't cache by specific subkey id, or the requested subkey is the master key, // just add master key id to the cache, otherwise, add this specific subkey to the cache long referenceKeyId = Preferences.getPreferences(mContext).getPassphraseCacheSubs() ? subKeyId : masterKeyId; CachedPassphrase cachedPassphrase; if (timeoutTtl == 0L) { cachedPassphrase = CachedPassphrase.getPassphraseLock(passphrase, primaryUserID); } else if (timeoutTtl >= Integer.MAX_VALUE) { cachedPassphrase = CachedPassphrase.getPassphraseNoTimeout(passphrase, primaryUserID); } else { cachedPassphrase = CachedPassphrase.getPassphraseTtlTimeout(passphrase, primaryUserID, timeoutTtl); long triggerTime = new Date().getTime() + (timeoutTtl * 1000); // register new alarm with keyId for this passphrase AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, referenceKeyId)); } mPassphraseCache.put(referenceKeyId, cachedPassphrase); break; } case ACTION_PASSPHRASE_CACHE_GET: { long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, Constants.key.symmetric); long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, Constants.key.symmetric); Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); Message msg = Message.obtain(); try { // If only one of these is symmetric, error out! if (masterKeyId == Constants.key.symmetric ^ subKeyId == Constants.key.symmetric) { Log.e( Constants.TAG, "PassphraseCacheService: Bad request, missing masterKeyId or subKeyId!"); msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND; } else { Passphrase passphrase = getCachedPassphraseImpl(masterKeyId, subKeyId); msg.what = MSG_PASSPHRASE_CACHE_GET_OKAY; Bundle bundle = new Bundle(); bundle.putParcelable(EXTRA_PASSPHRASE, passphrase); msg.setData(bundle); } } catch (ProviderHelper.NotFoundException e) { Log.e( Constants.TAG, "PassphraseCacheService: Passphrase for unknown key was requested!"); msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND; } try { messenger.send(msg); } catch (RemoteException e) { Log.e(Constants.TAG, "PassphraseCacheService: Sending message failed", e); } break; } case ACTION_PASSPHRASE_CACHE_CLEAR: { AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); if (intent.hasExtra(EXTRA_SUBKEY_ID) && intent.hasExtra(EXTRA_KEY_ID)) { long referenceKeyId; if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) { referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L); } else { referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L); } // Stop specific ttl alarm and am.cancel(buildIntent(this, referenceKeyId)); mPassphraseCache.delete(referenceKeyId); } else { // Stop all ttl alarms for (int i = 0; i < mPassphraseCache.size(); i++) { CachedPassphrase cachedPassphrase = mPassphraseCache.valueAt(i); if (cachedPassphrase.mTimeoutMode == TimeoutMode.TTL) { am.cancel(buildIntent(this, mPassphraseCache.keyAt(i))); } } mPassphraseCache.clear(); } break; } default: { Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!"); break; } } updateService(); return START_STICKY; }