1 /*
2  * Copyright (C) 2006 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 android.content;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 import android.text.TextUtils;
24 import android.util.proto.ProtoOutputStream;
25 
26 import java.io.PrintWriter;
27 
28 /**
29  * Identifier for a specific application component
30  * ({@link android.app.Activity}, {@link android.app.Service},
31  * {@link android.content.BroadcastReceiver}, or
32  * {@link android.content.ContentProvider}) that is available.  Two
33  * pieces of information, encapsulated here, are required to identify
34  * a component: the package (a String) it exists in, and the class (a String)
35  * name inside of that package.
36  *
37  */
38 public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
39     private final String mPackage;
40     private final String mClass;
41 
42     /**
43      * Create a new component identifier where the class name may be specified
44      * as either absolute or relative to the containing package.
45      *
46      * <p>Relative package names begin with a <code>'.'</code> character. For a package
47      * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
48      * will return a ComponentName with the package <code>"com.example"</code>and class name
49      * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
50      * permitted.</p>
51      *
52      * @param pkg the name of the package the component exists in
53      * @param cls the name of the class inside of <var>pkg</var> that implements
54      *            the component
55      * @return the new ComponentName
56      */
createRelative(@onNull String pkg, @NonNull String cls)57     public static @NonNull ComponentName createRelative(@NonNull String pkg, @NonNull String cls) {
58         if (TextUtils.isEmpty(cls)) {
59             throw new IllegalArgumentException("class name cannot be empty");
60         }
61 
62         final String fullName;
63         if (cls.charAt(0) == '.') {
64             // Relative to the package. Prepend the package name.
65             fullName = pkg + cls;
66         } else {
67             // Fully qualified package name.
68             fullName = cls;
69         }
70         return new ComponentName(pkg, fullName);
71     }
72 
73     /**
74      * Create a new component identifier where the class name may be specified
75      * as either absolute or relative to the containing package.
76      *
77      * <p>Relative package names begin with a <code>'.'</code> character. For a package
78      * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
79      * will return a ComponentName with the package <code>"com.example"</code>and class name
80      * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
81      * permitted.</p>
82      *
83      * @param pkg a Context for the package implementing the component
84      * @param cls the name of the class inside of <var>pkg</var> that implements
85      *            the component
86      * @return the new ComponentName
87      */
createRelative(@onNull Context pkg, @NonNull String cls)88     public static @NonNull ComponentName createRelative(@NonNull Context pkg, @NonNull String cls) {
89         return createRelative(pkg.getPackageName(), cls);
90     }
91 
92     /**
93      * Create a new component identifier.
94      *
95      * @param pkg The name of the package that the component exists in.  Can
96      * not be null.
97      * @param cls The name of the class inside of <var>pkg</var> that
98      * implements the component.  Can not be null.
99      */
ComponentName(@onNull String pkg, @NonNull String cls)100     public ComponentName(@NonNull String pkg, @NonNull String cls) {
101         if (pkg == null) throw new NullPointerException("package name is null");
102         if (cls == null) throw new NullPointerException("class name is null");
103         mPackage = pkg;
104         mClass = cls;
105     }
106 
107     /**
108      * Create a new component identifier from a Context and class name.
109      *
110      * @param pkg A Context for the package implementing the component,
111      * from which the actual package name will be retrieved.
112      * @param cls The name of the class inside of <var>pkg</var> that
113      * implements the component.
114      */
ComponentName(@onNull Context pkg, @NonNull String cls)115     public ComponentName(@NonNull Context pkg, @NonNull String cls) {
116         if (cls == null) throw new NullPointerException("class name is null");
117         mPackage = pkg.getPackageName();
118         mClass = cls;
119     }
120 
121     /**
122      * Create a new component identifier from a Context and Class object.
123      *
124      * @param pkg A Context for the package implementing the component, from
125      * which the actual package name will be retrieved.
126      * @param cls The Class object of the desired component, from which the
127      * actual class name will be retrieved.
128      */
ComponentName(@onNull Context pkg, @NonNull Class<?> cls)129     public ComponentName(@NonNull Context pkg, @NonNull Class<?> cls) {
130         mPackage = pkg.getPackageName();
131         mClass = cls.getName();
132     }
133 
clone()134     public ComponentName clone() {
135         return new ComponentName(mPackage, mClass);
136     }
137 
138     /**
139      * Return the package name of this component.
140      */
getPackageName()141     public @NonNull String getPackageName() {
142         return mPackage;
143     }
144 
145     /**
146      * Return the class name of this component.
147      */
getClassName()148     public @NonNull String getClassName() {
149         return mClass;
150     }
151 
152     /**
153      * Return the class name, either fully qualified or in a shortened form
154      * (with a leading '.') if it is a suffix of the package.
155      */
getShortClassName()156     public String getShortClassName() {
157         if (mClass.startsWith(mPackage)) {
158             int PN = mPackage.length();
159             int CN = mClass.length();
160             if (CN > PN && mClass.charAt(PN) == '.') {
161                 return mClass.substring(PN, CN);
162             }
163         }
164         return mClass;
165     }
166 
appendShortClassName(StringBuilder sb, String packageName, String className)167     private static void appendShortClassName(StringBuilder sb, String packageName,
168             String className) {
169         if (className.startsWith(packageName)) {
170             int PN = packageName.length();
171             int CN = className.length();
172             if (CN > PN && className.charAt(PN) == '.') {
173                 sb.append(className, PN, CN);
174                 return;
175             }
176         }
177         sb.append(className);
178     }
179 
printShortClassName(PrintWriter pw, String packageName, String className)180     private static void printShortClassName(PrintWriter pw, String packageName,
181             String className) {
182         if (className.startsWith(packageName)) {
183             int PN = packageName.length();
184             int CN = className.length();
185             if (CN > PN && className.charAt(PN) == '.') {
186                 pw.write(className, PN, CN-PN);
187                 return;
188             }
189         }
190         pw.print(className);
191     }
192 
193     /**
194      * Return a String that unambiguously describes both the package and
195      * class names contained in the ComponentName.  You can later recover
196      * the ComponentName from this string through
197      * {@link #unflattenFromString(String)}.
198      *
199      * @return Returns a new String holding the package and class names.  This
200      * is represented as the package name, concatenated with a '/' and then the
201      * class name.
202      *
203      * @see #unflattenFromString(String)
204      */
flattenToString()205     public @NonNull String flattenToString() {
206         return mPackage + "/" + mClass;
207     }
208 
209     /**
210      * The same as {@link #flattenToString()}, but abbreviates the class
211      * name if it is a suffix of the package.  The result can still be used
212      * with {@link #unflattenFromString(String)}.
213      *
214      * @return Returns a new String holding the package and class names.  This
215      * is represented as the package name, concatenated with a '/' and then the
216      * class name.
217      *
218      * @see #unflattenFromString(String)
219      */
flattenToShortString()220     public @NonNull String flattenToShortString() {
221         StringBuilder sb = new StringBuilder(mPackage.length() + mClass.length());
222         appendShortString(sb, mPackage, mClass);
223         return sb.toString();
224     }
225 
226     /** @hide */
appendShortString(StringBuilder sb)227     public void appendShortString(StringBuilder sb) {
228         appendShortString(sb, mPackage, mClass);
229     }
230 
231     /** @hide */
appendShortString(StringBuilder sb, String packageName, String className)232     public static void appendShortString(StringBuilder sb, String packageName, String className) {
233         sb.append(packageName).append('/');
234         appendShortClassName(sb, packageName, className);
235     }
236 
237     /** @hide */
printShortString(PrintWriter pw, String packageName, String className)238     public static void printShortString(PrintWriter pw, String packageName, String className) {
239         pw.print(packageName);
240         pw.print('/');
241         printShortClassName(pw, packageName, className);
242     }
243 
244     /**
245      * Recover a ComponentName from a String that was previously created with
246      * {@link #flattenToString()}.  It splits the string at the first '/',
247      * taking the part before as the package name and the part after as the
248      * class name.  As a special convenience (to use, for example, when
249      * parsing component names on the command line), if the '/' is immediately
250      * followed by a '.' then the final class name will be the concatenation
251      * of the package name with the string following the '/'.  Thus
252      * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
253      *
254      * @param str The String that was returned by flattenToString().
255      * @return Returns a new ComponentName containing the package and class
256      * names that were encoded in <var>str</var>
257      *
258      * @see #flattenToString()
259      */
unflattenFromString(@onNull String str)260     public static @Nullable ComponentName unflattenFromString(@NonNull String str) {
261         int sep = str.indexOf('/');
262         if (sep < 0 || (sep+1) >= str.length()) {
263             return null;
264         }
265         String pkg = str.substring(0, sep);
266         String cls = str.substring(sep+1);
267         if (cls.length() > 0 && cls.charAt(0) == '.') {
268             cls = pkg + cls;
269         }
270         return new ComponentName(pkg, cls);
271     }
272 
273     /**
274      * Return string representation of this class without the class's name
275      * as a prefix.
276      */
toShortString()277     public String toShortString() {
278         return "{" + mPackage + "/" + mClass + "}";
279     }
280 
281     @Override
toString()282     public String toString() {
283         return "ComponentInfo{" + mPackage + "/" + mClass + "}";
284     }
285 
286     /** Put this here so that individual services don't have to reimplement this. @hide */
writeToProto(ProtoOutputStream proto, long fieldId)287     public void writeToProto(ProtoOutputStream proto, long fieldId) {
288         final long token = proto.start(fieldId);
289         proto.write(ComponentNameProto.PACKAGE_NAME, mPackage);
290         proto.write(ComponentNameProto.CLASS_NAME, mClass);
291         proto.end(token);
292     }
293 
294     @Override
equals(Object obj)295     public boolean equals(Object obj) {
296         try {
297             if (obj != null) {
298                 ComponentName other = (ComponentName)obj;
299                 // Note: no null checks, because mPackage and mClass can
300                 // never be null.
301                 return mPackage.equals(other.mPackage)
302                         && mClass.equals(other.mClass);
303             }
304         } catch (ClassCastException e) {
305         }
306         return false;
307     }
308 
309     @Override
hashCode()310     public int hashCode() {
311         return mPackage.hashCode() + mClass.hashCode();
312     }
313 
compareTo(ComponentName that)314     public int compareTo(ComponentName that) {
315         int v;
316         v = this.mPackage.compareTo(that.mPackage);
317         if (v != 0) {
318             return v;
319         }
320         return this.mClass.compareTo(that.mClass);
321     }
322 
describeContents()323     public int describeContents() {
324         return 0;
325     }
326 
writeToParcel(Parcel out, int flags)327     public void writeToParcel(Parcel out, int flags) {
328         out.writeString(mPackage);
329         out.writeString(mClass);
330     }
331 
332     /**
333      * Write a ComponentName to a Parcel, handling null pointers.  Must be
334      * read with {@link #readFromParcel(Parcel)}.
335      *
336      * @param c The ComponentName to be written.
337      * @param out The Parcel in which the ComponentName will be placed.
338      *
339      * @see #readFromParcel(Parcel)
340      */
writeToParcel(ComponentName c, Parcel out)341     public static void writeToParcel(ComponentName c, Parcel out) {
342         if (c != null) {
343             c.writeToParcel(out, 0);
344         } else {
345             out.writeString(null);
346         }
347     }
348 
349     /**
350      * Read a ComponentName from a Parcel that was previously written
351      * with {@link #writeToParcel(ComponentName, Parcel)}, returning either
352      * a null or new object as appropriate.
353      *
354      * @param in The Parcel from which to read the ComponentName
355      * @return Returns a new ComponentName matching the previously written
356      * object, or null if a null had been written.
357      *
358      * @see #writeToParcel(ComponentName, Parcel)
359      */
readFromParcel(Parcel in)360     public static ComponentName readFromParcel(Parcel in) {
361         String pkg = in.readString();
362         return pkg != null ? new ComponentName(pkg, in) : null;
363     }
364 
365     public static final Parcelable.Creator<ComponentName> CREATOR
366             = new Parcelable.Creator<ComponentName>() {
367         public ComponentName createFromParcel(Parcel in) {
368             return new ComponentName(in);
369         }
370 
371         public ComponentName[] newArray(int size) {
372             return new ComponentName[size];
373         }
374     };
375 
376     /**
377      * Instantiate a new ComponentName from the data in a Parcel that was
378      * previously written with {@link #writeToParcel(Parcel, int)}.  Note that you
379      * must not use this with data written by
380      * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
381      * to handle a null ComponentObject here.
382      *
383      * @param in The Parcel containing the previously written ComponentName,
384      * positioned at the location in the buffer where it was written.
385      */
ComponentName(Parcel in)386     public ComponentName(Parcel in) {
387         mPackage = in.readString();
388         if (mPackage == null) throw new NullPointerException(
389                 "package name is null");
390         mClass = in.readString();
391         if (mClass == null) throw new NullPointerException(
392                 "class name is null");
393     }
394 
ComponentName(String pkg, Parcel in)395     private ComponentName(String pkg, Parcel in) {
396         mPackage = pkg;
397         mClass = in.readString();
398     }
399 
400     /**
401      * Interface for classes associated with a component name.
402      * @hide
403      */
404     @FunctionalInterface
405     public interface WithComponentName {
406         /** Return the associated component name. */
getComponentName()407         ComponentName getComponentName();
408     }
409 }
410