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();
  }
Example #3
0
 @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;
 }
Example #4
0
 @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;
  }