1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.dialer.phonenumberproto;
18 
19 import android.support.annotation.NonNull;
20 import android.support.annotation.WorkerThread;
21 import android.support.v4.util.ArrayMap;
22 import android.support.v4.util.ArraySet;
23 import com.android.dialer.DialerPhoneNumber;
24 import com.android.dialer.common.Assert;
25 import com.google.common.collect.ImmutableMap;
26 import com.google.common.collect.ImmutableSet;
27 import java.util.Map;
28 import java.util.Set;
29 
30 /**
31  * Divides a set of {@link DialerPhoneNumber DialerPhoneNumbers} according to those that are valid
32  * according to libphonenumber, and those that are not.
33  *
34  * <p>Numbers with post-dial portions are always considered invalid as most systems store E164
35  * numbers which do not support post-dial portions.
36  */
37 public final class PartitionedNumbers {
38   private final ImmutableMap<String, ImmutableSet<DialerPhoneNumber>>
39       e164NumbersToDialerPhoneNumbers;
40   private final ImmutableMap<String, ImmutableSet<DialerPhoneNumber>>
41       invalidNumbersToDialerPhoneNumbers;
42 
43   /**
44    * Divides a set of {@link DialerPhoneNumber DialerPhoneNumbers} according to those that are valid
45    * according to libphonenumber, and those that are not.
46    *
47    * <p>Numbers with post-dial portions are always considered invalid as most systems store E164
48    * numbers which do not support post-dial portions.
49    */
50   @WorkerThread
PartitionedNumbers(@onNull ImmutableSet<DialerPhoneNumber> dialerPhoneNumbers)51   public PartitionedNumbers(@NonNull ImmutableSet<DialerPhoneNumber> dialerPhoneNumbers) {
52     Assert.isWorkerThread();
53     Map<String, Set<DialerPhoneNumber>> e164MapBuilder = new ArrayMap<>();
54     Map<String, Set<DialerPhoneNumber>> invalidMapBuilder = new ArrayMap<>();
55 
56     for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) {
57       /*
58        * Numbers with post-dial digits are considered valid and can be converted to E164, but their
59        * post dial digits are lost in the process. For example, the normalized version of a number
60        * with a post-dial portion in the contacts database is stored without the post-dial portion.
61        */
62       if (dialerPhoneNumber.getIsValid() && dialerPhoneNumber.getPostDialPortion().isEmpty()) {
63         String validE164 = dialerPhoneNumber.getNormalizedNumber();
64         Set<DialerPhoneNumber> currentNumbers = e164MapBuilder.get(validE164);
65         if (currentNumbers == null) {
66           currentNumbers = new ArraySet<>();
67           e164MapBuilder.put(validE164, currentNumbers);
68         }
69         currentNumbers.add(dialerPhoneNumber);
70       } else {
71         String invalidNumber = dialerPhoneNumber.getNormalizedNumber();
72         Set<DialerPhoneNumber> currentNumbers = invalidMapBuilder.get(invalidNumber);
73         if (currentNumbers == null) {
74           currentNumbers = new ArraySet<>();
75           invalidMapBuilder.put(invalidNumber, currentNumbers);
76         }
77         currentNumbers.add(dialerPhoneNumber);
78       }
79     }
80 
81     e164NumbersToDialerPhoneNumbers = makeImmutable(e164MapBuilder);
82     invalidNumbersToDialerPhoneNumbers = makeImmutable(invalidMapBuilder);
83   }
84 
85   /** Returns the set of invalid numbers from the original DialerPhoneNumbers */
86   @NonNull
invalidNumbers()87   public ImmutableSet<String> invalidNumbers() {
88     return invalidNumbersToDialerPhoneNumbers.keySet();
89   }
90 
91   /** Returns the set of valid, E164 formatted numbers from the original DialerPhoneNumbers */
92   @NonNull
validE164Numbers()93   public ImmutableSet<String> validE164Numbers() {
94     return e164NumbersToDialerPhoneNumbers.keySet();
95   }
96 
97   /**
98    * Returns the corresponding set of original DialerPhoneNumbers that map to the valid E164 number
99    * from {@link #validE164Numbers()}.
100    *
101    * @throws NullPointerException if there are no numbers found
102    */
103   @NonNull
dialerPhoneNumbersForValidE164(String validE164)104   public ImmutableSet<DialerPhoneNumber> dialerPhoneNumbersForValidE164(String validE164) {
105     return Assert.isNotNull(e164NumbersToDialerPhoneNumbers.get(validE164));
106   }
107 
108   /**
109    * Returns the corresponding set of original DialerPhoneNumbers that map to the invalid number
110    * from {@link #invalidNumbers()}.
111    *
112    * @throws NullPointerException if there are no numbers found
113    */
114   @NonNull
dialerPhoneNumbersForInvalid(String invalidNumber)115   public ImmutableSet<DialerPhoneNumber> dialerPhoneNumbersForInvalid(String invalidNumber) {
116     return Assert.isNotNull(invalidNumbersToDialerPhoneNumbers.get(invalidNumber));
117   }
118 
makeImmutable( Map<K, Set<V>> mutableMapOfSet)119   private static <K, V> ImmutableMap<K, ImmutableSet<V>> makeImmutable(
120       Map<K, Set<V>> mutableMapOfSet) {
121     ImmutableMap.Builder<K, ImmutableSet<V>> mapBuilder = ImmutableMap.builder();
122     for (Map.Entry<K, Set<V>> entry : mutableMapOfSet.entrySet()) {
123       mapBuilder.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
124     }
125     return mapBuilder.build();
126   }
127 }
128